写在前面:欢迎进入 Week15 的综合实战训练!在过去的四周里,我们系统学习了堆利用(Week11-12)、IO_FILE 机制(Week13)、信息泄露与 ORW(Week14)等高阶技术。本周我们将把这些技术融合贯通,通过每日两道综合题的实战演练,锤炼出真正在赛场上拿分的能力。今天,作为热身,我们将回顾并深化两种最基础但至关重要的 ROP 技术:ret2win与ret2libc。它们是所有高级利用链的基石,熟练掌握它们,才能在复杂题目中快速构建出稳定的利用框架。
📑 目录
- ROP 技术核心回顾与热身
- ret2win:最直接的返回劫持
- ret2libc:绕过 NX 的经典艺术
- 综合实战思维:从单一技术到组合拳
- 总结与下篇预告
1. ROP 技术核心回顾与热身
返回导向编程(ROP)的核心思想是:在栈溢出的基础上,通过利用程序中已有的、以ret指令结尾的代码片段,来控制程序的执行流程csdn.net。这些代码片段被称为Gadgets。
1.1 为什么需要 ROP?
现代操作系统普遍启用了NX(No-eXecute)保护,使得栈、堆等数据段内存不可执行。传统的注入 shellcode 并跳转执行的方式失效blog.csdn.net+1。ROP 通过复用程序自身或已加载库(如 libc)中的可执行代码,巧妙绕过了这一限制。
1.2 ROP 链构造核心要素
构建一个有效的 ROP 链需要三个核心要素:
| 要素 | 说明 | 获取方式 |
|---|---|---|
| Gadgets | 以ret结尾的指令序列,用于控制寄存器或执行逻辑 | ROPgadget --binary ./pwn |
| 栈布局控制 | 精确计算溢出偏移,控制返回地址及后续参数 | GDB 调试(pattern offset) |
| 地址信息 | 目标函数(如system)及字符串(如/bin/sh)的地址 | 信息泄露(GOT 表)或已知 libc |
栈溢出漏洞
计算溢出偏移
寻找 Gadgets
控制寄存器
获取目标地址
泄露或计算
构造 ROP 链
填充栈布局
触发漏洞
执行 ROP 链
2. ret2win:最直接的返回劫持
ret2win是 ROP 技术中最简单直接的形式。其核心是:程序中存在一个后门函数(如system("/bin/sh")),我们只需通过溢出覆盖返回地址,直接跳转到该后门函数即可csdn.net+1。
2.1 利用条件与流程
- 存在后门函数:程序中存在如
backdoor(),get_shell()等函数。 - 栈溢出漏洞:能够覆盖返回地址。
- 无 Canary 或已绕过:否则溢出会触发检测。
利用流程:
# 伪代码示例 from pwn import * p = process('./pwn') elf = ELF('./pwn') win_addr = elf.symbols['win'] # 后门函数地址 offset = 112 # 溢出偏移量 payload = b'A' * offset + p64(win_addr) p.sendline(payload) p.interactive()2.2 实战要点
- 精确偏移:使用
cyclic或 GDB 的pattern精准计算偏移,避免覆盖栈帧其他重要数据。 - 栈对齐:64 位程序中,函数调用需 16 字节栈对齐。若
system内部使用movaps等指令,不对齐会导致段错误。可在返回地址前加一个retgadget 进行调整。 - 参数传递:若后门函数需要参数(如
system("/bin/sh")),需在 64 位下通过pop rdi; retgadget 设置rdi寄存器指向/bin/sh字符串。
3. ret2libc:绕过 NX 的经典艺术
当程序中没有后门函数,但启用了 NX 保护时,ret2libc是最经典的利用方式blog.csdn.net+1。其核心是:通过泄露 libc 中已执行函数的真实地址,计算出system和/bin/sh的地址,然后构造 ROP 链调用system("/bin/sh")。
3.1 核心原理:PLT 与 GOT 表
- PLT(Procedure Linkage Table):程序链接表,存储跳转指令。第一次调用函数时,通过 PLT 跳转到动态链接器解析函数地址tencent.com+1。
- GOT(Global Offset Table):全局偏移表,存储函数的真实地址。函数第一次被调用后,其真实地址会被写入 GOT 表对应项tencent.com+1。
调用 puts@plt
第一次调用
跳转到 dl_runtime_resolve
解析 puts 真实地址
写入 GOT 表
后续调用
直接从 GOT 表取地址执行
攻击者泄露 GOT 表
获取 puts 真实地址
计算 libc 基址
和 system 地址
3.2 利用流程详解
第一步:泄露 libc 函数地址
利用程序已有的输出函数(如puts,write),打印另一个函数(如puts自身)的 GOT 表地址,从而获取其真实地址csdn.net+1。
# 32位示例:泄露 puts 真实地址 puts_plt = elf.plt['puts'] # puts 的 PLT 地址 puts_got = elf.got['puts'] # puts 的 GOT 地址 main_addr = elf.symbols['main'] # main 函数地址,用于返回 payload = b'A' * offset + p32(puts_plt) + p32(main_addr) + p32(puts_got) p.sendline(payload) puts_addr = u32(p.recv(4)) # 接收并解析 puts 的真实地址第二步:计算 libc 基址与目标地址
利用已知函数的真实地址及其在 libc 中的偏移,计算出 libc 的加载基址,进而算出system和/bin/sh的地址csdn.net+1。
# 假设已知 puts 在 libc 中的偏移 libc_base = puts_addr - libc.symbols['puts'] system_addr = libc_base + libc.symbols['system'] bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))第三步:构造第二次溢出,执行 system(“/bin/sh”)
再次触发栈溢出,跳转到system函数,并将/bin/sh字符串地址作为参数传入csdn.net+1。
# 32位示例 payload2 = b'A' * offset + p32(system_addr) + p32(0xdeadbeef) + p32(bin_sh_addr) p.sendline(payload2)3.3 32位与64位差异对比
| 特性 | 32位 | 64位 |
|---|---|---|
| 参数传递 | 通过栈传递 | 前6个参数通过寄存器传递 |
| Gadget 需求 | 主要控制 EIP | 需pop rdi; ret等控制寄存器 |
| 栈布局 | 函数地址→返回地址→参数 | 函数地址→返回地址→(需通过Gadget设置参数) |
| 常见难点 | 栈平衡 | 栈对齐(16字节) |
64位 ret2libc 关键点:
- 需寻找
pop rdi; retgadget 来设置第一个参数(/bin/sh地址)。 - 注意栈对齐,
system内部可能要求 16 字节对齐。 - 可使用
one_gadget工具寻找满足条件的execve调用,简化利用bilibili.com。
# 64位示例:使用 pop rdi; ret gadget pop_rdi = 0x4011e3 # pop rdi; ret 的地址 bin_sh = 0x402354 # /bin/sh 字符串地址 system = 0x401040 # system 函数的 PLT 地址(若已知) payload = b'A' * offset + p64(pop_rdi) + p64(bin_sh) + p64(system) p.sendline(payload)4. 综合实战思维:从单一技术到组合拳
在真实的 CTF 题目中,利用往往不是单一技术,而是多种技术的组合。以下是几种常见的组合思路:
4.1 ret2libc + 栈迁移
当溢出长度有限,不足以放下完整 ROP 链时,可通过栈迁移技术将栈指针转移到可控区域(如 bss 段),再在该区域布置完整的 ROP 链csdn.net。
4.2 ret2libc + 格式化字符串
结合格式化字符串漏洞进行信息泄露,获取 libc 地址,再利用栈溢出构造 ret2libc 链。这种组合在无输出函数或输出受限的题目中非常有效csdn.net。
4.3 ret2libc + ORW
当题目开启沙箱(Seccomp)禁用execve时,ret2libc 失效。此时需结合 ORW 技术,通过 ROP 链调用open、read、write系统调用读取 flag 文件csdn.net。
4.4 ret2libc + 堆漏洞
利用堆漏洞(如 UAF、Tcache Poisoning)实现任意地址写,劫持__free_hook或_IO_list_all,结合 ret2libc 技术完成控制流劫持csdn.net。
否
是
是
否
是
否
漏洞类型分析
是否有沙箱?
ret2libc: system/bin/sh
ORW: open/read/write
溢出空间足够?
直接构造 ROP 链
栈迁移 + ROP 链
是否有可写内存?
构造 ORW ROP 链
结合堆漏洞
劫持 GOT/IO_FILE
Getshell
Get Flag
5. 总结与下篇预告
5.1 核心知识点总结
- ROP 是基石:ret2win 和 ret2libc 是所有高级利用技术的根基,必须熟练掌握。
- 地址泄露是关键:ret2libc 的核心在于通过 PLT/GOT 表泄露 libc 函数真实地址,进而计算目标地址。
- 版本差异要注意:32位和64位在参数传递、栈布局、Gadget 需求上存在显著差异,需针对性构造 ROP 链。
- 组合思维破局:复杂题目往往需要结合多种技术(如栈迁移、格式化字符串、ORW),单一技术难以奏效。
5.2 下篇预告
在下一篇中,我们将进入漏洞组合实战的第一站:
- fmtstr + canary + ROP:结合格式化字符串泄露 Canary,并绕过栈保护,构造 ROP 链。
- off-by-one + tcache:利用单字节溢出破坏 Tcache 链表结构,实现任意地址分配。
- 将通过 BUUCTF 或 pwn.college 的真题,演示如何从漏洞分析到完整 exp 构造的全过程。
结语:ret2win 和 ret2libc 就像是 PWN 选手的“起手式”,看似简单,却蕴含着控制流劫持的核心思想。当你能在任何题目中快速识别出这两种利用路径,并灵活与其他技术组合时,你就具备了攻克综合题的基础。本周的实战训练,就从熟练掌握这两个基础开始。