栈迁移

一、前言

在只能够控制ebp的情况下,我们怎么才能控制eip去拿到我们的shell呢

以32位程序为例子,在使用call这个指令时,进入函数的时候,会进行一系列栈操作:

1
2
3
push eip+4//保存返回地址
push ebp; //保存前栈帧的栈底指针
mov ebp,esp;

保护现场,避免执行完函数后堆栈不平衡以及找不到之前的入口。

在函数的结尾会进行一系列操作来还原现场leave;ret;

leave就相当于进入函数栈操作的逆过程。

1
2
leave   ==   mov  esp,ebp;pop ebp;
ret == pop eip //弹出栈顶数据给eip寄存器

二、什么是栈迁移

所以栈迁移这种技术,可以通过控制ebp的值,借助leave指令,间接控制esp的值。

核心思想:将栈的esp和ebp转移到一个输入不受长度限制且可控制的地址处,通常是bss段地址!(在栈中也可以)

最后ret的时候,如果我们能控制得了栈顶esp的指向,就相当于控制了程序执行流。

简单一句话就是控制esp指针的指向。

img

举个例子:

第一步:首先确定缓冲区变量溢出时,至少能覆盖栈上ebp和ret两个位置。之后,选取栈要被劫持的到的地址;例如,若能在bss段等内存上执行shellcode。则可以将栈迁移到shellcode开始处。记该地址为ShellAddr
第二步:寻找程序中一段leave ret的gadget地址,记该地址为LeaveAddr。
第三步:设置缓冲区变量,使其将栈上ebp覆盖为ShellAddr-4,将ret覆盖为LeaveAddr。
第四步:当程序执行到函数结束时,依此发生下列事件:

1、执行指令:mov esp,ebp,还原栈顶指针至当前函数栈底;此时esp指向栈上被篡改的ebp数据即ShellAddr-4;

2、执行pop ebp,将此时esp指向的LeaveAddr-4弹出给ebp,并且esp+4,此时指向ret即LeaveAddr。

3、执行leave ret:

​ 即先执行mov esp,ebp,此时esp和ebp都指向ShellAddr-4

​ 再执行pop ebp,把esp指向的数据弹给ebp,即把ShellAddr-4地址处的数据弹给ebp,然后esp+4,指向 ShellAddr。

​ 再执行ret,把ShellAddr地址处的内容弹出给eip,这个数据可以是system函数的地址。也可使其它shellcode地址。

这样就成功篡改执行流至shellcode区域了;

第五步:程序执行shellcode,攻击结束。

img

可以参考一下这张图,这张图是在虽然不是迁移到bss段(迁移到栈上),但是本质一样,流程一致。

三、总结攻击步骤

栈迁移攻击的实施过程可以分为以下步骤:

  1. 首先确定缓冲区变量在溢出时,至少能覆盖栈上ebp 与ret两个位置。
  2. 利用栈溢出构造垃圾数据和gadget代码段所在的地址以及程序中的leave_ret指令地址。
  3. 执行leave的第一条指令mov esp ebp, esp指向了存放bss_s-4的地址,即ebp指向的地址。
  4. 执行leave的第二条指令pop ebp 。ebp即是bss_s-4
  5. 执行ret准备执行第二次leave 。
  6. 第二次执行leave ret, mov esp ,ebp ,esp指向 bss_s-4
  7. 第二次执行pop ebp,把bss_s-4地址中的数据弹给ebp,esp+4指向bss_s
  8. 第二次执行ret,此时esp指向bss_s,即eip指向bss_s

成功劫持。