跳到主要内容
  1. Posts/

一次 CTF 复盘:ret2libc / ROP 的通用思路(不靠背脚本)

·1 分钟·

说明:本文用于 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。