在上一篇文章栈帧和函数调用的关系? 用最直白的语言解释一下.,我们阐述了过程调用和栈帧的关系,相信大家已经对函数开辟的栈帧的结构有了认识,如下图所示
我们接着用上一篇myadd函数的栈帧结构演示一下
仔细观察,在开辟myadd函数的栈帧之前的初始化动作(形参实例化结束后)把执行完myadd函数后要执行的内容保存到了这里
如何定位这里的位置呢? 注意我们传参的参数A, 就在A的下面一个地方的地址.
所以我们可以用一个指针p指向a,再把指针p–;就指向这里.之后就可以修改这里面保存的地址来实现悄无声息地跳转.
我们首先写一个BUG函数
void BUG()
{
printf("hello,im bug");
Sleep(2000);
}
再在myadd函数里面把下一条执行的指令修改为bug函数
int myadd(int a,int b)
{
int *p=&a;
p--;//这时候p就指向调转地址了
ret=*p;// 在修改之前先把原来的地址保存一下 保存在ret 里面 void* ret=null
*p=&BUG;//然后将此处修改为bug函数地址
}
我们这时候运行一下就会发现程序崩溃了,因为正常调用BUG函数会使用call指令,把BUG函数调用完后的返回地址给确定了,而非正常调用后BUG函数并没有明确返回地址.
但是我们可以把BUG函数的返回地址给确定了
还是通过栈帧结构发现
如果我们在其函数内部定义一个临时变量x,x往上两个单元的地址就是BUG函数结束后的返回地址.
之前我们已经把myadd函数结束后要执行下一条程序的地址保存在ret里面了,把ret里的值给BUG函数结束后的返回地址就行了.
代码如下
void BUG()
{
int x;
int *q=&x;
q+=2;
*q=ret;
printf("hello,im bug\n");
Sleep(2000);
}
运行结束后我们发现程序现在运行起来没有任何问题了.但是当整个程序结束的时候会报出EBP位置不正确的错误,这是为什么呢?
因为在正常调用开始时会push esp,然后在调用结束的时候会pop esp
但是在非正常调用的时候没有进行push esp的操作,而进行了pop esp的操作.这样esp就不在正确的位置上.
如何解决的呢?
在main函数里插入一段汇编代码
int main()
{
int a=0XAAAAAAAA;
int b=0xBBBBBBBB;
int c=myadd(a,b);
// 这是插入的汇编代码
__asm{
sub esp, 4
}
printf("main:you would run here!\n");
printf("res : %d\n",c);
getchar();
return 0;
}
至此,就完成了隐式的调用BUG函数
附上源码
#include<stdio.h>
#include<windows.h>
void *ret=NULL; //只要是指针 都是4个字节,所以void *可以定义指针 可以接受任何类型数据
void BUG()
{
int x;
//my add 非法调用,(非call调用 就少了push 入栈的过程,而在返回的时候ret pop了)
int *q=&x;
q+=2;
*q=ret;
printf("hello,im bug\n");//注意无论在哪修改 都是在函数执行完后返回的
Sleep(2000);
}
int myadd(int a,int b)
{
int *p=&a;
p--;
ret=*p;//注意 p解引用出来也是地址(需要返回的地址) 所以直接赋值给ret!
*p=&BUG;
printf("myadd调用结束,现在开始进入bug函数\n");
}
int main()
{
int a=0XAAAAAAAA;
int b=0xBBBBBBBB;
int c=myadd(a,b);
__asm{
sub esp, 4
}
printf("main:you would run here!\n");
printf("res : %d\n",c);
getchar();
return 0;
}