[32位汇编系列]001- Hello World

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

 

<script type="text/javascript"> </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"></script>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值