现在的位置: 首页 > 综合 > 正文

堆栈操作之———神奇的代码

2013年02月26日 ⁄ 综合 ⁄ 共 1336字 ⁄ 字号 评论关闭

下面看一段神奇的代码,这段代码运行之后结果是:

hello!

haha!

//主程序

#include <iostream>

using namespace std;

unsigned int retAddr;

void printhaha()

{

int a;

*(int *)(&a+2) = retAddr;           

cout<<"haha!"<<endl;

}

void printhello()

{

cout<<"hello!"<<endl;

int a = 0;

retAddr = *(int *)(&a+2);           

*(int *)(&a+2) = (int)printhaha;      

}

int main()

{

printhello();

__asm

{

sub esp, 4;

}

return 0;

}

首先大家要知道的一个知识是:在调用一个函数时会发生下面几件事,第一步是将函数的参数压栈(c的函数参数压栈方式从右往左压),第二步是将函数调用这条指令的下条指令的地址压进去(方便函数返回后直接接着执行函数调用的下条指令),在接着就是保存ebp,将ebp压栈,ebp后面的就是函数内部的局部变量了,在函数内部访问局部变量都是通过ebp来访问,如对函数printhello()里面的a, int a=0;  汇编形式就是mov [ebp-4], 0;

知道这些知识下面就容易了,j接着分析关键的代码:

retAddr = *(int *)(&a+2);

首先取a的地址也就是ebp-4,然后地址加2(实际转换之后加的是sizeofint*2 = 8),也就是ebp+4 ebp+4正好是函数调用时保存的下条指令地址,如果我将此地址里面的内容改变之后那么函数返回时将不执行printhello();这句后面的指令,而是执行修改之后的指令,而修改后的指令正是printhaha()这个函数的地址,所以printhello();执行完之后会接着执行printhaha(),printhaha()的内部加了这句:

*(int *)(&a+2) = retAddr; 

这句的目的是将下条指令的地址给恢复掉,到此有的人可能说就完了,其实还真没这么简单,我刚开始也以为就完了(之前我没有加__asm{sub esp ,4}),但是一运行之后虽然结果出来了但是随后马上就崩掉了。

最后就是最关键的地方了,大家还记得函数调用时所发生的几件事吧,函数在调用时会将下条指令的地址压进去,而我通过修改下条指令的地址来达到自动调用函数时,这时函数调用机制就变了,也就是此时他没有什么压参数啊 压下条指令的地址这些,但虽然没压进去最后函数执行完还是会有一个ret指令将压指针esp+4,所以这样就会造成堆栈的破坏(也就是破坏了堆栈的平衡),所以我在主函数的后面又加了:

__asm

{

sub esp, 4;

}

就是这个目的,其实大家再仔细分析下,我通过这种方式其实最终还是会破坏栈里面保存的寄存器的值,但是好在压入的edi的值为0,也就是没任何作用,所以我将其破坏掉不影响什么,最终程序能照常执行,其实也有办法就是将里面的值保存下来,破坏之后再恢复一是一样的。

最后希望有什么纰漏的地方大神们能够指出来!!!

抱歉!评论已关闭.