先来看一段代码。
#include<iostream>
using namespace std;
void unExpected(){
cout<<"UnExpected!"<<endl;
}
int main(int argc, char* argv[]){
long i=0;
*(&i+4) = (long)unExpected;
cout<<"Hello whatever"<<i<<endl;
return 0;
}
以下是执行的结果(注:编译环境GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1))
Hello whatever
UnExpected!
Segmentation fault
看c++代码看不出有调用unExpected函数,但就是被调用了,反汇编可看到main的部分代码如下
Dump of assembler code for function main(int, char**):
0x0000000000400876 <+0>: push %rbp
0x0000000000400877 <+1>: mov %rsp,%rbp
0x000000000040087a <+4>: push %rbx
0x000000000040087b <+5>: sub $0x28,%rsp
0x000000000040087f <+9>: mov %edi,-0x24(%rbp)
0x0000000000400882 <+12>: mov %rsi,-0x30(%rbp)
=> 0x0000000000400886 <+16>: movq $0x0,-0x18(%rbp)
所定义的long i就对应着rbp-0x18的位置,c/c++的默认调用函数的规则为_cdecl,它规定了函数的堆栈平衡由调用者负责,并且参数从最右边开始逐个入栈,最后入栈的是函数的返回地址,也就是函数调用完以后下一条要执行的指令的地址,在一个c/c++函数调用发生时,其栈的布局大致如下所示
低<---------------------------------------------------高
[局部变量][rbx:8][rbp:8][rip:8][arg1][arg2][arg3]...
通过局部变量定位到函数的返回地址,只需简单的加减处理,rip=rbp+8=(char*)(&i+3)+8=&i+4,所对对&i+4这个地址赋值,就是对函数的返回地址进行了修改,所以才发生了unExpected函数的调用。