BUUCTF [HarekazeCTF2019]baby_rop wp
1.使用checksec命令查看文件的保护机制开启情况:
显示除了NX保护均未开启或完全启用 ,而NX开启说明数据区域(如栈、堆)不可执行,是防止将 shellcode 写入数据区后跳转执行,所以可优先考虑不利用shellcode注入且简单直接的方式寻找漏洞。
2.利用IDA静态分析:
main函数中明显存在system函数的调用,并提示了输入v4的栈溢出风险,那么便可以尝试利用栈溢出漏洞配合ROP链的构造,直接调用system函数并传入命令进行解题。
对于system函数:
我们可以使用main函数中call _system的调用地址,也可以直接使用system函数本身的地址:
对于/bin/sh命令:
通过Shift + F12的先行搜索可以发现程序内含 /bin/sh 这一关键字符串的详细信息,双击跳转后可获得其地址:0x601048。
现在已经获取到了system函数和/bin/sh命令的地址,那么接下来还需要pop rdi指令将/bin/sh命令作为system函数的第一个参数弹入对应寄存器,以及ret指令的地址实现64位架构的栈对齐(经验证,此题可不需要特意栈对齐,但保险期间,后续payload构造还是选择进行栈对齐):
使用ROPgadget相关指令打印地址:
1.
pop rdi; ret
指令的作用:
pop rdi; ret
是一个在 64位系统ROP(Return-Oriented Programming)攻击 中至关重要的指令片段(gadget)。它的作用分为两步:
pop rdi
:从当前栈顶弹出一个8字节的值(即攻击者预先放置在栈上的数据,如/bin/sh
字符串的地址),并将其存入 RDI 寄存器。在64位Linux的System V AMD64 ABI调用约定中,RDI用于传递函数的第一个参数(例如system("/bin/sh")
中的"/bin/sh"
地址)。
ret
:紧接着从栈顶弹出下一个值(如system
函数的地址)到RIP寄存器,使程序跳转到目标函数执行。由于RDI已正确设置参数,函数(如system
)便能正常调用。底层原理:在64位系统中,参数通过寄存器传递(而非32位的栈传递),但栈溢出只能控制栈数据,无法直接控制寄存器。
pop rdi; ret
作为"桥梁",通过控制栈布局间接设置寄存器值,是Ret2libc攻击的核心。
2.为什么有的题目对于使用ret地址实现64位架构栈对齐这个步骤的要求可有可无?
核心原因在于并非所有函数都对栈指针的对齐状态敏感。下面这个表格梳理了关键点:
情况
栈对齐要求
原因
必须对齐
严格需要16字节对齐
函数内部使用了SSE等指令(如
movaps
),这些指令对内存操作有严格的对齐要求,不对齐会直接导致段错误。可有可无
不强制要求16字节对齐
函数内部逻辑不涉及需要严格对齐的指令或操作,栈指针末位是否为
0
不影响其正常执行。这种差异主要源于以下几点:
函数实现的多样性
不同的函数,甚至同一函数在不同环境下的编译实现可能不同。有些会使用需要严格对齐的SSE指令进行优化,有些则不会。如果
system
函数或其内部调用的函数(如strcpy
)没有使用这类指令,那么即使栈未对齐也能正常工作。调用瞬间的栈指针状态
根据System V AMD64 ABI规范,在
call
指令执行前,栈指针(RSP)必须16字节对齐。call
指令会将返回地址压栈(使RSP减8),因此函数入口处的RSP通常处于16字节对齐+8的状态(即末位是8
)。一个ret
指令的本质是pop rip
,它会让RSP再加8,从而使栈指针恢复到16字节对齐的状态(末位变为0
)。如果你的溢出恰好覆盖到了上一个栈帧的返回地址,使得在进入你的ROP链之前已经执行过一些操作,栈指针可能已经处于对齐状态,这时额外的ret
就不是必须的。Libc版本与环境差异
不同版本或发行版的libc库中,函数的实现可能有细微差别。可能在你本地测试不需要对齐的
system
函数,在远程服务器上却因为libc版本不同而崩溃。简单来说,栈对齐要求“可有可无”的现象,反映了底层系统规范与具体函数实现之间的差异。作为解题者,最可靠的方法是养成“防御性对齐”的习惯,即在构造64位ROP链时,默认加入一个
ret
gadget来确保栈指针对齐,这样可以避免许多不必要的麻烦。
3.编写python脚本:
from pwn import *
r = remote('node5.buuoj.cn', 25390)offset = 16 + 8
pop_rdi = 0x0000000000400683
ret_addr = 0x0000000000400479
system_addr = 0x4005E3
binsh_addr = 0x601048payload = b'a' * offset + p64(pop_rdi) + p64(binsh_addr) + p64(ret_addr) + p64(system_addr)
r.sendline(payload)
r.interactive()
4.获取flag:
我们利用的是 "/bin/sh"命令 ,仅让我们获取了控制权,所以我们需要搭配 ls(显示子目录)和 cat(读取)命令获取flag:
但使用 ls 命令后发现,此题的flag并不直接位于bin目录下,那么便需要查找其路径:
find -name flag
获得flag的路径后,便可使用cat命令读取: