麒麟子

~~

导航

<2010年5月>
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345

统计

常用链接

留言簿(11)

随笔分类

随笔档案

Friends

WebSites

积分与排名

最新随笔

最新评论

阅读排行榜

评论排行榜

讨论会:一个不是问题的问题!

 

 1#include <iostream>
 2using namespace std;
 3void test( void )
 4{
 5 cout << "Success!" << endl;
 6}

 7int main( void )
 8{
 9 int a[ 1 ];
10 int* p = (int*)&a[0]+3;
11
12 *=  ( int )test;
13 return 0;
14}

15
16


首先,这个不是特性,也不是什么BUG,也不是什么,纯属娱乐! 对了,请大家不要用Release模式等会进行优化的编译方案。
很明显,上面的a和p都是临时变量,并且没有使用。。优化就啥都没了。 我用的是VS 2005  其它环境。

执行时,输出 Success!

提示: 输出时会DOWN掉,不过不影响结果的查看!


不知道各位有何高见!
我心中也有一个答案。但先不说,大家一起来讨论。。。 共同完成这篇贴子。 随后,大家的高端回复会以如下方式出现
如果不希望最后出现在这里,请大家注明。 默认情况下,表示同意!

ID:XXXX
解释:*****************


=======================
ID:XXXXX
解释:********************

posted on 2010-05-06 12:41 麒麟子 阅读(1921) 评论(38)  编辑 收藏 引用 所属分类: Programming

评论

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 12:54 梦在天涯

什么也没有输出吧1

那估计输出了也是随即的!  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 13:04 打酱油的

不会有输出。也不会出现CORE DUMP  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 13:06 schindlerlee

。。。这不就是指针段内偏移指向函数了么
而且是和编译器有关的,不同的编译器生成的结果不一定相同。
这是bug,不是特性。  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 13:11 skykrnl

其实原理很简单,系统调用 main 函数的时候先压入了 返回地址,
现在 p 恰好位于栈中返回地址处,然后你修改成了test函数,main函数退出后发现将返回地址是test函数,于是跳过去执行啦。
程序崩溃时必然的,你没有ExitProcess.  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 13:14 小时候可靓了

@skykrnl
嗯,过程是这样的。。 但我想看看大家目前到底有多无聊。能把这个问题提升到一个什么样的层次! 一起来吧,哈哈  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 13:25 打酱油的

这个问题以前试验过了,但是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;
}
  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 13:28 小时候可靓了

@打酱油的
嗯,自己构造,只要地址偏移正确就OK啦!  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 13:40 小时候可靓了

在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;
}  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 13:58 Kevin Lynx

这个可以从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时,堆栈上提供的返回地址是不定的,崩溃也很正常了。  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 14:03 小时候可靓了

@Kevin Lynx
嗯,分析得很好哦。。但是,我觉得这和main的参数没关系。。偏移到ret_addr就已经停下了。还没经过B:arg_var1 B:arg_var2 B:local_var1  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 14:57 春续

*p = ( int )test;

这句没看明白。
  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 15:11 饭中淹

1- CALL会把下一个指令的地址放进堆栈。
2- RET就让这个地址出栈,并跳转至这个地址。
3- 局部变量也是在栈上的。

代码中,你用局部变量的地址定位到栈内的ret返回地址,然后将其修改为TEST的函数地址。RET后,就跳转到TEST函数了。因为没有CALL,所以栈内不会压入返回地址,然后栈就乱掉了,后面依赖栈的指令,就可能会导致出错。

在一些软件保护里面,经常会用到这种手段,PUSH FUNCPTR, RET。这样可以用CALL来调用函数。从而迷惑分析者。通过ESP寄存器直接操作,更让分析者头大。再用一些无效指令插在其中,做成花指令,就更高端了。特别是花连跳,分析者就很难一眼分辨出走向了。

  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 15:19 Kevin Lynx

@小时候可靓了
我说的是有点问题。跟参数没关系。参数先于返回地址压栈。- - 昏头了。

话说回来,仔细分析的话,我突然发觉:
int* p = (int*)&a[0]+3;
这里为什么会是3呢?跟了下汇编,发觉直接被翻译为ebp+4了:
push ebp
mov ebp, esp
...
mov eax, [ebp+4]

不是很明白这个地方。

饭老大说得和我一样。  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。[未登录] 2010-05-06 15:30

典型的 "撞大运编程"  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 15:33 小时候可靓了

@Kevin Lynx
int a;
int *p
这两个是临时变量

a 的地址是 ebp -8
p 的地址是 ebp-4
所以,&a[0]+3其实就是 ebp-8+ 3*4 = ebp+4  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 15:34 小时候可靓了

@坏
撞大运和这可不一样。 这是明明知道的后果!  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 15:41 Kevin Lynx

@小时候可靓了
a 的地址是 ebp -8
p 的地址是 ebp-4

这两个结论从何而来?何况,为什么要+3?

ps,话说这样N多回复会给你BLOG增加不少积分啊 :D  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 15:51 小时候可靓了

@Kevin Lynx
都说了,这个程序是故意写出来的。不是撞大运撞的。 +3是为了正好得到我们的偏移地址! 上面那个结论,就是临时变量在栈上分配内存分成那样的撒。至于BLOG的分,我不知道这回事!  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 15:51 饭中淹

@Kevin Lynx
p的地址大于a。
所以a在栈顶,p在a下面。还有个push ebp,接下来就是返回地址。
正好三个。  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 15:52 小时候可靓了

@饭中淹
嗯,就是这样的。 不过如果你在DEBUG下看的话,会发现a不在栈顶。 DEBUG会多分一些  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 16:07 Kevin Lynx

@小时候可靓了
{
int a;
int b;
int c;
}
按我的理解,c应该在栈顶,而不是a。但是实际上跟踪你的代码来看,a确实在栈顶,在p后添加变量int i ,又有意外:
a : 0x0012ff74
&p:0x0012ff78
&i:0x0012ff70
  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。[未登录] 2010-05-06 16:15

@小时候可靓了
在我这里gcc和BCB都是无任何输出的。
随便在前面加个局部变量VC也同样无输出。
这不就是撞大运么....  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 16:16 Kevin Lynx

@坏
需要适当调整 3 这个偏移量

ps,当然也取决于编译器生成的指令。鉴于目前我也不明白为什么偏移是3,看起来LZ也无法给出详细的说明,这个3很有可能只是个巧合。

1、除了push ebp外可能还有压入其他寄存器的值(保存寄存器信息)
2、a理论上应该不在栈顶,就像我例子中的i,而p应该在栈顶
  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 16:33 小时候可靓了

@Kevin Lynx
栈顶是低地址~~  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 16:36 Kevin Lynx

@小时候可靓了
这个不需要你重申,我更不希望我来重申我的问题:
解释下这个:
int a[1];
int *p;
int i;

a : 0x0012ff74
&p:0x0012ff78
&i:0x0012ff70

注意p在中间 。  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 16:38 小时候可靓了

@Kevin Lynx
这个巧合是建立在刚刚那个代码上的。。这个+3,是人为构造的。 不管+几,我们就是想把他指向main的返回地址。

不过呢,在编译器环境条件众多的情况下, 如 @坏 所说,真是撞大运。

至于合理的解释,上面有兄弟给了答案了。。。  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 16:42 小时候可靓了

@Kevin Lynx
其实我贴出来,同样是想看到关于这个临时空间的解释。。你知道吗?在我的机器上是这样的。
0x0012ff60
0x0012ff54
0x0012ff48

如果我能一一解释清楚,我就不让大家讨论了。。。 嘿嘿  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 16:42 Kevin Lynx

@小时候可靓了
饭给的解释是我在群里跟他谈过的。
这个解释是我在看汇编的时候看到的:
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三者的相对地址和其定义顺序存在差别?(难道编译器对数组的处理要特殊点?)
  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 16:43 Kevin Lynx

@小时候可靓了
0x0012ff60 a
0x0012ff54 p
0x0012ff48 i ???
如果是这样的话,那这个才是正确的排列地址啊。诡异的是我那个情况。  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 16:44 小时候可靓了

@Kevin Lynx
可是,他们的差值很鬼异!  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 16:45 Kevin Lynx

@小时候可靓了
刚想补上这句,差距都是12.。。- -
我这边差距都是4,正常差距,但是顺序诡异。。。  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 16:59 Kevin Lynx

唉,淡定吧。这种情况让我头疼。  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 17:01 Kevin Lynx

汗。。- - 。。偶然间看到LZ 博客HGE一栏居然转载有我N久以前乱写的东西,真巧啊。。  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 17:03 小时候可靓了

@Kevin Lynx
是啊,以前在学校的时候,捣鼓了两天HGE。  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 19:00 zuhd

又是offset.....看了王爽汇编之后,你一定会发现这复杂的世界又是多么的和谐  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。 2010-05-06 19:30 小时候可靓了

@zuhd
王爽的我没看过耶!!! 汗!!!  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。[未登录] 2010-05-11 21:49 海边沫沫

高手好多哦。你们想得太复杂了。

我认为这一句
*p = ( int )test;
调用了test函数,只不过test函数没有返回值,且p指向非法内存,所以给*P赋值就会犯错,于是程序就会挂掉。

楼主说的特性是不是test和test()是一样的啊?  回复  更多评论   

# re: 讨论会:高手们都来看看,这个如何解释。[未登录] 2010-05-11 22:02 海边沫沫

我发现我上面的结论错了。刚才把代码输入到VS2008,调试了一下。发现:
*p=(int)test;确实只是把test的地址放到了*p里面,而不是调用test();

而在return的时候调用了test,然后出错。

说明*p这个非法内存访问是真的改变了main函数返回后执行的下一条指令的地址。  回复  更多评论   


只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理