很多人不知道call和ret的具体动作,只知道call的时候会跳转到被调用函数的地址继续执行指令,ret会直接跳转到返回地址,至于寄存器和栈上的变化不是很了解。
0x00 定义
- CALL pushes the return address onto the stack and transfers control to a procedure.
- RET pops the return address off the stack and returns control to that location.
0x01 解释
call指令隐含操作push EIP,ret指令隐含操作 pop EIP,两条指令完全对应起来.
EIP寄存器用于保存下一个要执行的命令地址。在函数返回的过程最后执行ret,就是把之前入栈的返回地址出栈,并放入到EIP寄存器。这样指令就不会顺序继续执行了,而是跳了一下,跳到EIP指向的地方。
相应的,调用call指令的时候,会把下一个要执行的命令地址入栈。
0x02 汇编代码
函数调用
30: print_out(0, 2);
013D155E push 2 //第二个实参压栈
013D1560 push 0 //第一个实参压栈
013D1562 call print_out (13D10FAh)//返回地址压栈,本例中是013D1567,然后调用print_out函数
013D1567 add esp,8 //两个实参出栈
//注意在call命令中,隐含操作是把下一条指令的地址压栈,也就是所谓的返回地址
函数返回
013D1431 mov esp,ebp //ebp的值传给esp,也就是恢复调用前esp的值
013D1433 pop ebp //弹出ebp,恢复ebp的值
013D1434 ret //把返回地址写入EIP中,相当于pop EIP
0x03 堆栈变化
函数调用
函数调用的过程是EBP和ESP分别向栈顶移动的过程,注意EBP是实参和返回地址入栈以后相对于ESP先入栈的。
函数返回
明显能看到在ret的时候,esp自动减了一个栈指针的大小(不知道这样描述是否准确,不是一个栈帧的大小,因为函数执行的指令集合是一个栈帧),返回地址出栈,进入EIP寄存器(这里没有画出来)。