一次 CTF 复盘:ret2libc / ROP 的通用思路(不靠背脚本)
目录
说明:本文用于 CTF / 本地靶场学习。讲清思路路线,不给可直接套用的攻击脚本。
很多人学 ret2libc 的痛点是:看别人题解像魔法——为什么他知道要泄露?为什么能算 libc 基址?为什么一条链就能调用 system?
其实 ret2libc/ROP 的核心是一条稳定的推理链:
1)我能控制什么?(输入能覆盖到哪)
2)我需要什么信息?(ASLR/PIE 下地址不固定)
3)我如何拿到信息?(泄露)
4)我如何把信息变成地址?(基址 + 偏移)
5)我如何让程序执行我想要的调用?(ROP:把参数摆好,跳到函数)
换题不换本质。
一、先做“利用可行性”判断:checksec 一眼看方向 #
典型 ret2libc 场景常见组合:
- NX 开:不能直接跑 shellcode,倾向 ROP / ret2libc
- Canary 关:栈溢出更直接
- PIE 关:程序本体地址固定,找 gadget 更容易
- ASLR 开:libc 地址随机,需要泄露
即使 Canary/PIE 开也不代表不能做,只是信息需求更高、链条更长。
二、第一步:确认“控制点”——我到底能覆盖到哪里? #
你要确认的不仅是“能不能溢出”,而是:
- 是否能覆盖返回地址(控制 rip)
- 覆盖偏移是多少(offset)
- 输入是否会被截断(read/fgets 长度限制等)
这个阶段最关键的产物:offset。
学习建议:用 gdb 观察崩溃现场(rip、rsp 附近栈内容、输入在内存的位置),建立稳定复现。
三、第二步:信息问题——ASLR 下你为什么必须“泄露”? #
当 ASLR 开启时:
- libc 的加载基址每次不同
- system/puts/printf 等函数地址都跟着变
- 你无法稳定硬编码调用地址
因此必须先做信息泄露:拿到某个运行时地址,再反推出 libc 基址。
四、第三步:泄露什么最划算?(选择泄露目标) #
最常见、也最通用的泄露方式:
- 泄露 GOT 表项中的真实函数地址(例如 puts@GOT 在运行时被解析成 libc puts 的实际地址)
- 利用程序已有输出函数把它打印出来(puts/printf/write 等)
你要记住的关键点:
- GOT 里存的是“已解析函数指针”
- 泄露一个 libc 函数地址,就能通过偏移反推出 libc 基址
五、第四步:把泄露变成“可用地址”(基址 + 偏移) #
一旦拿到某个 libc 函数的运行时地址(例如 puts 的真实地址),你要做的是纯数学:
- libc_base = puts_runtime - puts_offset
- system_addr = libc_base + system_offset
- binsh_addr = libc_base + binsh_offset(libc 里常见的 “/bin/sh” 字符串)
这一步没有运气成分,只有“libc 版本要对齐”。
六、第五步:ROP 链到底在干嘛? #
以 x86_64 调用 system("/bin/sh") 为例,最低需求是:
- rdi 指向 “/bin/sh” 的地址
- rip 跳到 system 的地址
所以经典链的语义是:
1)把参数放进 rdi
2)必要时做栈对齐
3)跳到 system 执行
你不必背 gadget 列表,关键是理解:ROP 链就是手工满足调用约定。
七、两段式利用:为什么经常要“先泄露再打最终链”? #
因为一开始不知道 libc 基址,所以常见流程是两次交互:
第一段(leak stage)
- 构造一段调用,把某个 libc 地址打印出来
- 再把控制流带回可重复输入点(例如 main 或菜单循环)
第二段(pwn stage)
- 有了 libc_base,就能计算 system/binsh
- 再发最终链完成调用
理解两段式,你就不会觉得题解“凭空出现”。
八、稳定性:为什么有时加一个 ret 就好了? #
一些环境要求调用前栈满足 16 字节对齐。进入 libc 函数时如果对齐不满足,可能在某些指令处崩溃。
所以题解里常见“额外塞一个 ret”,本质是对齐垫片,不是玄学。
九、把思路固化成你的检查清单 #
以后做 ret2libc/ROP,你可以按这张清单走:
- checksec:NX/PIE/Canary/RELRO
- 控制点:能否控 rip?offset?
- 泄露点:是否能打印 GOT?是否有输出函数可用?
- libc 对齐:题目给 libc 吗?本地是否一致?
- 计算:libc_base、system、"/bin/sh"
- 链语义:参数 → 对齐 → 调用
- 两段式:leak → 回到交互点 → pwn
清单比脚本更值钱。
十、写题解时建议你写什么(以后你的博客会很强) #
建议至少写清楚:
1)漏洞点(为什么能控制)
2)泄露点(为什么选它)
3)关键计算(基址与偏移)
4)链的语义(每一步在做什么)
5)稳定性处理(对齐、回到 main、重复输入)
当你能用自己的话写出来,你就真正掌握了 ret2libc/ROP。