栈平衡为什么要堆栈平衡因为要保持栈的大小,使ESP始终指向栈顶
概念函数如果要返回父程序,则在堆栈中进行操作的时候,一定要在RET指令之前,将ESP指向函数压入栈中时的地址如果通过堆栈传递参数了,那么在函数执行完毕后,要平衡参数导致的堆栈变化代码示意代码语言:javascript复制//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
过程使用输入函数(如read)将后续的Payload加载到bss段内,也就是伪造的栈通过pop ebp;ret来调整EBP寄存器pop ebp;ret可以使用pop EBX;ret 来代替通过leave;ret来更改ESP,使其指向伪造的栈(bss)然后在伪造的栈中执行下一段ROP注意使用bss作为stack发动ROP攻击可能会失败这是因为(后续ROP使用的GOT等)必要的变量被破坏,以及跳到stack等原因因为read/write时,系统内的dl_fixup函数对stack做了很好的保护使用的bss段建议在中间区域,如bss+0x800左右参考链接:
https://darkwing.moe/2019/04/15/Pwn学习笔记14-stack-pivot与Off-by-one/