版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/swjtu100/article/details/49962117
0x00 系统栈的工作原理
1) 进程使用的内存
- 代码区:二进制机器代码
- 数据区:全局变量等
- 堆区:进程动态请求内存,用完归还。如动态分配和回收
- 栈区:用于动态存储函数间调用关系,也叫系统栈、调用栈,由系统自动维护
2) 系统栈
int func_B(int arg_B1, int arg_B2)
{
int var_B1, var_B2;
var_B1=arg_B1+arg_B2;
var_B2=arg_B1-arg_B2;
return var_B1*var_B2;
}
int func_A(int arg_A1, int arg_A2)
{
int var_A;
var_A = func_B(arg_A1,arg_A2) + arg_A1 ;
return var_A;
}
int main(int argc, char **argv, char **envp)
{
int var_main;
var_main=func_A(4,3);
return var_main;
}
- 程序中使用的缓冲区可以是堆区、栈区、存静态变量的数据区
- 同一文件不同函数的代码在内存代码区的分布是散乱无关的
- 程序执行中函数调用与系统栈的变化
3) 寄存器与函数栈帧
- 当前运行函数的栈帧总在系统栈顶端
- 当前栈帧(系统栈顶端)由ESP和EBP标识
ESP:栈指针寄存器,存放着指向顶端栈帧的栈顶的指针
EBP:基址指针寄存器,存放着指向顶端栈帧的底部的指针
- 函数栈帧包含
局部变量:保存局部变量
栈帧状态值:保存当前栈帧的顶部和底部
函数返回地址:函数结束后返回到之前的代码区继续执行 - 函数栈帧的大小不固定,与函数局部变量多少有关。函数运行过程中栈帧的大小也在变
- EIP:指令寄存器,存着指向下一条等待执行指令的指针。
0x01 函数调用
1) 函数调用约定与相关指令
- 函数调用约定:描述了函数参数传递方式和栈协同工作的技术细节。包括:
a) 参数传递方式是寄存器还是压栈
b) 栈传递时,参数压栈顺序
c) 清栈由调用方还是被调方进行 - 不同OS、语言、编译器的函数调用约定有差别
Visual C++6.0可支持3种函数约定,默认使用_cdecl调用方式
_fastcall方式在参数较少时通过寄存器传参:参数多时,前两个用寄存器传参,其余用压栈的方式传递
2) 函数调用步骤
- 参数入栈:参数从右向左依次压入栈中
- 返回地址入栈:当前代码区调用指令的下一条指令地址压入栈,供函数返回是继续执行
- 代码区跳转:CPU从当前代码区跳转到被调用函数的入口
- 栈帧调整
a) EBP入栈:保存当前栈帧状态值,以备后面恢复本栈帧时使用
b) ESP—>EBP:更新栈帧底部,切换到新栈帧
c) ESP – 新栈帧大小:抬高栈顶,给新栈帧分配空间
- OllyDbg中栈帧是按前栈帧EBP值划分的,返回地址成了栈帧顶部数据
3) 函数返回步骤
- 保存返回值:函数返回值保存在EAX寄存器
- 弹出当前栈帧,恢复上一个栈帧
a) ESP + 当前栈帧大小:堆栈平衡基础上,降低栈顶,回收当前栈帧空间
b) POP EBP:前栈帧EBP弹给EBP,恢复上一个栈帧
c) POP EIP:函数返回地址弹给EIP
- 跳转:按EIP的值返回母函数继续执行
4) 函数调用实现
——《0day安全》学习笔记