首页
论坛
课程
招聘
[原创]从MsgBox开始学习汇编
2010-9-29 23:35 7808

[原创]从MsgBox开始学习汇编

2010-9-29 23:35
7808
从MsgBox开始学习汇编
快到十一了,没有什么好礼物送给大家就写个汇编的基础教程给那些和我一样菜菜朋友们吧~~
先写一个大家都很熟悉的程序,呵呵~~
.386
.model flat,stdcall
option casemap:none

include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib

.data
szText                db         'This is a Test',0
szCaption       db      'Test',0

.code
start:
        invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK
        invoke ExitProcess,NULL
end start

很简单是吧,比HelloWorld还要简单~~有人会问,这样一个程序有什么用?其实我们中国人要培养一种能力,观察发现问题的能力,往往越是简单的东西,
里面却隐藏着最大的智慧,你去看那些老外写的书,一般都很简单,但深挖下去就会深不见底,就像1+1=2?但有多少人问为什么?可是到目前为止还没有研究出来,只有咱国的陈景润做到了1+2吧,当时好像美国一家研究所花天价全世界请科学家搞,可是老陈还是没去,可惜~~很久以前看的,也不是很记得了,~~~不好意思,又扯了这么多闲话!其实我只想说不要好高骛远,不管学什么都要一步一个脚印~~把地基打牢了,才能盖出苍天大楼~~

我们把上面的代码改一下,写成这样(前面的基本一样,我只写.code部分)
.code
_MsgBox proc
       
        push MB_OK
        push offset szCaption
        push offset szText
        push NULL
        call MessageBox       
        ret

_MsgBox endp

start:
        invoke _MsgBox
        invoke ExitProcess,NULL
end start
这样子,我们的代码更加清楚了,可以知道程序是怎么样工作的了,结合这个例子,我给大家讲讲过程调用与返回令,以及堆栈的操作!
首先我们来OD载入一下程序,如下所示:
00401000  /$  6A 00         PUSH 0                                   ; /Style = MB_OK|MB_APPLMODAL
00401002  |.  68 0F304000   PUSH MsgBox.0040300F                     ; |Title = "Test"
00401007  |.  68 00304000   PUSH MsgBox.00403000                     ; |Text = "This is a Test"
0040100C  |.  6A 00         PUSH 0                                   ; |hOwner = NULL
0040100E  |.  E8 0D000000   CALL <JMP.&user32.MessageBoxA>           ; \MessageBoxA
00401013  \.  C3            RETN
00401014 >/$  E8 E7FFFFFF   CALL MsgBox.00401000
00401019  |.  6A 00         PUSH 0                                   ; /ExitCode = 0
0040101B  \.  E8 06000000   CALL <JMP.&kernel32.ExitProcess>         ; \ExitProcess
00401020   $- FF25 08204000 JMP DWORD PTR DS:[<&user32.MessageBoxA>] ;  user32.MessageBoxA
00401026   .- FF25 00204000 JMP DWORD PTR DS:[<&kernel32.ExitProcess>;  kernel32.ExitProcess
程序停留在00401014处,F7进入
这里是一个函数调用CALL指令,(科普一下)过程调用指令首先把子程序的返回地址(即CALL指令下面的一行指令的地址)压入到堆栈,以便子程序执行完之
后返回调用程序继续往下执行,请看00401014这行CALL MsgBox.00401000这是一个段内直接调用指令,具体的操作如下:
ESP<---ESP-4
[ESP]<----EIP
EIP<---EIP+disp
       
这里给大家说一下disp是什么意思:返回地址与子程入口地址的差值(F7进入CALL)看堆栈窗口

       ..........        调用MessageBoxA函数
       0             ESP-4----->0013FFB0
           szText           ESP-4----->0013FFB4
           szCaption     ESP-4----->0013FFB8
           0                   ESP-4----->0013FFBC
           00401019     ESP-4----->0013FFC0   将返回地址00401019压入到堆栈
       7C817077   ESP--->0013FFC4

0013FFAC   00401013  /CALL 到 MessageBoxA 来自 MsgBox.0040100E
0013FFB0   00000000  |hOwner = NULL
0013FFB4   00403000  |Text = "This is a Test"
0013FFB8   0040300F  |Title = "Test"
0013FFBC   00000000  \Style = MB_OK|MB_APPLMODAL

当运行完MessageBoxA,到RETN的时候,请大家看堆栈窗口
0013FFC0   00401019  返回到 MsgBox.<模块入口点>+5 来自 MsgBox.00401000
在执行MessageBoxA的函数时将从上向下弹出堆栈中的值,直到0013FFC0处
MessageBox,0,addr szText,addr szCaption,0
RETN指令是从堆栈中弹出一个字,送到指令EIP中,现在大家看堆栈中的值为ESP 0013FFC0
EIP<---[ESP]
ESP<---ESP+4
执行完RETN后,请看ESP,EIP的值:
EIP=[ESP]也就是ESP地址处的值为00401019
ESP=0013FFC0+4=0013FFC4

看着上面好像为头晕了,在此总结一下,主要有两点请大家记住:
第一:过程调用与返回指令
过程调用还有一种方法就是JMP法,其实CALL指令用于调用函数,并执行,RETN指令用于返回调用函数处的下一条指令,继续执行!!
第二:PUSH和POP指令
PUSH指令,压堆栈
ESP<---ESP-4
[ESP]<---SRC
POP指令,弹堆栈
[ESP]--->DST
ESP<---ESP+4
PUSH和POP必须成对出现,不然会出错,如:
PUSH DS
PUSH CS
....
POP CS
POP DS
正好是相反的

上面讲了两个知识点,下面我还想讲讲汇编中的参数传递,首先我们把汇编源代码改一下,如下 :
.code
_MsgBox proc
       

        push MB_OK
        push ebx
        push ecx
        push NULL
        call MessageBox       
       
        pop ecx
        pop ebx
        ret
       
_MsgBox endp

start:
        mov ebx,offset szCaption
        mov ecx,offset szText
        call _MsgBox
        invoke ExitProcess,NULL
end start

我想把改成上面的,用通用寄存器来传递参数,可是结果出错了,呵呵,相信上了上面的内容,这个问题好解决了吧,呵呵,当我们用寄存器来传递参数前,一定要记住保持堆栈平衡,不然就会出现莫明其秒的错误,如果调试这样的错误很难发现,当我们用OD调试上面的这个程序的时候就会发现,当执行到子程序RETN的时候,ESP--->0013FFC8里面的值是0000009C,然后在往下面执行的时候,程序会跳到0000009C处,这样程序就出错了,其实根本没有这个地址~~
上面的程序主要是我们不了解API函数,当我们调用API函数,不管是用INVOKE,还是CALL指令,其实编译器都会帮我们自动调整堆栈,所以我们没有必要多此一举,用
POP ecx,POP ebx,呵呵,其实我们只要在在执行操作之前保存ESP的值,然后执行这些操作之后,不管是执行了什么操作,然后用POP esp弹出ESP里的值,然后RET返回,将ESP里的值传给EIP,这样程序照样会正常执行下去不会出错,呵呵~~~
所以我们将代码改成
_MsgBox proc
       
        push esp
        push MB_OK
        push ebx
        push ecx
        push NULL
        call MessageBox       
       
        pop ecx
        pop ebx
        pop esp
        ret
       
_MsgBox endp
这样不管中间执行了什么操作,还是会按我们的要求返回,正常执行

如果将上面的程序改为如下:
计算整数之和,源代码如下:
Sum proc
push esi
push ecx
mov eax,0
L1:
add eax,[esi]
add esi,TYPE DWORD
Loop L1

pop ecx
pop esi
_Sum endp

这里没有用到API函数调用,所以必须自己手加入pop指令,保持堆栈,其实我们还有一种可以偷懒的方法就是与PROC指令配套使用的USES指令,这个指令允许我们修改被列出的所有寄存器,呵呵,爽吧,所以上面的代码可以这样写
_Sum proc uses esi ecx
mov eax,0

L1:       
add eax,[esi]
add esi,4   -->等同于add esi,TYPE DWORD
Loop L1

Ret
_Sum

这里我们用OD调试得下面
00401000  /$  56            PUSH ESI                            ;  MsgBox.00403000
00401001  |.  51            PUSH ECX
00401002  |.  B8 00000000   MOV EAX,0
00401007  |>  0306          /ADD EAX,DWORD PTR DS:[ESI]
00401009  |.  83C6 04       |ADD ESI,4
0040100C  |.^ E2 F9         \LOOPD SHORT MsgBox.00401007
0040100E  |.  59            POP ECX
0040100F  |.  5E            POP ESI

00401000,00401001,0040100E,0040100F这四行不就完成了堆栈操作,呵呵~~
好了,今天就讲到这里,时间不早了,还要上班,可能有些地方讲的不是很清楚,有问题请留言,明天继续能大家讲汇编,呵呵,会一步一步加深的,希望和我一样喜欢汇编的朋友们能和我一起学习~~

[2022冬季班]《安卓高级研修班(网课)》月薪三万班招生中~

收藏
点赞0
打赏
分享
最新回复 (11)
雪    币: 705
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
一九零五 活跃值 2010-9-30 00:55
2
0
嘿嘿,最近也在学Win32汇编,进度挺小,希望LZ持续更新,关注中
雪    币: 217
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
百折不挠 活跃值 2010-9-30 08:56
3
0
MsgBox proc
  
  push esp
  push MB_OK
  push ebx
  push ecx
  push NULL
  call MessageBox  
  
  pop ecx
  pop ebx
  pop esp
  ret
  
_MsgBox endp
这段程序我认为是有问题的。
因为在messagebox内部已经做了四个参数的出栈,在调用函数之前,堆栈是:
NULL
ecx
ebx
MB_OK
esp
call完以后,堆栈应该只剩esp,其它几个参数都被call出栈了,
这个时候再pop ecx,pop ebx,pop esp好象就有问题了吧。
雪    币: 202
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xdjhf 活跃值 2010-9-30 22:20
4
0
我没有去调试这些代码,但我认为楼上的是正解
雪    币: 141
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
XSJS 活跃值 2010-9-30 22:29
5
0
同没有调试,但是肯定楼上两个是对的。
而且PUSH ESP完全没意义,也没试过,貌似直接PUSH ESP的指令是非法,这个是貌似,不记得了
雪    币: 343
活跃值: 活跃值 (10)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
iiii 活跃值 1 2010-9-30 22:37
6
0

_MsgBox proc

push esp
push MB_OK
push ebx
push ecx
push NULL
call MessageBox

pop ecx
pop ebx
pop esp
ret

_MsgBox endp
这样不管中间执行了什么操作,还是会按我们的要求返回,正常执行


天雷滚滚 ~~~~ 楼主改正吧。

- -# 楼主想表达的应该是下面这个。 不过这些东西都没什么意义。

push ebp
mov ebp,esp
........... 不要去动ebp
mov esp,ebp
pop ebp



push esp 是合法操作
雪    币: 1962
活跃值: 活跃值 (2593)
能力值: ( LV12,RANK:520 )
在线值:
发帖
回帖
粉丝
熊猫正正 活跃值 9 2010-10-1 01:12
7
0
谢谢大家,我想我又多学了点东西,呵呵~~
雪    币: 27
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
snakeas 活跃值 2010-10-1 02:05
8
0
支持一下!!!!
雪    币: 13
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Greater 活跃值 2010-11-4 10:38
9
0
学习拉
发现看雪处处是我师
呵呵
雪    币: 200
活跃值: 活跃值 (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kaihua 活跃值 2010-11-6 05:45
10
0
学习拉
学习
雪    币: 346
活跃值: 活跃值 (27)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
wzanthony 活跃值 2010-11-7 01:08
11
0
跑下题,那啥1+1的,LZ还是改改吧。。。
1+1是指一个不小于6的偶数都可以表示为两个奇素数之和,而不是说要证明1+1=2。
至于为什么1+1=2?这个是从定义分析的,自然数加一的意义是此自然数的后一个自然数,1后面是2,所以1(+1)就是2了,1后面要是3,那么1(+1)才会是3。
雪    币: 1962
活跃值: 活跃值 (2593)
能力值: ( LV12,RANK:520 )
在线值:
发帖
回帖
粉丝
熊猫正正 活跃值 9 2010-11-7 01:44
12
0
感谢楼上的指点~~
游客
登录 | 注册 方可回帖
返回