函数调用与返回过程
call过程:
// 段内call通常进行两步操作
push ip
jmp addr
// 如果call的函数不是本段当中的地址
push cs
push ip
jmp addr
ret过程:
ret实际上是一个伪指令,由编译器决定在编译时最终使用retn(return from near procedure)
或者retf(return from far procedure)
,这实际上对应了call的两种方式
段内call不需要将段地址压栈,对应的retn
只需要实现
pop ip
段间call需要将段地址压栈,因此对应的retf
实现
pop ip
pop cs
堆栈设置
每一个函数都需要自己的堆栈,而call指令并没有为被调用函数设置堆栈,因此需要在子程序的开头设置堆栈
push ebp
mov ebp, esp
----------------------
// 子程序内容
----------------------
mov esp, ebp
pop ebp
ret
参数传递
// 主程序按从右向左的顺序将参数逐个压栈,最后一个参数先入栈。每一个参数压栈一次。
// 在子程序中,使用[EBP+X] 的方式来访问参数。X=8 代表第 1 个参数;X=12 代表第二个参数,依次类推
// [ebp+4]保存ip的值,因为call会将当前ip压栈
push arg3
push arg2
push arg1
call func
GS安全编译
GS是针对栈的一种保护机制,具体的过程如下:
在函数调用发生时,会使用一个随机值(IDA中会标记为__security_cookie
)来防止栈溢出
以上程序中重点需要关注这几句
mov eax, __security_cookie
xor eax, ebp
mov [ebp+var_4], eax
-------------------
mov ecx, [ebp+var_4]
xor exc, ebp
call ....
通俗的来讲就是首先对ebp和__security_cookie
做异或,结果放在ebp-4的位置,也就是ebp下面的第一个位置,在函数ret之前,取出这个值和ebp再做一次异或,两次异或后的结果就会恢复为__security_cookie
,只需要验证__security_cookie
就知道是否发生了溢出