反汇编代码

根据CPU指令表找到对应的汇编代码

32位反汇编的默认操作数大小为32,若前面加了66前缀可以减小为16,即把eax改为ax;而如果加67可以前缀可以增大大小,但若本来就为eax则没有作用,反而可能使编译出错

示例

1
2
3
4
5
6
7
8
9
6A 00				push 0
E8 7C 00 00 00 call 81
A3 00 30 40 00 mov dword ptr[00403000],eax
E8 B4 00 00 00 call b9
6A 00 push 0
68 EF 11 40 00 push 0x004011ef
6A 00 push 0
6A 65 push 65
FF 35 00 30 40 00 push dword ptr [00403000]
  1. 6A 00 — push 0

    • 6Apush 指令的操作码,用于将一个字节大小的立即数压入堆栈。
    • 00 是立即数,表示将 0 压入堆栈。
  2. E8 7C 00 00 00 — call 81

    • E8call 指令的操作码,用于调用一个相对的函数地址,地址是相对当前指令指针(EIP)的偏移。
    • 7C 00 00 00 表示调用的偏移量是 0x7C,即跳转到当前指令后的 0x7C 字节处。
  3. A3 00 30 40 00 — mov dword ptr [00403000], eax

    • A3mov 指令的操作码,用于将 eax 寄存器中的值移动到内存中。
    • 00 30 40 00 是目标内存地址,表示将 eax 的值存储到 0x00403000 处。
  4. E8 B4 00 00 00 — call b9

    • E8call 指令的操作码,用于调用相对偏移的函数。
    • B4 00 00 00 表示调用的偏移量是 0xB4,即跳转到当前指令后的 0xB4 字节处。
  5. 6A 00 — push 0

    • 同第1行,6A 00 再次将 0 压入堆栈。
  6. 68 EF 11 40 00 — push 0x004011ef

    • 68push 指令的操作码,用于将一个 4 字节的立即数压入堆栈。
    • EF 11 40 00 是立即数,表示将 0x004011EF 压入堆栈。
  7. 6A 00 — push 0

    • 再次将 0 压入堆栈(与前面的 push 0 相同)。
  8. 6A 65 — push 65

    • 6Apush 指令,用于将一个字节大小的立即数压入堆栈。
    • 65 是立即数,表示将 0x65(十进制的 101)压入堆栈。
  9. FF 35 00 30 40 00 — push dword ptr [00403000]

    • FF 35push 指令的操作码,用于将存储在内存地址中的 4 字节的值压入堆栈。
    • 00 30 40 00 是内存地址,表示将 0x00403000 地址中的值压入堆栈。

函数

新建空白工程

image-20240926223941290

函数实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
.386
.model flat,stdcall
option casemap:none

include windows.inc
include user32.inc
include kernel32.inc

includelib user32.lib
includelib kernel32.lib

.const

.data
;AREA ORG 260 它的作用可以是为了将来的数据定义预留的注释。

.code

MyAdd:
push ebp
mov ebp,esp
sub esp,16

push ebx

;ENTER 16,0
mov eax,dword ptr [ebp+8]
mov [ebp-4],eax
mov eax,dword ptr [ebp+12]
mov [ebp-8],eax

pop ebx
leave
;mov esp,ebp
;pop ebp
retn 8
START:
push eax
mov ebp,esp

;函数 栈
push 2
push 1
call MyAdd ;平衡栈
;add esp,8 c调用约定是为了printf等参数不确定的函数

pop eax
invoke ExitProcess,0
ret
end START

文件头部和常量定义

1
2
3
.386
.model flat,stdcall
option casemap:none
  • **.386**:指示编译器生成适用于 80386 或更高版本 CPU 的代码。
  • **.model flat,stdcall**:定义了内存模型为平坦模式(flat),使用 stdcall 调用约定(用于 Windows API 调用)。
  • **option casemap:none**:禁用大小写自动转换,汇编器将区分大小写。

包含 Windows 相关库

1
2
3
4
5
include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib
  • **include**:引入 Windows API 所需的头文件和声明。
  • **includelib**:指定链接的库文件,user32.libkernel32.lib 分别包含常用的 Windows API 函数。

MyAdd 函数

1
2
3
4
5
MyAdd:
push ebp
mov ebp,esp
sub esp,16
push ebx
  • **push ebp**:将当前的基指针 (ebp) 压入栈中,保存上一次的栈帧。
  • **mov ebp,esp**:将当前栈指针 (esp) 赋值给基指针 (ebp),建立新的栈帧。
  • **sub esp,16**:在栈上为局部变量分配 16 字节空间。
  • **push ebx**:将 ebx 寄存器的值压入栈,保护其值不被修改。
1
2
3
4
mov eax,dword ptr [ebp+8]
mov [ebp-4],eax
mov eax,dword ptr [ebp+12]
mov [ebp-8],eax
  • **mov eax,dword ptr [ebp+8]**:从栈中获取第一个函数参数并存入 eax 寄存器。
  • **mov [ebp-4],eax**:将第一个参数存储到栈中的局部变量位置。
  • **mov eax,dword ptr [ebp+12]**:从栈中获取第二个函数参数并存入 eax
  • **mov [ebp-8],eax**:将第二个参数存储到栈中的局部变量位置。
1
2
3
pop ebx
leave
retn 8
  • **pop ebx**:恢复之前保存的 ebx 寄存器值。
  • **leave**:相当于 mov esp,ebppop ebp,恢复栈指针和基指针。
  • **retn 8**:从函数返回,并在返回时清理 8 字节的栈参数(即两个 DWORD 参数)。

程序入口 START

1
2
3
START:
push eax
mov ebp,esp
  • **push eax**:将 eax 压入栈,保存其值。
  • **mov ebp,esp**:将当前栈指针复制到基指针,建立栈帧。
1
2
3
push 2
push 1
call MyAdd
  • **push 2**:将值 2 压入栈作为第二个参数。
  • **push 1**:将值 1 压入栈作为第一个参数。
  • **call MyAdd**:调用 MyAdd 函数。
1
2
3
pop eax
invoke ExitProcess,0
ret
  • **pop eax**:将栈顶值弹出到 eax 寄存器中。
  • **invoke ExitProcess,0**:调用 Windows API 函数 ExitProcess 结束程序,参数为 0 表示正常退出。
  • **ret**:返回到调用程序(程序结束)。

调试结果

image-20240927175552098

观察堆栈可看到刚开始被压入栈的两个参数1和2在函数调用的过程中被取出作为函数的局部变量