栈迁移

发布于 2024-04-10  117 次阅读


参考博客:http://t.csdnimg.cn/Yw1NZ

1.简单介绍:

为什么会有栈迁移这种东西呢?

首先我们在栈溢出攻击的时候,有一个条件就是在栈上有充足的空间,能让我们进行布局。一般来说栈的剩余空间是足够我们存放指令的,但是有个别情况只能容纳一个ret或者ebp,那么这里面没有空间我们就去其他的地方去找空间。这就是栈溢出的思想。

2.实现的原理:

首先们得了解程序调用函数的时候进行了什么操作:

在这里,当上层函数嗲用foo这个函数的时候,也就是我们的eip执行到call foo的指令的时候,call指令以及foo函数开头的指令做了如下的事情来保证前面已经执行了的过程:

  1. 记住foo函数执行过后应该在哪里继续执行:将当前的eip下面的位置放到栈里面,也就是ret。
  2. 记住上层函数的栈底位置:保存当前的ebp的内容到栈里面,也就是old ebp。
  3. 记住foo函数开始的位置:保存当前栈顶的内容到 ebp,便于foo函数栈内的寻址。

这里的三个步骤就是上面图里面的1,2步。当call roo执行完之后,栈里面就是这个样子:

当foo函数执行完之后,eip就会执行leave和ret两条指令来恢复现场,这个时候栈里面就是上面的右图。

由此可见,leave和ret指令相当于完成了下面的这些事情:

  • 清空当前函数的栈来还原空间(移动esp到当前的ebp)
  • 还原栈底(将这个时候的esp所指的上层函数的栈底old ebp摊入ebp寄存器里面)
  • 还原执行流(将这个时候的esp所指的上层函数调用foo是的地址弹入eip寄存器里面)

也就是这三步是上三步的逆过程,再恢复现场的时候,栈顶指针的位置是由ebp给控制的,而 ebp 寄存器的内容则可由栈中数据控制( pop ebp)。

所以我们就可以来思考一下,一旦攻击者能篡改栈上的old ebp的内容,那么就能篡改ebp的内容,然后就有可能改esp的内容,进而影响eip。

但是leave所代表的子指令是有先后顺序的,也就是无法先执行pop ebp,然后执行mov esp,ebp,因此直觉上是无法先影响ebp再影响esp的。

但是如果把栈上的ret部分覆盖为另外一组的leave ret的地址,也就是程序退出的时候会执行两次leave的指令,一次ret指令。当pop ebp第一次被执行的时候,eip将指令指向另外一条mov esp,ebp指令的地址然而这个时候ebp的值已经变成了第一次的pop ebp的值,这样esp就会被“骗”到另外一个空间,这个栈空间也就完成了迁移。

然后我们就知道栈迁移的核心思想,下面就是我们的执行步骤:

Step1:

首先确定缓冲区溢出时,至少能覆盖栈上ebp和ret这两个部分,然后就选取栈要被劫持到的地址;如若能在bss等上面执行shellcode,就可将栈迁移到shellcode开始处,把这个地址记为 HijackAddr:

Step2:

寻找程序里面一段leave ret gadget的地址,记这个地址为 LeaveRetAddr

Step3:

设置缓冲区变量,使其将栈上 ebp 覆盖为 HijackAddr-4,将 ret 覆盖为LeaveRetAddr

Step4 :

程序执行至函数结束时,将依次发生如下事件:

  1. 执行指令:mov esp,ebp,还原栈顶指针至当前函数栈底;这个时候esp指向栈上被篡改的ebp数据,也就是 HijackAddr-4;
  2. 执行指令:pop ebp,将篡改的HijackAddr-4放入 ebp 寄存器内;此时 esp 上移,指向栈上被篡改的 ret 数据:(图片见下)
  3. 执行指令:pop eip,将LeaveRetAddr放入eip寄存器内,篡改执行流,以执行第二遍leave指令;
  4. 执行指令(第二遍的leave指令):mov esp, ebp,将HijackAddr-4移入 esp 寄存器内,即栈顶指针被劫持指向了 HijackAddr-4,发生了栈的迁移;(图片见下)
  5. 执行指令(第二遍的leave指令):pop ebp,无实际效用,ebp寄存器仍为HijackAddr-4,但此时esp 被拉高4个字节,指向HijackAddr;
  6. 执行指令: pop eip,将 HijackAddr移入 eip 内,成功篡改执行流至shellcode区域;

下面是第二步,第四步的图片

Step5:

攻击结束

大致的流程就是这个样子的了


The world's full of lonely people afraid to make the first move.