~~
首先,这个不是特性,也不是什么BUG,也不是什么,纯属娱乐! 对了,请大家不要用Release模式等会进行优化的编译方案。很明显,上面的a和p都是临时变量,并且没有使用。。优化就啥都没了。 我用的是VS 2005 其它环境。执行时,输出 Success!提示: 输出时会DOWN掉,不过不影响结果的查看!不知道各位有何高见!我心中也有一个答案。但先不说,大家一起来讨论。。。 共同完成这篇贴子。 随后,大家的高端回复会以如下方式出现如果不希望最后出现在这里,请大家注明。 默认情况下,表示同意!ID:XXXX解释:*****************=======================ID:XXXXX解释:********************
posted on 2010-05-06 12:41 麒麟子 阅读(2106) 评论(38) 编辑 收藏 引用 所属分类: Programming
什么也没有输出吧1 那估计输出了也是随即的! 回复 更多评论
不会有输出。也不会出现CORE DUMP 回复 更多评论
。。。这不就是指针段内偏移指向函数了么而且是和编译器有关的,不同的编译器生成的结果不一定相同。这是bug,不是特性。 回复 更多评论
其实原理很简单,系统调用 main 函数的时候先压入了 返回地址, 现在 p 恰好位于栈中返回地址处,然后你修改成了test函数,main函数退出后发现将返回地址是test函数,于是跳过去执行啦。 程序崩溃时必然的,你没有ExitProcess. 回复 更多评论
@skykrnl 嗯,过程是这样的。。 但我想看看大家目前到底有多无聊。能把这个问题提升到一个什么样的层次! 一起来吧,哈哈 回复 更多评论
这个问题以前试验过了,但是gcc没有生成对main的函数调用,所以这个效果没有出来。改一下就可以了:#include <iostream>using namespace std;void test( void ){ cout << "Success!" << endl;}void test2(void){ int a[ 1 ]; int* p = (int*)&a[0]+2; *p = ( int )test;}int main( ){ test2(); return 0;} 回复 更多评论
@打酱油的 嗯,自己构造,只要地址偏移正确就OK啦! 回复 更多评论
在VS中,我们还可以这样 #include <iostream> using namespace std; void test( void ) { cout << "Success!" << endl; } int main( void ) { _asm { mov dword ptr[ebp+4],offset test;把test的地址给返回地址 } return 0; } 回复 更多评论
这个可以从call和ret指令所做的事情来看,更涉及到函数调用在编译器以及目标机器指令问题。不过因为这里不存在虚拟机问题,都是x86,也就只针对call和ret而言: 不难想象在main之前的地方有如下代码: ; 压参数 push xxx push xxx push xxx call main ;main xxx xxx ret 首先call的动作主要包括:先压入返回地址到堆栈上(ebp指向),而c函数中,函数负责堆栈平衡,那么main中清除局部变量,改变ebp后,可以肯定ebp指向的当前堆栈中的值就是返回地址。ret指令则是从栈顶取出该地址并执行PC寄存器的跳转。 另一方面,函数调用时的运行时堆栈问题:首先栈是向下增长的,函数A调用函数B,那么首先压入参数到栈中,在函数B中因为局部变量的增长栈继续向下增长,也就是说,最终可以通过ebp的偏移取得函数A中局部变量的信息。他们贡献同一个栈: --stack-- A:local_var1 A:local_var2 A:ret_addr B:arg_var1 B:arg_var2 B:local_var1 .... 基于以上两个条件,指针a[0]+3,则向高地址偏移了12字节的地址(3*sizeof(int)),看下main函数的参数,实际上是3个:argc, argv, env。这样偏移后,恰好就是调用main那个函数在使用call时,压入的返回地址。 因此,在main返回时,ret弹出的地址已经被改变。 ps: 在错误地跳转到test后,test执行完去ret时,堆栈上提供的返回地址是不定的,崩溃也很正常了。 回复 更多评论
@Kevin Lynx 嗯,分析得很好哦。。但是,我觉得这和main的参数没关系。。偏移到ret_addr就已经停下了。还没经过B:arg_var1 B:arg_var2 B:local_var1 回复 更多评论
*p = ( int )test;这句没看明白。 回复 更多评论
1- CALL会把下一个指令的地址放进堆栈。 2- RET就让这个地址出栈,并跳转至这个地址。 3- 局部变量也是在栈上的。 代码中,你用局部变量的地址定位到栈内的ret返回地址,然后将其修改为TEST的函数地址。RET后,就跳转到TEST函数了。因为没有CALL,所以栈内不会压入返回地址,然后栈就乱掉了,后面依赖栈的指令,就可能会导致出错。 在一些软件保护里面,经常会用到这种手段,PUSH FUNCPTR, RET。这样可以用CALL来调用函数。从而迷惑分析者。通过ESP寄存器直接操作,更让分析者头大。再用一些无效指令插在其中,做成花指令,就更高端了。特别是花连跳,分析者就很难一眼分辨出走向了。 回复 更多评论
@小时候可靓了 我说的是有点问题。跟参数没关系。参数先于返回地址压栈。- - 昏头了。 话说回来,仔细分析的话,我突然发觉: int* p = (int*)&a[0]+3; 这里为什么会是3呢?跟了下汇编,发觉直接被翻译为ebp+4了: push ebp mov ebp, esp ... mov eax, [ebp+4] 不是很明白这个地方。 饭老大说得和我一样。 回复 更多评论
典型的 "撞大运编程" 回复 更多评论
@Kevin Lynx int a; int *p 这两个是临时变量 a 的地址是 ebp -8 p 的地址是 ebp-4 所以,&a[0]+3其实就是 ebp-8+ 3*4 = ebp+4 回复 更多评论
@坏 撞大运和这可不一样。 这是明明知道的后果! 回复 更多评论
@小时候可靓了 a 的地址是 ebp -8 p 的地址是 ebp-4 这两个结论从何而来?何况,为什么要+3? ps,话说这样N多回复会给你BLOG增加不少积分啊 :D 回复 更多评论
@Kevin Lynx 都说了,这个程序是故意写出来的。不是撞大运撞的。 +3是为了正好得到我们的偏移地址! 上面那个结论,就是临时变量在栈上分配内存分成那样的撒。至于BLOG的分,我不知道这回事! 回复 更多评论
@Kevin Lynx p的地址大于a。 所以a在栈顶,p在a下面。还有个push ebp,接下来就是返回地址。 正好三个。 回复 更多评论
@饭中淹 嗯,就是这样的。 不过如果你在DEBUG下看的话,会发现a不在栈顶。 DEBUG会多分一些 回复 更多评论
@小时候可靓了 { int a; int b; int c; } 按我的理解,c应该在栈顶,而不是a。但是实际上跟踪你的代码来看,a确实在栈顶,在p后添加变量int i ,又有意外: a : 0x0012ff74 &p:0x0012ff78 &i:0x0012ff70 回复 更多评论
@小时候可靓了在我这里gcc和BCB都是无任何输出的。随便在前面加个局部变量VC也同样无输出。这不就是撞大运么.... 回复 更多评论
@坏 需要适当调整 3 这个偏移量 ps,当然也取决于编译器生成的指令。鉴于目前我也不明白为什么偏移是3,看起来LZ也无法给出详细的说明,这个3很有可能只是个巧合。 1、除了push ebp外可能还有压入其他寄存器的值(保存寄存器信息) 2、a理论上应该不在栈顶,就像我例子中的i,而p应该在栈顶 回复 更多评论
@Kevin Lynx 栈顶是低地址~~ 回复 更多评论
@小时候可靓了 这个不需要你重申,我更不希望我来重申我的问题: 解释下这个: int a[1]; int *p; int i; a : 0x0012ff74 &p:0x0012ff78 &i:0x0012ff70 注意p在中间 。 回复 更多评论
@Kevin Lynx 这个巧合是建立在刚刚那个代码上的。。这个+3,是人为构造的。 不管+几,我们就是想把他指向main的返回地址。 不过呢,在编译器环境条件众多的情况下, 如 @坏 所说,真是撞大运。 至于合理的解释,上面有兄弟给了答案了。。。 回复 更多评论
@Kevin Lynx 其实我贴出来,同样是想看到关于这个临时空间的解释。。你知道吗?在我的机器上是这样的。 0x0012ff60 0x0012ff54 0x0012ff48 如果我能一一解释清楚,我就不让大家讨论了。。。 嘿嘿 回复 更多评论
@小时候可靓了 饭给的解释是我在群里跟他谈过的。 这个解释是我在看汇编的时候看到的: 00401750 push ebp 00401751 mov ebp,esp 00401753 sub esp,0Ch 00401756 lea eax,[ebp+4] 00401759 mov dword ptr [p],eax 恰好a莫名其妙地出现在栈顶,而不是p,(而在我举的包含i的例子中,作为出现在最后定义的i却莫名其妙地出现在栈顶),加上这个push ebp,就出现了3。 谁能给个解释:为什么a、p、i三者的相对地址和其定义顺序存在差别?(难道编译器对数组的处理要特殊点?) 回复 更多评论
@小时候可靓了 0x0012ff60 a 0x0012ff54 p 0x0012ff48 i ??? 如果是这样的话,那这个才是正确的排列地址啊。诡异的是我那个情况。 回复 更多评论
@Kevin Lynx 可是,他们的差值很鬼异! 回复 更多评论
@小时候可靓了 刚想补上这句,差距都是12.。。- - 我这边差距都是4,正常差距,但是顺序诡异。。。 回复 更多评论
唉,淡定吧。这种情况让我头疼。 回复 更多评论
汗。。- - 。。偶然间看到LZ 博客HGE一栏居然转载有我N久以前乱写的东西,真巧啊。。 回复 更多评论
@Kevin Lynx 是啊,以前在学校的时候,捣鼓了两天HGE。 回复 更多评论
又是offset.....看了王爽汇编之后,你一定会发现这复杂的世界又是多么的和谐 回复 更多评论
@zuhd 王爽的我没看过耶!!! 汗!!! 回复 更多评论
高手好多哦。你们想得太复杂了。 我认为这一句 *p = ( int )test; 调用了test函数,只不过test函数没有返回值,且p指向非法内存,所以给*P赋值就会犯错,于是程序就会挂掉。 楼主说的特性是不是test和test()是一样的啊? 回复 更多评论
我发现我上面的结论错了。刚才把代码输入到VS2008,调试了一下。发现: *p=(int)test;确实只是把test的地址放到了*p里面,而不是调用test(); 而在return的时候调用了test,然后出错。 说明*p这个非法内存访问是真的改变了main函数返回后执行的下一条指令的地址。 回复 更多评论
Powered by: C++博客 Copyright © 麒麟子