您的位置:首页 > 其它

[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的异同点

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: