32位汇编第二讲,编写窗口程序,加载资源,响应消息,以及调用C库函数
一丶32位汇编编写Windows窗口程序
首先我们知道32位汇编是可以调用Windows API的,那么今天我们就调用windowsAPI来写一个窗口程序
如果你有windows开发知识,那么就很理解了,如果没有,那么跟着我写,跟着步骤去写,那么也可以写出来
首先我们要编写一个窗口程序(使用SDKAPI编写)有几个步骤
1.设计窗口类
2.注册窗口类
3.创建窗口
4.显示窗口
5.更新窗口
6.建立消息循环
7.窗口过程函数
总共需要这几步,每不单独做个讲解.
1.设计窗口类
设计窗口类,顾名思义,就是你要给你的窗口设置一些属性,比如我窗口的风格,名字,类名,图标,菜单什么的
这里windows为我们提供了一个结构体
WNDCLASS结构体,里面就包含了这些属性,我们只需要依次添加,看下WNDCLASS里面的内容
代码语言:javascript复制WNDCLASS
This structure contains the window class attributes that are registered by the RegisterClass function.
typedef struct _WNDCLASS {
UINT style; //窗口的风格
WNDPROC lpfnWndProc; //窗口消息处理的过程函数
int cbClsExtra; //额外内存申请(不重要)
int cbWndExtra; //额外内存申请(不重要)
HANDLE hInstance; //程序的实例句柄
HICON hIcon; //图标
HCURSOR hCursor; //资源光标
HBRUSH hbrBackground; //窗口背景
LPCTSTR lpszMenuName; //窗口名字
LPCTSTR lpszClassName; } WNDCLASS ; //窗口类名
对于上面的结构体,我们只需要里面的参数需要什么内容即可
使用汇编编写:
代码语言:c复制include windows.inc
include user32.inc ;加载要使用的头文件和lib库,至于这些是什么,下面仔细讲解
include kernel32.inc
includelib user32.lib
includelib kernel32.lib
.386
.model FLAT,stdcall
option casemap:none
.const ;常量区
g_szClassName db "ClassName",0 ;窗口类的类名名称
g_szWndName db "WndName",0 ;窗口的名称
.data ;初始化的数据区
.code ;代码区
WinMain proc ;程序启动的时候执行的入口函数
;设计我们的窗口类
LOCAL @wc:WNDCLASS ;定义WNDCLASS,对里面的属性修改
LOCAL @hInstance : HINSTANCE ;定义程序的实例句柄
LOCAL @hWnd:HWND ;定义我们的hWnd,接受创建窗口的时候的返回值
LOCAL @msg:MSG ;定义消息循环的结构体
;思路,第一步,取得窗口的实例句柄,给hInstance
invoke GetModuleHandle,NULL ;调用API即可获取,返回值默认放在Eax当中
mov @hInstance,eax
;check(为了排版,不写检查了)....
;开始给WNDCLASS各种属性赋值
mov @wc.style, CS_VREDRAW or CS_HREDRAW; ;默认,垂直和水平拉伸窗口,窗口内容重新布局和绘制
mov @wc.lpfnWndProc, WindowProc; ;窗口过程函数
mov @wc.cbClsExtra, 0; ;额外内存
mov @wc.cbWndExtra, 0; ;额外内存
mov eax, @hInstance ;实例句柄的值给eax,下方设置进去,(内存到内存不可以,所以中转)
mov @wc.hInstance, eax; ;给窗口设置实例句柄
mov @wc.hIcon, NULL; ;图标资源为NULL
mov @wc.hCursor, NULL; ;鼠标光标为NULL
mov @wc.hbrBackground, COLOR_ACTIVEBORDER; ;设置背景画刷
mov @wc.lpszMenuName, NULL; ;设置菜单名称
mov @wc.lpszClassName,offset g_szClassName;;设置窗口类名名称
;这里就设计完成了,下一步就要注册这个窗口类,
到系统中,所以这里为中间线,注册窗口的代码我会接着这下面继续写,上面的代码就不重复写了,
下面的几个步骤是一样的,最后在把整个的汇编代码贴上
WinMain endp
end WinMain
2.剩余步骤一起执行
代码语言:javascript复制;对于下方的API不熟悉的可以调用MSDN,下载地址在 www.w1x8.com,因为文件太大,所以不上传到课堂资料中了
;注册窗口类
invoke RegisterClass,addr @wc ;在这里我们使用伪指令addr,他的作用是自动帮我们计算局部变量所在的内存地址,
如果对指令不挑明白,可以打开OD找到这个地方看下指令是怎么写的
;创建窗口
invoke CreateWindowEx, ;这里注意一下只能使用CreateWindowEx,因为.inc文件中没有CreateWindows
0 ;窗口的扩展风格
offset g_szClassName, ;窗口的类名
offset g_szWndName, ;窗口的标题名字
WS_OVERLAPPEDWINDOW, ;窗口的风格
CW_USEDEFAULT, ;下面4个默认的分别是否是 窗口的高度 宽度 ,窗口的x,y坐标
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL, ;窗口父类的实例句柄
NULL, ;窗口的菜单
@hInstance, ;程序的实例句柄
NULL ;创建窗口的额外参数
mov @hWnd,eax ;创建窗口后返回一个窗口句柄,返回值地方在eax中,这个上面定义了
;显示窗口
invoke ShowWindow,@hWnd,SW_SHOW ;显示窗口
;更新窗口invoke UpdateWindow,@hWnd
;建立消息循环
.while TRUE
invoke GetMessage,addr @Msg,NULL,0,0
;判断
.if (eax == -1)
.break
.endif
invoke TranslateMessage, addr @msg ;把虚拟键码,转化为键盘按键
invoke DispatchMessage, addr @msg ;把msg中的消息,放到窗口过程中执行
.endw
;建立窗口过程
WindowProc proc hWnd:HWND,uMsg:UINT,wParam,WPARAM,lParam:LPARAM
;判断消息执行
.if uMsg == WM_KEYDOWN
.....;执行你的代码
.endifinvoke DefWindowProc,hWnd,uMsg,wParam,lParam;retWindowProc endp ;函数结束
对于上面的代码,不保证能正确执行,因为编写博客,不能把上面代码调试,所以思路代码都是一样的,
我会发到课堂资料中请参考课堂资料中的代码
3.资源的使用
现在我们还不能使用资源,那我们必须编译一个资源文件,.rc结尾,
资源文件,是vc 6.0中常用的资源文件,而编译资源文件的编译器是.rc.exe,这个编译器我都会放到
课堂资料中
首先编译一个资源弄文件
这里使用VC 6.0编写一个
主要代码就是这里,我们使用rc.exe编译这个资源弄文件(这个文件的后缀名是.rc结尾)
编译出来之后是.RES的文件,我们把它当做obj文件使用,连接到PE文件中(exe文件中)即可
但是我们在设计窗口类的时候,需要使用一下这个菜单资源的ID
菜单资源的ID,在资源对应的Result.h的头文件中,我们拿过来即可.
我们要做的就是把资源变为汇编中的即可
比如上面的DIR_MENU1 代表101
那我们用汇编编写为 IDR_MENU1 EQU 101 即可
我们使用link 连接到一起即可
代码语言:javascript复制link /subsystem:windows 窗口.obj AAA.RES
然后编译出来就有菜单了,如果响应消息,则在窗口过程函数中捕获WM_COMMAND消息即可
然后资源文件其实是二进制,连接到EXE中(也就是放到EXE当中),那么我们使用WinHex可以再不需要源码的
情况下,把名字修改了
我的WinHex没有设置编码,所以看得不太清楚,这里就是存放资源的地方,我们把名字修改了,重新打开我们的窗口
改为Y,重新打开窗口
可以看到,已经修改为YIle了,所以逆向是很好玩的.不需要代码,可以直接修改你的程序
二丶.inc文件格式,和.lib文件的说明
1..inc文件说明
上面我们使用了各种.inc文件,我们看下内部是什么,比如windows.inc
对于.inc文件,有个第三方出的工具,可以自动生成,我们看下(MASM32,会打包)
其中上面画框的使我们需要的,下面的我们不太关系,如果关心,可以自动尝试一下(这个工具建议收藏)
我们编写windows程序的时候,只需要包含一个windows.h即可编写代码,是因为windows.h里面有帮我们定义的各种宏,以及函数的声明,在这里我们使用的.inc也是一样的,所以像上面的各种宏,和使用的函数,我们都不用定义了
这里主要介绍一下,lib 转化为.inc文件,首先我们知道,lib文件中存放了各种函数的声明,参数个数,所以这个工具是提取lib,并且转化为对应的.inc文件
我们看一下吧,随便找个lib拷贝过去
拷贝到工具目录下(tools)
可以看到很多工具,这里 我们使用的是 l2inc 正确的读法 是 lib to inc ,这里的2代表是to的意思
可以看到也有inc转化为lib的,自己尝试
我们拷贝到l2inc文件下
打开CMD,进入当前的路径,输入 l2inc lib文件名 回车即可生成
那我们的汇编程序就可以使用了
inc文件中对应的就是函数的声明,可以看出,参数类型都是DWORD类型的
2.lib文件说明
比如昨天我们编译的HelloWord程序,就要手动编译的时候,加上对应的user32.lib,而user32.lib是保存了dll文件中的 名字,还有导出函数,所以加载了这个lib,会找对应的dll和他的导出函数,进而执行我们的程序
这里在文件内部使用的,所以我们连接的时候不用手动去写了
这里的lib文件是 动态的静态加载
什么意思:
动态的指的就是dll,静态的指的就是dll所对应的lib,这个lib保存了dll的路径信息,还有导出函数信息,当我们连接到EXE中的时候,会从lib中拷贝dll的路径,以及导出函数,然后放到exe当中,
当我们调用的时候,会根据dll的路径,找到对应的dll,根据导出函数,调用dll的导出函数(比如昨天的HELLO信息框)
静态加载:
静态加载则是直接把lib连接到exe当中,(这个lib中放的都是代码),相当于把代码拷贝到exe中,这样调用的时候,直接执行代码,而不从dll中去执行这个API了. 确定点是文件大,不容易维护,优点,这个程序任何windows平台上,都能运行,不管你有没有dll
关于静态加载,和动态加载,在下面的调用C库函数中讲解
三丶动态和静态的使用C库函数
1.首先是动态的使用
动态的使用我们需要加上 msvcrt.inc然后还需要msvcrt.lib
.inc 我们知道存的是函数的声明, 而.lib则是存放的dll的路径,以及导出函数
例子:
代码语言:javascript复制.386
.model FLAT,stdcall
option casemap:none
;__UNICODE__ equ
include msvcrt.inc
includelib msvcrt.lib ;crt_ 动态使用
.data
g_SzBuff db 100 dup(0) ;使用Strcpy,拷贝到这里面
g_SiTile db "Hello",0 ;把Hello拷贝到szBuff里面
.const
.code
START:
invoke crt_strcpy ,offset g_SzBuff,offset g_SiTile ;拷贝字符串,为什么使用crt开头,因为调用约定是C,作者
;调用约定是C,那么会有名称粉碎,每次比如strcpy,则在前边加上
;_开头,如果是std调用约定,则在后面加上@符号,所以作者为了省事
;在_strcpy加上了crt,这样简单
ret
end START
看下编译出的程序,使用OD调试查看
我们要拷贝字符串,则看下是否成功拷贝
拷贝后
然后我们 ALT E 看下模块表,可以找到我们的MSVCRT
可以看出调用的是这个.dll的内容
看下Call
Call后面则不一样,表明调用的是Dll中,然后看下面的代码,有个 add ESP,0X8,则表明strcpy是一个C调用约定
因为C调用约定必须外面平栈
2.静态的使用
静态的使用,则用libc.lib,这里面存放了代码,但是需要注意一下,我们提供的工具 MASM32有这个,
而VC 6.0中也有,VS系列也有,至于使用那个版本,就看环境变量谁在前边了,(最好不用MASM32的)
MASM32的libC不全,会导致我们编写代码出错,我们可以从其他位置拷贝一个,放到MASM32的lib文件夹中
(因为我的环境变量他在最前边,所以优先找他,所以我要拷贝,或者你直接拷贝到根目录下)
静态使用分为两步
1.包含lib includelib libc.lib
2.对你使用的函数声明一下,因为没有inc文件了,所以都要自己声明
例子:
代码语言:javascript复制.386
.model FLAT,stdcall
option casemap:none
;__UNICODE__ equ
; include msvcrt.inc
; includelib msvcrt.lib ;crt_ 动态使用
includelib libc.lib ;静态使用
strcpy proto c, :dword, :dword ;声明函数
.data
g_SzBuff db 100 dup(0) ;使用Strcpy,拷贝到这里面
g_SiTile db "Hello",0 ;把Hello拷贝到szBuff里面
.const
.code
START:
; invoke crt_strcpy ,offset g_SzBuff,offset g_SiTile ;拷贝字符串,为什么使用crt开头,因为调用约定是C,作者
; ;调用约定是C,那么会有名称粉碎,每次比如strcpy,则在前边加上
; ;_开头,如果是std调用约定,则在后面加上@符号,所以作者为了省事
; ;在_strcpy加上了crt,这样简单
;静态使用
invoke strcpy, offset g_SzBuff,offset g_SiTile
ret
end START
看下OD调试(对于编译连接,这里不说了,很常用了,不会的自己多敲几遍,对于以后新增加编译选项则会对应的讲解一下)
我们可以看到,CALL直接成为了地址了,因为代码就在我么我们的EXE文件中,所以直接在对应的地址找到代码的执行位置执行即可.
第二讲资料:
链接:http://pan.baidu.com/s/1qXIW2sc 密码:zpq1
32汇编所有资料链接请看所有资料分享的总博客,里面集合了链接