主要从三个方面来详解栈帧
1. 在c语言中关于函数调用过程,用栈帧来分析
2. 可以通过栈帧来修改函数返回时的地址
3. 让不能正常返回的函数可以返回
----------
测试环境 VS2008 win10
----------
1.函数的调用过程 栈帧分析(栈帧在C语言中是指每一个函数调用时栈区会自动地为其开辟一块空间即为栈帧。栈帧中保存了该函数的返回地址和局部变量)
先来看一段代码:
<textarea readonly="readonly" name="code" class="C++">
#include <stdio.h>
int Add(int x,int y)
{
int z = 0;
z = x+y;
return z;
}
int main()
{
int a = 0xaaaaaaaa;
int b = 0xbbbbbbbb;
int ret = Add(a,b);
printf("ret = %d\n", ret);
return 0;
}
</textarea>
`然后一直按F10调试到 int c =Add(a,b)这一步,同时可以在窗口里打开反汇编 函数中的变量都是存在于栈区中(当然了用stactic定义的变量不存于栈区),并且这些变量会一直存在于程序运行期间
----------
ebp为栈底指针,esp为栈顶指针,EIP用于存放当前指令的下一条指令的地址
----------
<textarea readonly="readonly" name="code" class="汇编">
int a = 0xaaaaaaaa;
00E9141E mov dword ptr [a],0AAAAAAAAh
int b = 0xbbbbbbbb;
00E91425 mov dword ptr [b],0BBBBBBBBh
int ret = Add(a,b);
00E9142C mov eax,dword ptr [b]
00E9142F push eax
00E91430 mov ecx,dword ptr [a]
00E91433 push ecx
00E91434 call @ILT+215(_Add) (0E910DCh)
00E91439 add esp,8
00E9143C mov dword ptr [ret],eax
</textarea>
可以看出即将调用Add函数时是将b先压栈,a后压栈,随后调用call指令(call指令有两个作用 1> 将call指令的下一条指令的地址存到栈中 2> 用目标函数的地址覆盖EIP的值)
<img src="https://img-blog.csdn.net/2017094030746?watmark/2/text/aHR0cDovL2cuY3Nkbi5uZXQvV5bm1hbjIzMw
==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="40%">
然后接着用F11进入Add函数内部
<textarea readonly="readonly" name="code" class="汇编">
int Add(int x,int y)
{
00E913B0 push ebp
00E913B1 mov ebp,esp
00E913B3 sub esp,0CCh
00E913B9 push ebx
00E913BA push esi
00E913BB push edi
00E913BC lea edi,[ebp-0CCh]
00E913C2 mov ecx,33h
00E913C7 mov eax,0CCCCCCCCh
00E913CC rep stos dword ptr es:[edi]
int z = 0;
00E913CE mov dword ptr [z],0
z = x+y;
00E913D5 mov eax,dword ptr [x]
00E913D8 add eax,dword ptr [y]
00E913DB mov dword ptr [z],eax
</textarea>
这段汇编是将EBP先压栈,此时的EBP指向的是main函数的栈底,然后使ESP的内容移动到EBP中,此时相当于EBP指向了另一个栈帧的栈底,而使得ESP减去一个数,,这样一来开辟了一块新的栈帧,而这个新的栈帧即为Add函数的栈帧返回值z,将其先保存到EAX寄存器里,然后移动EBP,ESP,使得回到main函数的栈帧中。而调用ret时和call相反
2. 可以通过栈帧来修改函数返回时的地址
<textarea readonly="readonly" name="code" class="C">
#include <stdio.h>
#include <windows.h>
void bug()
{
printf("I am a bug");
Sleep(3000);
}
int Add(int x, int y)
{
int *p = &x;
p--;
*p = bug;
printf("Add is a 函数\n");
}
int main()
{
int a = 0;
int b = 0;
int ret = Add(a,b);
printf("ret: %d\n", ret);
return 0;
}
</textarea>
将call指令的下一条指令的地址用bug函数的地址覆盖掉,达到了跳转到bug函数执行
执行结果:
Add is a 函数
I am a bug请按任意键继续
3. 让不能正常返回的函数可以返回
在上面的 bug函数中,当bug函数调用完毕后不知道返回时的地址,所以我们可以在进入bug函数之前保存一下call指令的下一条指令的地址
#include <stdio.h>
#include <windows.h>
void *ret = NULL;
void bug()
{
int c = 0;
int *p = &c;
p+=2;
*p = (int)ret;
printf("I am a bug");
Sleep(3000);
}
int Add(int x, int y)
{
int *p = &x;
p--;
ret = *p;
*p = bug;
printf("Add is a 函数\n");
return 0;
}
int main()
{
int a = 0xaaaaaaaa;
int b = 0xbbbbbbbb;
int ret = Add(a,b);
printf("main is running\n");
return 0;
}