栈平衡

为什么要堆栈平衡

因为要保持栈的大小,使ESP始终指向栈顶

概念

  • 函数如果要返回父程序,则在堆栈中进行操作的时候,一定要在RET指令之前,将ESP指向函数压入栈中时的地址
  • 如果通过堆栈传递参数了,那么在函数执行完毕后,要平衡参数导致的堆栈变化

代码示意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//ESP 为 NN  
push p2 //ESP=NN-4
push p1 //ESP=NN-8
call test //ESP=NN-0C
{ //进入函数内
push ebp //ESP=NN-10
mov ebp, esp //EBP 指向 栈顶 EBP==当前ESP
mov eax, dword ptr [ebp+0ch] //eax = 参数2
mov ebx, dword ptr [ebp+08h] //ebx = 参数1
sub esp, 8 //ESP == NN-18
...
add esp, 8 //ESP== NN-10
pop ebp //ESP==NN - C
ret 8 //ESP==NN + 4 +8
}
//ESP = NN

总结

也就是说当函数在栈中操作时,需要先把ESP 转交给EBP 然后继续操作,当操作完后,在ret之前,要先将ESP恢复成进入栈前的状态(进入栈后每次压入参数都会使栈空间一点点变小,而因为ESP要始终指向栈顶的原因,所以要把它恢复成原来的大小。)最后再将EBP移除栈,这样就是一个简单的栈平衡。


栈转移

  • 在栈空间不够存放paylaod的情况下,需要一个新的地址空间来存放payload。
  • 开启了 PIE保护,栈地址未知,我们可以将栈劫持到已知的区域。

概念

劫持栈的rsp(esp),使其指向其他位置,形成一个伪造的栈。这样栈也就被劫持到攻击者控制的内存上去,然后在该位置做ROP。

必要的Gadget

pop EBP;ret : 释放EBP,并链接伪造的栈

leave;ret : 更改ESP,指向后续的payload

  • 等价于 mov ESP,EBP; pop EBP; ret;

原理

栈转移的原理就是以 pop ebp;ret + 伪造的栈 让程序直接跳转到伪造的栈里面,然后为了保持栈平衡,从而执行leave; ret,最后继续执行伪造的栈内的payload

过程

  1. 使用输入函数(如read)将后续的Payload加载到bss段内,也就是伪造的栈
  2. 通过pop ebp;ret来调整EBP寄存器
    • pop ebp;ret可以使用pop EBX;ret 来代替
  3. 通过leave;ret来更改ESP,使其指向伪造的栈(bss)
  4. 然后在伪造的栈中执行下一段ROP

注意

  1. 使用bss作为stack发动ROP攻击可能会失败
  2. 这是因为(后续ROP使用的GOT等)必要的变量被破坏,以及跳到stack等原因
    • 因为read/write时,系统内的dl_fixup函数对stack做了很好的保护
  3. 使用的bss段建议在中间区域,如bss+0x800左右

参考链接:

https://darkwing.moe/2019/04/15/Pwn学习笔记14-stack-pivot与Off-by-one/