调用栈浅说
调用栈的图参考了这篇博客https://blog.csdn.net/VarusK/...
通过这个图可以形象看出函数调用的过程中,每个函数调用的栈帧如何,栈区的排布等信息。
当一个函数被调用的时候,进栈的顺序为:
- 主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址。
- 函数的各个参数,由右往左入栈的,然后是函数中的局部变量。
修改调用栈中的返回地址
如上面一小节提到的那样,我们的传参和主调函数的下一个指令地址在栈的结构中是相邻的,于是我们可以通过参数地址定位到主调函数的下一指令地址,并对其进行修改。
代码如下:
#include <stdio.h>
#define dp(msg)\
{\
printf(msg);\
printf(" > in func: < %s >", __FUNCTION__);\
printf("\n");\
}
void leaper( int a )
{
int* p = &a;
if ( a ){
for( int i = 0; i < 64; i ++ ){
if ( ( *(p+i) > (long)leaper ) && ( ( *(p+i) - (long)leaper ) < 0x1000 ) ){
*(p+i) += 5;
break;
}
if ( ( *(p+i) < (long)leaper ) && ( ( (long)leaper - *(p+i) ) < 0x1000 ) ){
*(p+i) += 5;
break;
}
}
}
return;
}
void func_a()
{
dp("here!");
}
int main( int argc, char* argv[] )
{
leaper(1);
func_a();
dp("end");
return 0;
}
代码说明:main中调了leaper函数,首先会把执行func_a的指令地址(也就是执行完leaper之后的指令地址)先压栈,然后将传参压栈,在leaper中参数地址取出来&a,然后在其周围寻找类似于leaper指针值的值,找到第一个就ok,然后将其+5,这就算给下一条指令地址加上偏移了,这里面我担心不同编译器的栈帧结构不太一样,所以采取这种写法容错高一些,+5是为了正确跳过指令的长度,(不同的芯片这个值不一样,这样看具体的指令长度,比如arm应该是4,86的芯片的call指令写5应该是ok,具体应该怎么样可以使用objdump看汇编代码,然后进行修改)。
最终的效果,因为调用栈中的下一条指令的地址被修改了,所以func_a会被跳过。直接打印"end"了。
因为c/cpp有了指针,会导致有很多玩内存的玩法,通常会造成一些让人错愕的效果,“不安全”和“玩法”成为了c/cpp这枚硬币的两面。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。