[32位汇编系列]001- Hello World
2009-07-12 21:56
274 查看
对于windows程序员来说, 不会汇编, 永远只能是菜鸟, 虽然这句话说的很绝对, 但是事实却是如此。也许你不会汇编也能写出很漂亮的程序,但是,你不会对windows的编程有很深入的了解。windows是非开源操作系统,没有源代码可看,要想深入了解它,必须得会汇编,通过反汇编,来了解其中的机制。病毒、反毒、脱壳、破解、外挂、逆向等,这些牛叉的技术, 没有一样不用汇编的,所以,从今天起,我们一块步入汇编的大门,一起来修炼汇编这项“内功”。
我并不打算从每个寄存器讲起,或者从进制讲起,这些基本的东西, 上网查一查,或者在学习的过程中,慢慢去学习,因为这些东西都是死记硬背的东西,没有什么可讲的。
每个课程都会用至少一个例子来说明问题,只有例子和代码, 才能锻炼人,才能真正学到东西。现在我们开始第一个,Hello World, 虽然一提这个会被高手嘲笑, 但是谁还没被嘲笑过呢?
现在来看代码:
.386
.model flat, stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data
szCaption db 'A Message Box !', 0
szText db 'Hello World !', 0
.code
start:
invoke MessageBox, NULL, offset szText, offset szCaption, MB_OK
invoke ExitProcess, 0
end start
.386 伪指令表示当前使用的平台,一般的应用程序都是这个
.model flat, stdcall 表示内存模式为平坦模式,函数调用方式为stdcall
option casemap:none 表示不忽略大小写,因为windows的api函数是大小写区分的,所以,这里也必须区分大小写
include 指明用到的头文件, 在汇编里面,头文件的扩展名一般为inc
includelib 指明用到的链接库, 通常为user32.lib, kernel32.lib, 如果用到其它的库,需要再加上
.data 表示数据段,这里的数据为只读数据,不可更改
.code 表示代码段, 表示这里的都是代码
注意, 这里的段和16位汇编的段是不同的, 这里的段实际上就是一个节[Section], 关于可执行文件[PE]将在后面的教程中提到。
db 就是define byte, 表示定义字节型的变量
szCaption db 'A Message Box !', 0
表示定义一个szCaption的ascii字符串,字符串以0结尾,szText同理
start:
是一个标签,没有实际意义, 只是用来定位代码的,和高级语言中的标签是一样的
end start
表示到这里,代码结束, 编译器对于此句后面的所有代码将会忽略
基本的伪指令讲完了, 看看实际的代码:
invoke MessageBox, NULL, offset szText, offset szCaption, MB_OK
这句代码,表示调用user32.dll中MessageBox函数,显示一个对话框,标题是szCaption表示的字符串,内容是szText表示的字符串, offset 表示字符串的偏移地址, 这个跟高级语言中的调用很相似, invoke伪指令表示调用这个函数,它会把函数的各个参数依次压入栈, 然后再调用call指令来调用这个函数。
invoke ExitProcess, 0
调用kernel32.dll中的ExitProcess函数, 退出应用程序,退出代码为0, 表示程序正常退出
代码很简单, 下面我们看一看如何编译并且链接这段代码。
我们要编译汇编程序, 必须下载masm32, http://movsd.com/
上面有下载, 下载完毕后, 我们假设安装在D:/Masm32目录下, 在编译程序之前, 需要设置好程序的包含文件以及库文件的位置,可以用下面的批处理来做:
set include=d:/masm32/include
set lib=d:/masm32/lib
set path=d:/masm32/bin;%path%
我们将上述代码保存为env.bat, 然后打开一个命令行窗口, 运行上述批处理, 之后, 我们便可以来编译汇编程序了:
ml /c /coff HelloWorld.asm
link /subsystem:windows HelloWorld.obj
ml 表示编译, 将一个汇编源程序编译成目标文件obj, /c 表示编译, /coff 表示编译成coff格式的obj
link 表示链接, /subsystem:windows 表示子系统为windows gui, 也就是说将程序编译成图形界面的程序
如果是命令行, 则要用参数/subsystem:console
如果代码没有错误, ml 将生成 HelloWorld.obj文件, link 将生成 HelloWorld.exe文件, 运行该程序后, 将会出现一个经典的hello world 对话框。
看起来很简单, 代码和高级语言很多地方相似, 尤其invoke宏的使用, 使得函数的调用非常接近高级语言的函数调用, 下面我们来看一看反汇编后的代码和这个源程序的汇编有何不同:
00401000 >/$ 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00401002 |. 68 00304000 push 00403000 ; |Title = "A Message Box !"
00401007 |. 68 10304000 push 00403010 ; |Text = "Hello World !"
0040100C |. 6A 00 push 0 ; |hOwner = NULL
0040100E |. E8 07000000 call <jmp.&user32.MessageBoxA> ; /MessageBoxA
00401013 |. 6A 00 push 0 ; /ExitCode = 0
00401015 /. E8 06000000 call <jmp.&kernel32.ExitProcess> ; /ExitProcess
0040101A $- FF25 08204000 jmp dword ptr [<&user32.MessageBoxA>>; user32.MessageBoxA
00401020 .- FF25 00204000 jmp dword ptr [<&kernel32.ExitProces>; kernel32.ExitProcess
看起来, 这段代码和源程序还是有很大不同的:
invoke MessageBox, NULL, offset szText, offset szCaption, MB_OK
被翻译成了下面5句代码:
push 0 ; MB_OK 的值
push 00403000 ; szCaption这个字符串的地址
push 00403010 ; szText字符串地址
push 0 ; NULL的值
call <jmp.&user32.MessageBoxA>; 调用user32模块中的MessageBoxA函数
四个push指令, 将MessageBox的四个参数从右到左依次压入栈中, 然后用一个call指令来调用这个函数
敏锐的你可能已经发现, 我们明明调用的是MessageBox函数, 这里怎么成了MessageBoxA了呢?
其实在user32.dll中根本都没有MessageBox这个函数, 而真正的函数是MessageBoxA和MessageBoxW
MessageBoxA表示Ansi版本的函数
MessageBoxW表示Unicode版本的函数
在windows 的api中, 只要涉及到字符串的函数, 都会分成两个, 一个是以A结尾的Ansi版本,一个是以W结尾的Unicode版本
在windows.h中有类似如下的定义:
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif
这个就告诉我们,如果你定义了UNICODE, 则用MessageBoxW,如果没有定义UNICODE,则用MessageBoxA
如此,你就明白了为何这里会是MessageBoxA
push 0 ; 错误代码0, 通常表示程序正常退出
call <jmp.&kernel32.ExitProcess>; 调用kernel32.dll中的ExitProcess函数
这个和上面的MessageBox函数的调用一样, 都是先把参数从右到左压入栈中, 然后再调用想用的函数
从这里, 我们可以看出invoke伪指令的作用:
1. 首先将参数依次压入栈中
2. 接着调用call指令来执行函数
这个HelloWorld程序基本上是windows下的32位汇编编程的一个模板, 以后我们就在这个模板的基础上, 增加新的内容, 同时,我也将会把每个程序都进行反汇编,然后再和汇编代码进行对比, 看看有何异同, 之所以这么做, 是因为我们在反汇编的时候, 看到的代码和实际的代码有的时候会有很大的不同,我们实际写程序的时候, 会有很多的伪指令, 而这些伪指令让我们写程序变得方面, 但是却掩盖了问题的本质, 我们学就要学本质, 而不是形式。
好了, 今天的分析就到此为止, 关于调用约定, 可以参考文章:
调用方式__cdecl和__stdcall的异同点
我并不打算从每个寄存器讲起,或者从进制讲起,这些基本的东西, 上网查一查,或者在学习的过程中,慢慢去学习,因为这些东西都是死记硬背的东西,没有什么可讲的。
每个课程都会用至少一个例子来说明问题,只有例子和代码, 才能锻炼人,才能真正学到东西。现在我们开始第一个,Hello World, 虽然一提这个会被高手嘲笑, 但是谁还没被嘲笑过呢?
现在来看代码:
.386
.model flat, stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data
szCaption db 'A Message Box !', 0
szText db 'Hello World !', 0
.code
start:
invoke MessageBox, NULL, offset szText, offset szCaption, MB_OK
invoke ExitProcess, 0
end start
.386 伪指令表示当前使用的平台,一般的应用程序都是这个
.model flat, stdcall 表示内存模式为平坦模式,函数调用方式为stdcall
option casemap:none 表示不忽略大小写,因为windows的api函数是大小写区分的,所以,这里也必须区分大小写
include 指明用到的头文件, 在汇编里面,头文件的扩展名一般为inc
includelib 指明用到的链接库, 通常为user32.lib, kernel32.lib, 如果用到其它的库,需要再加上
.data 表示数据段,这里的数据为只读数据,不可更改
.code 表示代码段, 表示这里的都是代码
注意, 这里的段和16位汇编的段是不同的, 这里的段实际上就是一个节[Section], 关于可执行文件[PE]将在后面的教程中提到。
db 就是define byte, 表示定义字节型的变量
szCaption db 'A Message Box !', 0
表示定义一个szCaption的ascii字符串,字符串以0结尾,szText同理
start:
是一个标签,没有实际意义, 只是用来定位代码的,和高级语言中的标签是一样的
end start
表示到这里,代码结束, 编译器对于此句后面的所有代码将会忽略
基本的伪指令讲完了, 看看实际的代码:
invoke MessageBox, NULL, offset szText, offset szCaption, MB_OK
这句代码,表示调用user32.dll中MessageBox函数,显示一个对话框,标题是szCaption表示的字符串,内容是szText表示的字符串, offset 表示字符串的偏移地址, 这个跟高级语言中的调用很相似, invoke伪指令表示调用这个函数,它会把函数的各个参数依次压入栈, 然后再调用call指令来调用这个函数。
invoke ExitProcess, 0
调用kernel32.dll中的ExitProcess函数, 退出应用程序,退出代码为0, 表示程序正常退出
代码很简单, 下面我们看一看如何编译并且链接这段代码。
我们要编译汇编程序, 必须下载masm32, http://movsd.com/
上面有下载, 下载完毕后, 我们假设安装在D:/Masm32目录下, 在编译程序之前, 需要设置好程序的包含文件以及库文件的位置,可以用下面的批处理来做:
set include=d:/masm32/include
set lib=d:/masm32/lib
set path=d:/masm32/bin;%path%
我们将上述代码保存为env.bat, 然后打开一个命令行窗口, 运行上述批处理, 之后, 我们便可以来编译汇编程序了:
ml /c /coff HelloWorld.asm
link /subsystem:windows HelloWorld.obj
ml 表示编译, 将一个汇编源程序编译成目标文件obj, /c 表示编译, /coff 表示编译成coff格式的obj
link 表示链接, /subsystem:windows 表示子系统为windows gui, 也就是说将程序编译成图形界面的程序
如果是命令行, 则要用参数/subsystem:console
如果代码没有错误, ml 将生成 HelloWorld.obj文件, link 将生成 HelloWorld.exe文件, 运行该程序后, 将会出现一个经典的hello world 对话框。
看起来很简单, 代码和高级语言很多地方相似, 尤其invoke宏的使用, 使得函数的调用非常接近高级语言的函数调用, 下面我们来看一看反汇编后的代码和这个源程序的汇编有何不同:
00401000 >/$ 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00401002 |. 68 00304000 push 00403000 ; |Title = "A Message Box !"
00401007 |. 68 10304000 push 00403010 ; |Text = "Hello World !"
0040100C |. 6A 00 push 0 ; |hOwner = NULL
0040100E |. E8 07000000 call <jmp.&user32.MessageBoxA> ; /MessageBoxA
00401013 |. 6A 00 push 0 ; /ExitCode = 0
00401015 /. E8 06000000 call <jmp.&kernel32.ExitProcess> ; /ExitProcess
0040101A $- FF25 08204000 jmp dword ptr [<&user32.MessageBoxA>>; user32.MessageBoxA
00401020 .- FF25 00204000 jmp dword ptr [<&kernel32.ExitProces>; kernel32.ExitProcess
看起来, 这段代码和源程序还是有很大不同的:
invoke MessageBox, NULL, offset szText, offset szCaption, MB_OK
被翻译成了下面5句代码:
push 0 ; MB_OK 的值
push 00403000 ; szCaption这个字符串的地址
push 00403010 ; szText字符串地址
push 0 ; NULL的值
call <jmp.&user32.MessageBoxA>; 调用user32模块中的MessageBoxA函数
四个push指令, 将MessageBox的四个参数从右到左依次压入栈中, 然后用一个call指令来调用这个函数
敏锐的你可能已经发现, 我们明明调用的是MessageBox函数, 这里怎么成了MessageBoxA了呢?
其实在user32.dll中根本都没有MessageBox这个函数, 而真正的函数是MessageBoxA和MessageBoxW
MessageBoxA表示Ansi版本的函数
MessageBoxW表示Unicode版本的函数
在windows 的api中, 只要涉及到字符串的函数, 都会分成两个, 一个是以A结尾的Ansi版本,一个是以W结尾的Unicode版本
在windows.h中有类似如下的定义:
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif
这个就告诉我们,如果你定义了UNICODE, 则用MessageBoxW,如果没有定义UNICODE,则用MessageBoxA
如此,你就明白了为何这里会是MessageBoxA
push 0 ; 错误代码0, 通常表示程序正常退出
call <jmp.&kernel32.ExitProcess>; 调用kernel32.dll中的ExitProcess函数
这个和上面的MessageBox函数的调用一样, 都是先把参数从右到左压入栈中, 然后再调用想用的函数
从这里, 我们可以看出invoke伪指令的作用:
1. 首先将参数依次压入栈中
2. 接着调用call指令来执行函数
这个HelloWorld程序基本上是windows下的32位汇编编程的一个模板, 以后我们就在这个模板的基础上, 增加新的内容, 同时,我也将会把每个程序都进行反汇编,然后再和汇编代码进行对比, 看看有何异同, 之所以这么做, 是因为我们在反汇编的时候, 看到的代码和实际的代码有的时候会有很大的不同,我们实际写程序的时候, 会有很多的伪指令, 而这些伪指令让我们写程序变得方面, 但是却掩盖了问题的本质, 我们学就要学本质, 而不是形式。
好了, 今天的分析就到此为止, 关于调用约定, 可以参考文章:
调用方式__cdecl和__stdcall的异同点
相关文章推荐
- [32位汇编系列]002 - 创建标准的windows窗口(3)
- 32位汇编语言学习笔记(21)--用NASM实现Hello World小程序
- [32位汇编系列]004 - 对话框资源的使用(1)
- [32位汇编系列]005 - 定时器的使用(2)
- [32位汇编系列]005 - 定时器的使用(1)
- [32位汇编系列]002 - 创建标准的windows窗口(1)
- [32位汇编系列]003 汇编中__stdcall 调用约定以及参数传递
- [32位汇编系列]004 - 对话框资源的使用(2)
- [32位汇编系列]002 - 创建标准的windows窗口(2)
- 高阶数学的力量系列001:用L'Hospital法则证明一些等价无穷小
- 基于Proteus+8051汇编应用实例系列之一--单只数码管循环显示数字
- 自制反汇编逆向分析工具系列
- Hello World 系列文章(未完成)
- 我和汇编的那点事-hello world
- Windows环境下32位汇编语言程序设计第三版学习笔记之一
- 基于Proteus+8051汇编应用实例系列之三--通过按钮分组控制LED灯
- Netty4.x中文教程系列(二) Hello World !
- Netty4.x中文教程系列(一)Hello World !
- 一点新技能:Maven学习系列001
- mips汇编打印 hello world