函数调用与返回过程
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就知道是否发生了溢出
本文深入解析了函数调用(call)与返回(ret)的内部机制,包括段内与段间调用的区别,堆栈的设置与参数传递方式。同时,介绍了GS安全编译机制如何防止栈溢出,确保程序运行的安全性。
790

被折叠的 条评论
为什么被折叠?



