📚 网络安全 Kali 学习系列
模块 0 基础阶段 ✅ 已完成
01-18 Linux → 网络 → 密码学 → 编程 → Docker
模块 1 信息收集 ✅ 已完成 | 模块 2 Web渗透 ✅ 已完成
19-31 扫描 → 漏洞 → sqlmap → XSS → WAF绕过
模块 3 代码审计 ✅ 已完成
32-37 审计入门 → Node.js → PHP → Java → Python → CVE复现
模块 4 漏洞利用与后渗透 ✅ 已完成
38-43 Metasploit → Payload → 提权 → 维持 → 免杀
模块 4-2 逆向工程基础(进行中)
44 汇编基础与文件格式(当前篇)
45 静态分析工具 | 46 动态调试
x86/x64 汇编基础与 PE/ELF 文件格式
网络安全 Kali 学习系列 · 第 44 篇 · 模块 4-2 逆向工程基础

读完本篇你将能:理解 x86/x64 汇编的基本语法和寄存器用途,阅读简单的反汇编输出并还原程序逻辑,掌握 PE 和 ELF 文件格式的核心结构(节区、导入表、导出表、入口点),为后续使用 Ghidra、radare2 和 GDB 进行静态分析和动态调试打下基础。

模块 4 解决了"怎么利用漏洞"的问题,模块 4-2 解决"怎么理解编译后的程序"。当你拿到一个没有源码的恶意样本或闭源软件,汇编语言是唯一的对话方式。逆向工程不是把 0 和 1 翻译回 C 代码——那是编译器做的事。逆向是理解程序"在做什么"和"怎么做"的过程,汇编是中间语言。

目录

第一章 x86 汇编基础:寄存器与指令
第二章 栈帧与函数调用约定
第三章 x64 扩展:更多寄存器与调用约定
第四章 PE 文件格式:Windows 可执行文件结构
第五章 ELF 文件格式:Linux 可执行文件结构
第六章 分级练习

第一章 x86 汇编基础:寄存器与指令

x86 汇编是 Intel 32 位指令集的汇编语言。虽然现代系统多为 64 位(x64),但恶意软件、shellcode 和漏洞利用中 x86 仍然大量出现——因为 32 位代码更短、兼容性更好。理解 x86 是学习 x64 的前提。

1.1 通用寄存器

x86 有 8 个通用寄存器,每个 32 位。它们各有"历史角色"——虽然现代指令集允许任意寄存器做通用运算,但函数调用约定仍然依赖这些角色分工。

寄存器 全称 传统角色 16位 8位低
EAX Accumulator 返回值 / 乘除运算 AX AL
EBX Base 内存基址指针 BX BL
ECX Counter 循环计数器 CX CL
EDX Data I/O 端口 / 乘除高位 DX DL
ESI Source Index 字符串操作源指针 SI SIL
EDI Destination Index 字符串操作目标指针 DI DIL
EBP Base Pointer 栈帧基址指针 BP BPL
ESP Stack Pointer 栈顶指针 SP SPL

E 是 Extended 的缩写——16 位时代寄存器叫 AX/BX/CX,32 位扩展后在前面加 E。64 位时前面改用 R(如 RAX/RBX),但 32 位的 EAX 仍然可以使用。

1.2 核心指令

ASM · 基本指令示例 basic.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
; 数据传送
mov eax, 42 ; eax = 42
mov ebx, eax ; ebx = eax 的值
 
; 算术运算
add eax, 10 ; eax += 10
sub eax, 5 ; eax -= 5
xor eax, eax ; eax = 0(清零,比 mov 快)
 
; 栈操作
push eax ; esp -= 4, [esp] = eax
pop ebx ; ebx = [esp], esp += 4
 
; 控制流
cmp eax, 0 ; 比较 eax 和 0
je label_equal ; 如果相等则跳转
💡 小贴士
逆向分析中 xor eax, eax 是最常见的清零写法——因为 XOR 自身结果必为 0,且比 mov eax, 0 短一个字节(机器码 31 C0 vs B8 00 00 00 00)。在 shellcode 中,节省的每个字节都至关重要。

第二章 栈帧与函数调用约定

函数调用是逆向分析的核心——程序逻辑由一个个函数调用组成,每个函数有自己的栈帧。理解栈帧是理解函数参数、局部变量和返回地址的基础。

2.1 栈帧结构

x86 栈从高地址向低地址增长(push 时 ESP 减小)。一次函数调用时,栈帧布局如下:

ASM · 栈帧布局 stack_frame.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
; 高地址
[参数2] ; ebp+12
[参数1] ; ebp+8
[返回地址] ; ebp+4(call 指令压入)
[保存的ebp] ; ebp(push ebp 后的位置)
[局部变量1] ; ebp-4
[局部变量2] ; ebp-8
[...] ; esp 指向栈顶
 
; 函数序言(prologue)
push ebp ; 保存调用者的 ebp
mov ebp, esp ; 设置当前栈帧基址
sub esp, 0x20 ; 分配局部变量空间

逆向时看到 push ebp; mov ebp, esp 这两条指令就知道函数开始了——这是函数序言的标准模式。相对地,mov esp, ebp; pop ebp; ret 是函数结语。参数在 EBP+8 以上,局部变量在 EBP-4 以下。

2.2 cdecl 调用约定

x86 最常见的调用约定是 cdecl(C declaration),规则如下:

规则 cdecl stdcall fastcall
参数传递 栈(右到左) 栈(右到左) 寄存器+栈
清理方 调用者 被调用者 被调用者
返回值 EAX EAX EAX
可变参数 支持 不支持 不支持

Windows API 使用 stdcall(函数名带 WINAPI/PASCAL 声明),C/C++ 默认使用 cdecl。逆向时通过 ret N(stdcall)还是 ret(cdecl)可以区分调用约定。

第三章 x64 扩展:更多寄存器与调用约定

x64 在 x86 基础上扩展了寄存器——从 8 个增加到 16 个,从 32 位扩展到 64 位。前缀从 E 改为 R(如 RAX/RBP/RSP),新增 R8-R15。

3.1 x64 调用约定的关键变化

x64 最重要的变化是参数通过寄存器传递而非栈——前 4 个(Linux)/6 个(Windows)参数直接放入寄存器,溢出的才入栈。这直接影响逆向分析时的参数定位方式。

参数序号 Linux x64 (System V) Windows x64 (Microsoft)
第1参数 RDI RCX
第2参数 RSI RDX
第3参数 RDX R8
第4参数 RCX R9
第5参数 R8 栈
第6参数 R9 栈
💡 小贴士
逆向 x64 ELF 文件时,看到 call printf 前有 mov rdi, format_str 和 mov rsi, value——说明 printf 的第一个参数(格式串)在 RDI,第二个参数(值)在 RSI。x86 下两个参数都在栈上,需要 push value; push format_str; call printf。

第四章 PE 文件格式:Windows 可执行文件结构

PE(Portable Executable)是 Windows 上 .exe/.dll/.sys 文件的格式。理解 PE 结构是分析 Windows 恶意软件的基础——第 43 篇提到的免杀技术大量利用 PE 结构操作。

4.1 PE 文件总体结构

Text · PE 文件结构 pe_layout.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
DOS Header (64 bytes)
└─ e_lfanew → 指向 PE Header 偏移
DOS Stub (可变)
PE Signature "PE\0\0" (4 bytes)
COFF File Header (20 bytes)
└─ Machine, NumberOfSections, TimeDateStamp
Optional Header (96+ bytes)
└─ AddressOfEntryPoint ← 入口点
└─ ImageBase, SectionAlignment, DataDirectories
Section Table (每个 40 bytes)
.text .data .rdata .rsrc .reloc
Section Data (实际代码和数据)
└─ .text 节包含可执行代码

4.2 关键字段:入口点与导入表

Shell · PE 分析命令 pe_analyze.sh
1
2
3
4
5
6
7
8
9
10
11
12
# 查看入口点地址
objdump -f sample.exe | grep -i start
 
# 查看节区
objdump -h sample.exe
 
# 查看导入表(调用了哪些 API)
objdump -p sample.exe | grep -A5 "DLL Name"
 
# radare2 快速查看
r2 -qc 'iI' sample.exe
r2 -qc 'ii' sample.exe # 导入表

入口点(AddressOfEntryPoint)是程序执行的第一条指令地址——逆向时从这里开始分析。导入表(Import Table)列出了程序依赖的 DLL 和调用的 API 函数——看到 VirtualAllocEx 和 WriteProcessMemory 就知道可能有进程注入行为。

第五章 ELF 文件格式:Linux 可执行文件结构

ELF(Executable and Linkable Format)是 Linux 上的可执行文件格式,结构与 PE 类似但细节不同。Kali 上的安全工具多为 ELF 格式。

5.1 ELF 结构与 PE 对比

概念 PE (Windows) ELF (Linux)
文件头 DOS Header + PE Header ELF Header (64 bytes)
节区表 Section Table Section Header Table
入口点字段 AddressOfEntryPoint e_entry
导入表 Import Directory .dynamic + .got.plt
代码节区 .text .text

5.2 readelf 命令

Shell · ELF 分析 elf_analyze.sh
1
2
3
4
5
6
7
8
9
10
11
# 查看 ELF 头信息
readelf -h /bin/ls
 
# 查看节区表
readelf -S /bin/ls
 
# 查看动态链接库依赖
readelf -d /bin/ls | grep NEEDED
 
# ldd 查看动态库
ldd /bin/ls
💡 小贴士
逆向分析时,ELF 的 .got.plt 节区特别重要——它存储动态链接函数的地址。恶意软件经常修改 GOT 表实现函数劫持(GOT overwrite),用自定义函数替换 libc 函数实现持久化或数据窃取。

第六章 分级练习

🟢 基础验证

任务:在 Kali 上用 objdump -d /bin/ls | head -50 查看 ls 命令的反汇编前 50 行。找到函数序言(push ebp; mov ebp, esp),记录入口点地址。用 readelf -h /bin/ls | grep Entry 验证。

参考解法:readelf -h 显示的 Entry point address 就是 e_entry 字段。objdump -d 的输出中,地址与 e_entry 匹配的指令是程序入口。push rbp; mov rbp, rsp(x64)或 push ebp; mov ebp, esp(x86)是函数序言。

🟡 组合应用

任务:用 C 语言编写一个简单的 hello.c(调用 printf 打印 "Hello"),分别用 gcc 编译为 x86 和 x64 版本(gcc -m32 -o hello32 hello.c 和 gcc -o hello64 hello.c)。用 objdump 反汇编 main 函数,对比两个版本在调用 printf 时参数传递方式的差异(x86 用 push 入栈,x64 用 mov 到 rdi/rsi)。

提示:x86 版本会看到 push OFFSET format 然后 call printf。x64 版本会看到 lea rdi, [rip+format] 然后 call printf。注意 x64 使用 RIP 相对寻址。

🔴 开放挑战

任务:找到一个简单的 ELF 或 PE 二进制文件(如 /bin/true 或 Windows notepad.exe),用 objdump 或 readelf 完成以下分析:(1) 确定文件格式(32/64位、链接方式);(2) 列出所有节区和各自权限(可执行/可写/可读);(3) 找到入口点地址,反汇编入口点前 20 条指令;(4) 列出导入的动态库和调用的函数。写一份简短的分析报告。

参考方向:节区权限通过 readelf -S 的 FLAG 列查看(X=可执行,W=可写,A=分配)。注意 .text 节区应为 X(可执行),.data 应为 W(可写),.rodata 应为只读。异常权限(如 .text 同时可写可执行)是恶意软件的特征之一。

知识回顾

通用寄存器 MOV/ADD/SUB/XOR 栈帧结构 函数序言/结语 cdecl/stdcall/fastcall x64 寄存器传参 PE 文件格式 ELF 文件格式 入口点 导入表 节区权限 readelf/objdump

下一篇预告

本篇建立了汇编语言和文件格式的基础认知——寄存器、指令、栈帧、PE/ELF 结构。下一篇将把这些知识应用到实际工具中:静态分析工具 Ghidra 与 radare2。Ghidra 是 NSA 开源的逆向工程框架,radare2 是命令行逆向利器。我们将用它们反编译一个简单的 C 程序,从汇编视图到伪 C 代码,体验从"看机器码"到"读代码"的转换过程。

关注本公众号,持续获取网络安全 Kali 学习系列更新。如果觉得有帮助,欢迎点赞、在看、分享给同样在学习网络安全的朋友。