利用内存破坏漏洞(Exploiting Memory Corruption)
逆向工程告诉你某个二进制程序存在缺陷,而漏洞利用是一门独立的技艺:把这个缺陷转化为受控的原语(primitive),再在目标实际启用的缓解机制之下转化为代码执行。整个过程是一连串的转换——缺陷到原语、原语到信息泄露、泄露到控制——每一环都由二进制程序的保护措施决定,而非照搬套路。
何时使用
- 你在 ELF/PE 中发现崩溃或已知类别的缺陷,需要评估可利用性并构建可用的利用程序。
- 构造 ROP/JOP 链、ret2libc 或堆布局整理(heap grooming)序列。
- 在实验环境或 CTF 目标上绕过特定的缓解机制组合(ASLR、NX/DEP、canary、PIE、RELRO)。
- 执行经授权的二进制漏洞利用任务或 CTF pwn 题目。
何时不使用
- 还原二进制程序行为(反汇编、反编译、算法恢复)属于 analyzing-binaries;如果缺陷尚未理解,应先做这一步。
- 在能看到代码之前先对受保护二进制程序进行脱壳或反虚拟化——使用 unpacking-protected-binaries。
- Web、反序列化或注入类缺陷——那些属于各自的技能(testing-web-applications、exploiting-deserialization);本技能只针对原生内存破坏。
- 内核/驱动利用——其原语差异较大(无 libc、SMEP/SMAP/KASLR、系统调用上下文),超出本技能范围;用户态方法仍对其有参考意义。
- 通过 fuzzing 发现缺陷——那是发现阶段;本技能从已有崩溃开始。
先枚举缓解机制
你能构建的利用程序由目标启用了什么决定。规划前先读取这些信息(checksec,或 pwntools 的 checksec/ELF):
- NX / DEP:阻止在栈/堆上执行 shellcode;通常的应对是 ROP / ret2libc——复用已有的可执行代码。
- ASLR:阻止硬编码地址;通常的应对是通过信息泄露恢复基址,再计算偏移。
- 栈 canary:阻止朴素的栈返回地址覆盖;通常的应对是泄露 canary、改写其他位置,或对 fork 型服务暴力破解。
- PIE:阻止硬编码二进制程序自身地址;通常的应对是泄露一个二进制地址;在此之前只能做相对/部分覆盖。
- Partial RELRO:限制很少——GOT 仍可写;可以考虑 GOT 覆盖。
- Full RELRO:阻止 GOT 覆盖;转向 __malloc_hook/__free_hook(2.34 之前),或仅走 ROP 路径。
决定工作顺序的两个问题:我是否有信息泄露(应对 ASLR/PIE),以及我能写到哪里(应对 RELRO)。其余都由此推导。
从缺陷类别到原语
在选技术之前先明确你手里是什么:
- 栈缓冲区溢出 → 直接控制保存的返回地址;在 canary 允许的情况下,这是通往 ROP 链最干净的路径。
- 堆溢出 / off-by-one → 破坏相邻 chunk 的元数据或相邻对象的指针;原语取决于你能整理出的堆布局。
- 释放后使用(use-after-free)→ 用攻击者数据重新占用已释放的槽位,使过期指针在你的控制下被使用;是构造伪造 vtable/对象的经典路径。
- 类型混淆 → 把一种类型的字段当作另一种类型来使用,通常直接得到读/写或受控调用。
- 格式化字符串 → %n 用于定向写入,%p/%s 用于泄露;一个缺陷常常同时提供泄露和写入。
- 整数溢出 / 越界(OOB)→ 通常只是达到上述某一类的手段(例如计算出的过小尺寸导致堆溢出)。
这一步的目标是三种原语之一:控制流劫持、任意读、任意写。先拿到原语,再开始构建。
ROP 与代码复用
在 NX 开启时,你按自己选择的顺序执行程序自身的字节:
- 用 ROPgadget 或 ropper 查找 gadget;寻找 pop rdi; ret 这类设置调用参数的寄存器控制 gadget。
- ret2libc——在拿到 libc 基址后返回到 system("/bin/sh");当 libc 可用且已泄露时这是最直接的链。
- 当没有干净的 gadget 时,用 ret2csu 控制 rdx/rsi,借助 2.34 之前二进制程序中 __libc_csu_init 的尾部。
- 当 gadget 稀少但能布置伪造信号帧并到达 sigreturn 时,使用 SROP(sigreturn-oriented)。
- ret2dlresolve 通过迫使动态链接器解析某个函数来调用它,在没有泄露且为 partial RELRO 时很有用。
- 用 one_gadget 查找一个能执行 shell 的单一 libc 地址,在其约束满足时压缩最后阶段。
- 用 libc-database 把泄露的 libc 匹配到具体构建版本,以保证偏移正确——针对错误 libc 构建的链会静默失败。
堆利用(glibc ptmalloc)
堆工作是布局工作:你安排分配,使破坏落在有用的位置上。技术取决于 glibc 版本,因为分配器的检查一直在收紧。
- tcache poisoning(2.26+)——现代最高杠杆的原语:破坏已释放 tcache chunk 的 next,使下一次 malloc 返回任意指针。在 2.32+ 上该指针受 safe-linking 保护(与其存储地址 XOR,再右移 12),因此需要堆泄露来伪造;2.34 移除了以往用于终结利用链的 __free_hook/__malloc_hook 目标。
- fastbin dup——对 fastbin 双重释放以两次返回同一 chunk;注意现代 glibc 上 tcache 优先的行为。
- unsorted-bin 泄露——unsorted bin 中已释放的 chunk 持有 main_arena 指针;读取它可泄露 libc 地址以绕过 ASLR。
- House of * 系列技术(Force、Orange、Einherjar 及更新的)——当简单原语被阻断时,各自滥用特定的分配器不变量实现任意写;仅在布局需要时使用。
- 在 pwndbg 或 GEF 中完成上述所有操作——heap、bins 和 tcache 命令会显示实际状态,让你针对现实而非脑内模型来整理布局。
循环
用 pwntools 增量构建利用程序,在添加下一步之前验证每一次转换:到达崩溃、证明对指令指针的控制、落地泄露并确认计算出的基址,然后是写入,最后是 shell。当某个阶段行为异常时,就在那个精确位置用 gdb.attach() 附加,而不是盯着整条链看。本地可用而远程失败的利用程序,几乎总是环境差异——libc 版本、栈对齐(为 movaps 补一个 ret)、或依赖环境的泄露。
范围与授权
漏洞利用开发只针对你拥有的二进制程序、CTF 目标、实验虚拟机,或你被明确委托测试的软件——此处默认 egress 模式为 lab_only,因为对生产环境运行可用的利用程序属于入侵而非研究。两个具体边界:
- 第三方软件中的 0-day 使你处于协同披露流程,而非公开发布。构建概念验证,通过厂商渠道报告,不要在修复发布前公开投放武器化利用程序。
- 绝不要针对你不控制的系统测试利用程序来“看看是否有效”。内存破坏利用程序在失败时会令服务崩溃;那是对他人资产的可用性影响。
应拒绝的合理化借口
- “它在 EIP/RIP 上崩溃了,所以可利用。”控制指令指针只是起点而非终点。现代缓解机制意味着没有泄露时崩溃可能是死路;在宣称可利用之前,先评估原语的可达性。
- “本地能跑,直接发布。”远程 libc、ASLR 熵和栈对齐都不同。在报告为可用之前,匹配目标 libc 并针对真实环境测试。
- “把 shellcode 喷洒到栈上就行。”NX 成为默认已超过十年。如果你在往栈上写 shellcode 并跳过去,先确认 NX 确实是关闭的。
- “one_gadget 会处理最后一步。”它的约束(特定寄存器为 null、特定栈状态)常常不成立。检查它们;保留 ret2libc 作为后备。
- “writeup 里的堆技术直接就能用。”分配器检查因版本而异——tcache key、计数检查、safe-linking。确认目标 glibc 版本以及该技术仍然适用。
ATT&CK 覆盖
由 secskills-core/ttp-index.json 生成——编辑该文件,然后运行 python3 scripts/sync_attack.py --write。在报告中引用这些 ID 前,请对照当前 ATT&CK 发布版本重新核实。
- 执行(TA0002):T1203 Exploitation for Client Execution——另见 performing-social-engineering、analyzing-malware。
- 权限提升(TA0004):T1068 Exploitation for Privilege Escalation——另见 escalating-linux-privileges、escalating-windows-privileges。
- 横向移动(TA0008):T1210 Exploitation of Remote Services——另见 enumerating-network-services。
- 以上任何一项的检测内容:engineering-detections。主动搜索:hunting-threats。入侵后响应:responding-to-incidents。
参考
- analyzing-binaries——先还原代码并理解缺陷
- unpacking-protected-binaries——当二进制程序必须被脱壳/反虚拟化时
- reviewing-cryptography——当目标是密码学原语而非内存时
- maintaining-engagement-state——egress 模式以及此处默认的 lab-only 设置