一枚酸心果子

果子果子果子果子果子~~~

汇编语言与逆向工程入门指南

为什么要对汇编有一定入门

现在基本上都是Python,Java,JavaScript等高级编程语言,很少人回去关注汇编语言,去研究一下算是理解计算机底层原理、进行逆向工程和安全研究的必要技能吧,至少能帮助自己更好的理解逻辑底层

I. 汇编语言概述

1. 什么是汇编语言

汇编语言就是用助记符(比如MOV、ADD)来表示机器指令的低级编程语言。它比直接写二进制机器码要容易理解,但比高级语言更接近硬件。

主要特点:

  • 直接控制CPU和内存
  • 执行效率高
  • 依赖具体的CPU架构

常见用途:

  • 系统底层开发(内核、驱动)
  • 逆向分析和安全研究
  • 性能关键代码的优化

2. 汇编语言也有不同“方言”

就像人类语言有不同方言一样,汇编语言也根据硬件和书写习惯分为几种。

按CPU架构分

  • x86/x86-64: 主要用于个人电脑和服务器,如Intel 和AMD 的CPU。
  • ARM/ARM64: 广泛应用于手机、平板电脑等移动设备和嵌入式系统。
  • MIPS/RISC-V: 常用于学术研究和某些精简指令集场景。
  • 其他: 如用于单片机的AVR 架构,或PowerPC 等。

按书写语法分
​- Intel语法​:常见于Windows平台和Intel官方文档,格式是 指令 目标, 源。
​- AT&T语法​:在Unix/Linux世界里更流行,格式是 指令 源, 目标。

3. 寄存器和内存的通信(关键词的介绍)

汇编的作用就是操作CPU进行计算,CPU在计算的时候需要存储计算的值,这些值的存储位置就是CPU的寄存器,复杂的计算还需要内存辅助数据的存储,这里看下内存与CPU寄存器的配合方式。

a. 关于寄存器

CPU的通用寄存器有eax、ebx、ecx、edx等,不同架构提供的通用寄存器数量和名称有区别,这些通用寄存器就是可以给用户侧(汇编代码)自由使用的寄存器,其他的非通用寄存器如eip、esp、eflags等是为了CPU实现一些功能用的,不会开放给用户侧使用。

其中寄存器eax通常会当作返回值使用,比如我们的可执行文件执行完成返回的值(echo $?)其实就是执行完成后寄存器eax当时的值。

寄存器 含义 用途 包含寄存器
EAX 累加(Accumulator)寄存器 常用于乘、除法和函数返回值 AX(AH、AL)
EBX 基址(Base)寄存器 常做内存数据的指针, 或者说常以它为基址来访问内存. BX(BH、BL)
ECX 计数器(Counter)寄存器 常做字符串和循环操作中的计数器 CX(CH、CL)
EDX 数据(Data)寄存器 常用于乘、除法和 I/O 指针 DX(DH、DL)
ESI 来源索引(Source Index)寄存器 常做内存数据指针和源字符串指针 SI
EDI 目的索引(Destination Index)寄存器 常做内存数据指针和目的字符串指针 DI
ESP 堆栈指针(Stack Point)寄存器 只做堆栈的栈顶指针; 不能用于算术运算与数据传送 SP
EBP 基址指针(Base Point)寄存器 只做堆栈指针, 可以访问堆栈内任意地址, 经常用于中转 ESP 中的数据, 也常以它为基址来访问堆栈; 不能用于算术运算与数据传送 BP
1
2
3
4
5
6
; 一个简单的汇编代码,运行结果是28
global main
main:
mov eax, 20 ; 给eax寄存器赋值
add eax, 8 ; 给eax的值+8
ret ; 结束,返回了eax中的值

看到这里其实可以看出汇编部分的计算逻辑就是将内存中的数据读入到寄存器中,进行计算,然后将结果写回内存。

b. 内存操作方式

操作类型 语法格式 说明 示例
立即数到寄存器 mov reg, 立即数 将常数直接赋值给寄存器 mov eax, 20
寄存器到寄存器 mov reg1, reg2 寄存器间数据传递 mov ebx, eax
内存到寄存器 mov reg, [地址] 从内存读取数据到寄存器 mov eax, [myvar]
寄存器到内存 mov [地址], reg 将寄存器数据写入内存 mov [esi], eax
间接寻址 mov reg, [reg] 通过寄存器间接访问内存 mov eax, [esi]
基址变址 mov reg, [base+index*scale] 数组访问的标准形式 mov eax, [ebx+ecx*4]

c. 内存定义方式

定义指令 含义 占用空间 示例
db define byte 1字节 db 0x41
dw define word 2字节 dw 1234
dd define double word 4字节 dd 0x12345678
dq define quad word 8字节 dq 0x123456789ABCDEF0

d. 栈操作指令

指令 功能 说明 示例
push 压栈 将数据压入栈顶 push eax
pop 弹栈 从栈顶弹出数据 pop ebx
call 调用函数 将返回地址压栈并跳转 call function
ret 返回 从栈中弹出返回地址 ret

e. 寻址模式对比

寻址模式 语法 用途 示例
直接寻址 [地址] 直接访问固定地址 mov eax, [0x1000]
寄存器间接 [reg] 通过寄存器指向的地址 mov eax, [esi]
基址寻址 [base+offset] 基址加偏移量 mov eax, [ebp+8]
变址寻址 [index*scale] 索引乘以比例因子 mov eax, [ecx*4]
基址变址 [base+index*scale] 组合寻址,用于数组 mov eax, [ebx+ecx*4]

II. 汇编指令详解

1. 数据传送指令

数据传送指令用于在寄存器、内存和立即数之间传递数据,是最基础的汇编指令。

a. MOV指令(mov)

MOV指令用于将数据从一个位置复制到另一个位置。

1
2
3
4
5
; Intel语法示例
mov eax, 123 ; 将立即数123传送到EAX寄存器
mov ebx, eax ; 将EAX寄存器的值传送到EBX寄存器
mov [esi], eax ; 将EAX寄存器的值传送到ESI指向的内存地址
mov eax, [ebx+4] ; 将EBX+4地址处的值传送到EAX寄存器

b. LEA指令(lea)

LEA指令用于计算地址并加载到寄存器中,常用于地址计算。

1
2
3
; LEA指令示例
lea eax, [ebx+ecx*4] ; 计算EBX+ECX*4的地址并加载到EAX
lea esi, [ebp-8] ; 计算EBP-8的地址并加载到ESI

2. 算术运算指令

算术运算指令用于执行基本的数学运算,包括加法、减法、乘法和除法。

a. 加法指令(add、adc)

1
2
3
4
5
6
7
; ADD指令示例
add eax, ebx ; EAX = EAX + EBX
add eax, 10 ; EAX = EAX + 10
add [esi], eax ; [ESI] = [ESI] + EAX

; ADC指令示例(带进位加法)
adc eax, ebx ; EAX = EAX + EBX + CF

b. 减法指令(sub、sbb)

1
2
3
4
5
6
; SUB指令示例
sub eax, ebx ; EAX = EAX - EBX
sub eax, 5 ; EAX = EAX - 5

; SBB指令示例(带借位减法)
sbb eax, ebx ; EAX = EAX - EBX - CF

c. 乘法指令(mul、imul)

1
2
3
4
5
6
; MUL指令示例(无符号乘法)
mul ebx ; EDX:EAX = EAX * EBX

; IMUL指令示例(有符号乘法)
imul eax, ebx ; EAX = EAX * EBX
imul eax, ebx, 10 ; EAX = EBX * 10

d. 除法指令(div、idiv)

1
2
3
4
5
; DIV指令示例(无符号除法)
div ebx ; EAX = EDX:EAX / EBX, EDX = 余数

; IDIV指令示例(有符号除法)
idiv ebx ; EAX = EDX:EAX / EBX, EDX = 余数

3. 逻辑运算指令

逻辑运算指令用于执行位运算操作,包括与、或、异或、非等操作。

a. 位运算指令(and、or、xor、not)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
; AND指令示例
and eax, ebx ; EAX = EAX & EBX
and eax, 0xFF ; EAX = EAX & 0xFF

; OR指令示例
or eax, ebx ; EAX = EAX | EBX
or eax, 0x80 ; EAX = EAX | 0x80

; XOR指令示例
xor eax, ebx ; EAX = EAX ^ EBX
xor eax, eax ; EAX = 0(清零操作)

; NOT指令示例
not eax ; EAX = ~EAX

b. 移位指令(shl、shr、sar)

1
2
3
4
5
6
7
; 左移指令
shl eax, 1 ; EAX = EAX << 1
shl eax, cl ; EAX = EAX << CL

; 右移指令
shr eax, 1 ; EAX = EAX >> 1(逻辑右移)
sar eax, 1 ; EAX = EAX >> 1(算术右移)

4. 比较和跳转指令

比较和跳转指令用于实现程序的控制流,是汇编语言中实现条件判断和循环的关键。

a. 比较指令(cmp)

1
2
3
4
; CMP指令示例
cmp eax, ebx ; 比较EAX和EBX,设置标志位
cmp eax, 0 ; 比较EAX和0
cmp [esi], eax ; 比较[ESI]和EAX

b. 跳转指令(jmp、je、jne、jl、jg、jz、jnz)

1
2
3
4
5
6
7
8
9
10
; 无条件跳转
jmp label ; 跳转到label

; 条件跳转
je label ; 相等时跳转(ZF=1)
jne label ; 不相等时跳转(ZF=0)
jl label ; 小于时跳转(SF≠OF)
jg label ; 大于时跳转(SF=OF且ZF=0)
jz label ; 零标志位为1时跳转
jnz label ; 零标志位为0时跳转

5. 栈操作指令

栈操作指令用于管理函数调用和局部变量,是函数调用约定的重要组成部分。

a. 栈操作指令(push、pop)

1
2
3
4
5
6
7
; PUSH指令示例
push eax ; 将EAX压入栈
push 123 ; 将立即数123压入栈

; POP指令示例
pop eax ; 将栈顶元素弹出到EAX
pop ebx ; 将栈顶元素弹出到EBX

b. 栈指针操作(add、sub)

1
2
3
; 栈指针调整
sub esp, 16 ; 分配16字节的栈空间
add esp, 16 ; 释放16字节的栈空间

III. 反汇编技术详解

1. 反汇编基础

反汇编是将机器码转换为汇编代码的过程,是逆向工程的核心技术之一。通过反汇编,我们可以分析程序的逻辑结构、理解算法实现、发现安全漏洞。

a. 反汇编原理

工作原理:

  1. 读取二进制文件的机器码
  2. 根据指令集规范解析指令格式
  3. 将机器码转换为对应的汇编助记符
  4. 分析指令参数和寻址模式
1
2
3
4
5
6
7
8
9
10
11
12
; 机器码示例
55 8B EC 83 EC 10 8B 45 08 03 45 0C 8B E5 5D C3

; 对应的汇编代码
push ebp
mov ebp, esp
sub esp, 10h
mov eax, [ebp+8]
add eax, [ebp+0Ch]
mov esp, ebp
pop ebp
retn

实际操作:

  • 使用十六进制编辑器查看原始机器码
  • 对照指令集手册手动解析(学习用)
  • 使用反汇编工具自动转换

b. 反汇编工具分类

静态反汇编器(不运行程序):

  • IDA Pro:最强大的商业反汇编器
    • 操作:File → Open → 选择目标文件
    • 特点:支持多种架构,插件丰富
  • Ghidra:NSA开源的免费工具
    • 操作:创建项目 → Import → 选择文件
    • 特点:功能强大,完全免费
  • Radare2:命令行反汇编器
    • 操作:r2 -A 目标文件aa(分析)
    • 特点:轻量级,脚本化

动态反汇编器(运行时分析):

  • x64dbg:Windows下的调试器
    • 操作:File → Open → 选择可执行文件
    • 特点:支持x86/x64,界面友好
  • OllyDbg:经典的32位调试器
    • 操作:File → Open → 选择PE文件
    • 特点:插件丰富,适合学习
  • GDB:Linux下的调试器
    • 操作:gdb 程序名rundisassemble
    • 特点:功能强大,命令行操作

2. 静态反汇编技术

静态反汇编是在不运行程序的情况下分析程序结构,通过分析二进制文件来理解程序逻辑。

a. 函数识别

识别方法:

  1. 查找函数序言模式(push ebp; mov ebp, esp)
  2. 查找函数尾声模式(mov esp, ebp; pop ebp; ret)
  3. 分析调用约定和参数传递
1
2
3
4
5
6
7
8
9
; 函数序言(Function Prologue)
.text:00401000 push ebp ; 保存旧的栈帧指针
.text:00401001 mov ebp, esp ; 设置新的栈帧指针
.text:00401003 sub esp, 20h ; 分配局部变量空间

; 函数尾声(Function Epilogue)
.text:00401020 mov esp, ebp ; 恢复栈指针
.text:00401021 pop ebp ; 恢复旧的栈帧指针
.text:00401022 retn ; 返回

IDA Pro操作:

  • P键创建函数
  • F5查看伪代码
  • 右键选择”Create Function”

b. 控制流分析

分析方法:

  1. 识别条件跳转指令(jz, jnz, jl, jg等)
  2. 分析跳转目标和条件
  3. 重构程序的控制流图
1
2
3
4
5
6
7
8
9
; 条件分支示例
.text:00401000 cmp eax, 0
.text:00401003 jz short loc_401010
.text:00401005 mov eax, 1
.text:0040100A jmp short loc_401015
.text:00401010 loc_401010:
.text:00401010 mov eax, 0
.text:00401015 loc_401015:
.text:00401015 retn

IDA Pro操作:

  • 空格键切换图形视图
  • Ctrl+Enter查看交叉引用
  • 使用Xrefs to分析函数调用关系

c. 字符串分析

分析方法:

  1. 查找字符串常量
  2. 分析字符串的引用位置
  3. 理解字符串的用途
1
2
3
4
; 字符串引用示例
.text:00401000 push offset aEnterPassword ; "Enter password: "
.text:00401005 call printf
.text:0040100A add esp, 4

IDA Pro操作:

  • Shift+F12打开字符串窗口
  • 双击字符串跳转到引用位置
  • X查看字符串的交叉引用

3. 动态反汇编技术

动态反汇编是在程序运行时分析其行为,可以获取程序的真实执行信息。

a. 调试器使用

基本操作流程:

  1. 加载目标程序到调试器
  2. 设置断点暂停程序执行
  3. 单步执行观察寄存器变化
  4. 分析内存内容和程序状态
1
2
3
4
5
6
7
8
; 调试器中的寄存器状态
EAX = 00000001
EBX = 00000002
ECX = 00000003
EDX = 00000000
ESP = 0012FF80
EBP = 0012FF88
EIP = 00401000

x64dbg操作:

  • F2:设置/取消断点
  • F7:单步步入(Step Into)
  • F8:单步跳过(Step Over)
  • F9:继续执行(Run)
  • Ctrl+G:跳转到地址

b. 内存分析

分析方法:

  1. 查看程序的内存布局
  2. 分析堆栈和堆的内容
  3. 监控内存读写操作
1
2
3
; 内存转储示例
00401000: 55 8B EC 83 EC 10 8B 45 08 03 45 0C 8B E5 5D C3
00401010: 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

x64dbg操作:

  • Ctrl+M:打开内存窗口
  • Ctrl+Shift+M:打开内存映射
  • Ctrl+Shift+H:打开句柄窗口
  • 右键内存 → “Follow in Dump”

c. 断点设置

断点类型:

  1. 软件断点(0xCC指令)
  2. 硬件断点(CPU调试寄存器)
  3. 内存断点(访问特定内存)
  4. 条件断点(满足条件时触发)
1
2
3
; 断点设置示例
.text:00401000 int3 ; 软件断点
.text:00401001 mov eax, [ebp+8] ; 断点后的代码

x64dbg断点操作:

  • F2:在当前位置设置断点
  • Ctrl+F2:设置条件断点
  • Alt+F2:设置内存断点
  • Shift+F2:设置硬件断点

4. 反汇编技巧与案例

a. 代码混淆识别

控制流平坦化识别:

  1. 查找状态变量(通常是寄存器或内存变量)
  2. 识别大量的无条件跳转(jmp指令)
  3. 观察代码块之间的跳转关系
1
2
3
4
5
6
7
8
9
10
; 控制流平坦化示例
.text:00401000 mov eax, 0 ; 状态变量
.text:00401005 jmp short loc_401020
.text:00401010 loc_401010:
.text:00401010 mov eax, 1
.text:00401015 jmp short loc_401020
.text:00401020 loc_401020:
.text:00401020 cmp eax, 0
.text:00401025 jz short loc_401010
.text:00401030 retn

识别方法:

  • 在IDA Pro中查看控制流图,如果看到大量分散的代码块
  • 查找循环中的状态变量和跳转表
  • 使用脚本自动化识别跳转模式

b. 加密算法识别

AES算法特征识别:

  1. 查找S-Box查找表(通常包含256个字节的常量)
  2. 识别轮密钥扩展函数
  3. 查找MixColumns、SubBytes等变换
1
2
3
4
5
; AES加密识别示例
.text:00401000 mov eax, [ebp+8] ; 输入数据
.text:00401003 mov ebx, [ebp+0Ch] ; 密钥
.text:00401006 call aes_encrypt ; AES加密函数
.text:0040100B mov [ebp+10h], eax ; 输出结果

具体识别步骤:

  • 搜索字符串中的”S-Box”、”AES”等关键词
  • 查找0x63, 0x7C, 0x77等AES S-Box特征字节
  • 分析函数调用关系,找到加密/解密入口点
  • 使用工具如PEiD、Detect It Easy识别加密库

c. 反调试技术识别

调试器检测识别:

  1. 查找IsDebuggerPresent、CheckRemoteDebuggerPresent等API调用
  2. 识别时间检测(GetTickCount、QueryPerformanceCounter)
  3. 查找异常处理相关的反调试代码
1
2
3
4
5
6
7
8
9
10
11
12
; 反调试检测示例
.text:00401000 call GetCurrentProcess
.text:00401005 push eax
.text:00401006 call IsDebuggerPresent
.text:0040100B test eax, eax
.text:0040100D jnz short loc_401020
.text:0040100F mov eax, 1
.text:00401014 jmp short loc_401025
.text:00401020 loc_401020:
.text:00401020 mov eax, 0
.text:00401025 loc_401025:
.text:00401025 retn

绕过方法:

  • 使用调试器插件(如ScyllaHide)自动绕过
  • 手动修改跳转指令(jnz改为jz)
  • Hook相关API返回假值
  • 使用虚拟机或沙箱环境

d. 简单CrackMe分析

分析步骤:

  1. 使用IDA Pro加载程序,查看字符串引用
  2. 定位主函数和关键函数
  3. 分析密码验证逻辑
  4. 找到正确的密码或绕过验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
; 主函数分析
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 push offset aEnterPassword ; "Enter password: "
.text:00401008 call printf
.text:0040100D add esp, 4
.text:00401010 push offset password_buffer
.text:00401015 call scanf
.text:0040101A add esp, 8
.text:0040101D push offset password_buffer
.text:00401022 call check_password
.text:00401027 add esp, 4
.text:0040102A test eax, eax
.text:0040102C jz short loc_401040
.text:0040102E push offset aCorrect ; "Correct!"
.text:00401033 call printf
.text:00401038 jmp short loc_40104E
.text:00401040 loc_401040:
.text:00401040 push offset aWrong ; "Wrong!"
.text:00401045 call printf
.text:0040104E loc_40104E:
.text:0040104E pop ebp
.text:0040104F retn

具体操作:

  • 在IDA中按Shift+F12查看字符串
  • 双击”Enter password”跳转到引用位置
  • 按F5查看伪代码,理解程序流程
  • 在check_password函数设置断点

e. 密码检查函数

分析密码验证逻辑:

  1. 查看字符串比较指令(cmpsb、strcmp等)
  2. 分析比较的目标字符串
  3. 理解返回值逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
; 密码检查函数
.text:00401050 push ebp
.text:00401051 mov ebp, esp
.text:00401053 mov esi, [ebp+8] ; 输入密码
.text:00401056 mov edi, offset correct_password ; 正确密码
.text:0040105B mov ecx, 8 ; 密码长度
.text:00401060 repe cmpsb ; 逐字节比较
.text:00401062 jz short loc_401070
.text:00401064 mov eax, 0 ; 返回0(错误)
.text:00401069 jmp short loc_401075
.text:00401070 loc_401070:
.text:00401070 mov eax, 1 ; 返回1(正确)
.text:00401075 loc_401075:
.text:00401075 pop ebp
.text:00401076 retn

破解方法:

  • 查看correct_password的内容(双击跳转)
  • 修改jz为jnz绕过验证
  • 直接修改返回值(mov eax, 1)
  • 使用十六进制编辑器修改机器码

IV. C代码与汇编对照

1. 基本运算对照

a. 加法运算

C代码:

1
2
3
int add(int a, int b) {
return a + b;
}

对应的汇编代码:

1
2
3
4
5
6
7
8
9
10
11
; 函数序言
push ebp
mov ebp, esp

; 函数体
mov eax, [ebp+8] ; 获取参数a
add eax, [ebp+0Ch] ; 加上参数b,结果存在EAX中

; 函数尾声
pop ebp
retn

b. 条件判断

C代码:

1
2
3
4
5
6
7
int max(int a, int b) {
if (a > b) {
return a;
} else {
return b;
}
}

对应的汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
; 函数序言
push ebp
mov ebp, esp

; 比较a和b
mov eax, [ebp+8] ; 获取参数a
cmp eax, [ebp+0Ch] ; 比较a和b
jg short loc_401010 ; 如果a>b,跳转到loc_401010

; a <= b的情况
mov eax, [ebp+0Ch] ; 返回b
jmp short loc_401015

; a > b的情况
loc_401010:
mov eax, [ebp+8] ; 返回a

loc_401015:
pop ebp
retn

2. 循环结构对照

a. for循环

C代码:

1
2
3
4
5
6
7
int sum(int n) {
int result = 0;
for (int i = 1; i <= n; i++) {
result += i;
}
return result;
}

对应的汇编代码:

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
; 函数序言
push ebp
mov ebp, esp
sub esp, 8 ; 分配局部变量空间

; 初始化
mov dword ptr [ebp-4], 0 ; result = 0
mov dword ptr [ebp-8], 1 ; i = 1

; 循环开始
loc_401010:
mov eax, [ebp-8] ; 获取i的值
cmp eax, [ebp+8] ; 比较i和n
jg short loc_401025 ; 如果i>n,跳出循环

; 循环体
mov eax, [ebp-4] ; 获取result
add eax, [ebp-8] ; result += i
mov [ebp-4], eax ; 保存result

; 循环增量
inc dword ptr [ebp-8] ; i++
jmp short loc_401010 ; 跳回循环开始

; 循环结束
loc_401025:
mov eax, [ebp-4] ; 返回result
mov esp, ebp
pop ebp
retn

b. while循环

C代码:

1
2
3
4
5
6
7
8
int factorial(int n) {
int result = 1;
while (n > 1) {
result *= n;
n--;
}
return result;
}

对应的汇编代码:

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
; 函数序言
push ebp
mov ebp, esp
sub esp, 4 ; 分配局部变量空间

; 初始化
mov dword ptr [ebp-4], 1 ; result = 1

; 循环开始
loc_401010:
cmp dword ptr [ebp+8], 1 ; 比较n和1
jle short loc_401025 ; 如果n<=1,跳出循环

; 循环体
mov eax, [ebp-4] ; 获取result
imul eax, [ebp+8] ; result *= n
mov [ebp-4], eax ; 保存result

; 循环减量
dec dword ptr [ebp+8] ; n--
jmp short loc_401010 ; 跳回循环开始

; 循环结束
loc_401025:
mov eax, [ebp-4] ; 返回result
mov esp, ebp
pop ebp
retn

3. 数组操作对照

a. 数组访问

C代码:

1
2
3
int getArrayElement(int arr[], int index) {
return arr[index];
}

对应的汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
; 函数序言
push ebp
mov ebp, esp

; 计算数组元素地址
mov eax, [ebp+8] ; 获取数组首地址
mov ecx, [ebp+0Ch] ; 获取索引
mov eax, [eax+ecx*4] ; 计算arr[index]的地址并取值

; 函数尾声
pop ebp
retn

b. 数组赋值

C代码:

1
2
3
void setArrayElement(int arr[], int index, int value) {
arr[index] = value;
}

对应的汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
; 函数序言
push ebp
mov ebp, esp

; 计算数组元素地址并赋值
mov eax, [ebp+8] ; 获取数组首地址
mov ecx, [ebp+0Ch] ; 获取索引
mov edx, [ebp+10h] ; 获取值
mov [eax+ecx*4], edx ; arr[index] = value

; 函数尾声
pop ebp
retn

4. 函数调用对照

a. 函数调用

C代码:

1
2
3
4
5
6
7
8
int add(int a, int b) {
return a + b;
}

int main() {
int result = add(10, 20);
return result;
}

对应的汇编代码:

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
; add函数
add:
push ebp
mov ebp, esp
mov eax, [ebp+8] ; 获取参数a
add eax, [ebp+0Ch] ; 加上参数b
pop ebp
retn

; main函数
main:
push ebp
mov ebp, esp
sub esp, 4 ; 分配局部变量空间

; 调用add函数
push 20 ; 第二个参数
push 10 ; 第一个参数
call add ; 调用add函数
add esp, 8 ; 清理栈参数

mov [ebp-4], eax ; 保存返回值到result
mov eax, [ebp-4] ; 返回result
mov esp, ebp
pop ebp
retn

5. 指针操作对照

a. 指针解引用

C代码:

1
2
3
int getValue(int *ptr) {
return *ptr;
}

对应的汇编代码:

1
2
3
4
5
6
7
8
9
10
11
; 函数序言
push ebp
mov ebp, esp

; 指针解引用
mov eax, [ebp+8] ; 获取指针值
mov eax, [eax] ; 解引用指针,获取指向的值

; 函数尾声
pop ebp
retn

b. 指针赋值

C代码:

1
2
3
void setValue(int *ptr, int value) {
*ptr = value;
}

对应的汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
; 函数序言
push ebp
mov ebp, esp

; 指针赋值
mov eax, [ebp+8] ; 获取指针值
mov edx, [ebp+0Ch] ; 获取要赋的值
mov [eax], edx ; 将值赋给指针指向的地址

; 函数尾声
pop ebp
retn

V. 结语

本篇核心知识点总结:

  • 汇编指令是机器指令的助记符,与机器指令是一一对应的
  • AT&T的汇编语法格式和Intel汇编语法格式的是不同的
  • 常用寄存器:EAX 、EBX、ECX、EDX、EDI、ESI、EBP、ESP
  • 存取速度从高到低分别是: 寄存器 > 1级缓存 > 2级缓存 > 3级缓存 > 内存 > 硬盘
  • 常用的汇编指令:mov、je、jmp、call、add、sub、inc、dec、and、or

AI辅助汇编分析设想:
随着现在AI大模型的不断发展和各类出圈表现,在二进制逆向工程中的也能够扮演重要的辅助角色,能够提升反编译代码的可读性与分析效率,未来随着多模态模型(结合代码、控制流图、动态执行轨迹)的发展,这一领域有望实现更高效的自动化重构。

近期发的一些文章也收到了一些想看的case,还在努力整理中
还有个别问我一些面试题的。后面如果多了,也可以汇总一下发出来(当然我说的不一定对哈哈哈)

持续输出技术分享,您的支持将鼓励我继续创作!