本文结合反汇编pc版植物大战僵尸,讲解游戏暂停后恢复call的过程

首先介绍栈空间用到的寄存器
ESP 栈顶指针 (extended stack pointer)英文直译是:扩展栈指针
EBP 栈底指针 ,指的是本层call子程序的栈底,就是最上层call的栈底。不是整个栈空间的栈底 (extended base pointer)英文直译是扩展基址指针
push 入栈(给call穿参数)
push eax 等价于 sub esp , 4 mov [esp],eax 把eax压入栈等价于 县 让esp栈指针 - 4 ,然后把 eax的值存入到 esp指向的空间里,也就是栈顶空间里
pop 出栈(call 结束后还原eip)
pop eax 等价于 mov eax,[esp] add esp , 4 把栈里的值出栈给eax 。等价于 先把 esp栈顶指向的空间的值 复制给 eax,然后把栈顶指针 + 4
call调用子程序
call 12345678 等价于 push eip jmp 12345678 pop eip (eip中存储着cpu下一条要执行指令的地址)
例如下面的例子,每次要暂停游戏,以后点击返回游戏以后
执行call之后, eip里面的指,要push到栈顶
栈顶变成了之前eip里面的值
下图执行到retn语句的时候栈顶刚好就是call语句执行以后push进来的eip的值。这样cpu执行eip的值,就回到了call的下一条语句
retn 返回
retn 8 等价于 pop eip , add esp ,8 。先把eip出战,这样cpu再执行的eip的值,就是之前call执行以后,执行的push eip的地址了
栈平衡
内平栈
内平栈的特点是通过retn指令 改变esp,让栈平衡
调用call的时候有函数参数,需要push入栈,retn的时候要把栈顶指针esp还原 加地址
例如当植物大战僵尸每次从暂停以后,再返回游戏,调用了下面的call指令
push edx
call 12345678
return 0x4
调用函数有1个参数,那么就要push 一个参数,这里edx作为第一个参数。 然后call 函数地址。再retn的时候会 retn 0x4 . retn 4等价于 pop eip, add esp 4. 先出栈eip ,让cpu指向调用call前的指令,再 add esp 4,是让栈顶指针 +4 ,就是像栈底移动4,原因是因为在调用call之前,有一个 push edx,这额操作让esp 发生改变,等价于 mov [esp],edx sub esp ,4,让esp减去了4,所以 retn 0x4,让esp再加上4,达到栈平衡
如下图反汇编演示了当植物大战僵尸每次从暂停以后,再点击“”返回游戏“”,调用了下面的call指令,(注意左上角的阳光,我已经改了最大阳光上限,不是9990,而是更大,位数显示不全了)
外平栈
外平栈的retn 后面不跟着数字,而是在call结束以后在后面 add esp 直接增加 栈顶指针
如下面例子:
push eax
push ecx
call 12345678
add esp 8
call里面的子程序retn 后面不跟数字
这个add esp 8是让栈顶指针加8,因为call 之前 push了2次,eax和ecx长度都是32位,是4字节 4x2 =8 字节,所以最后加8 字节。
截图如下: