麒麟子

~~

导航

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

统计

常用链接

留言簿(12)

随笔分类

随笔档案

Friends

WebSites

积分与排名

最新随笔

最新评论

阅读排行榜

评论排行榜

讨论会结贴

前几天发了一篇关于一个缓冲区溢出问题的讨论。原文地址

当然是饱受非意。有人说这是撞大运,有人说这是无聊。但是呢,从讨论中,我们发现了更多的问题。学到了更多的知识。 其实许多时候我们有必要“撞大运”,但是在撞大运出问题之后,一定要弄清楚事情的原因。 博友的回复已经充分说明了当时的问题。 但是提出了一个新问题:就是临时变量分配时的空间问题。
比如说有分连续分配了3个临时变量,却发现这3个临时变量的址址不是按变量大小连续。(如两个INT变量间相差是12,而非预期的4) 又或者后声明的变量地址却跑在了前头)。 

这也形成了许多我提出的讨论问题是撞大运的说法。 其实这个问题许多人都试过,能不能运行成功输出success也要看编译器版本和编译器环境。 
关于变量空间的问题,我想在 这篇文章 中你们能得到满意的答案。
并且,同样关于本文讨论的问题,我朋友的一个博文中也已经给出了分析,并且给出了返回地址被覆盖时,平衡堆栈的措施。 

我的目的在于让大家一起讨论,不管这算不算是无聊,我们总会有些收获。

下面是一些博友的回复,也可以跳转到 原文地址 查看更多

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

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

 

 

# 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:58 Kevin Lynx

这个可以从callret指令所做的事情来看,更涉及到函数调用在编译器以及目标机器指令问题。不过因为这里不存在虚拟机问题,都是x86,也就只针对callret而言:

不难想象在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 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 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

谁能给个解释:为什么api三者的相对地址和其定义顺序存在差别?

posted on 2010-05-09 19:38 麒麟子 阅读(1750) 评论(6)  编辑 收藏 引用 所属分类: Programming

评论

# re: 讨论会结贴 2010-05-09 22:44 ztz0223

其实,整个代码就是利用巧合把main函数的返回栈帧给改写了。

我把修改过后的汇编代码贴下,整个过程就是如下:

1 .file "call.c"
2 .section .rodata
3 .LC0:
4 .string "Success\n!"
5 .text
6 .globl test
7 .type test, @function
8 test:
9 pushl %ebp
10 movl %esp, %ebp
11 subl $24, %esp
12 movl $.LC0, %eax
13 movl %eax, (%esp)
14 call printf
15 movl $1, %eax
16 leave
17 ret
18 .size test, .-test
19 .globl main
20 .type main, @function
21 main:
22 pushl %ebp
23 movl %esp, %ebp
24 leal 4(%ebp), %eax 就在这里把保存的main的栈内容改写了
25 movl $test, (%eax)
26 movl $0, %eax
27 leave
28 ret
29 .size main, .-main
30 .ident "GCC: (GNU) 4.5.0"
31 .section .note.GNU-stack,"",@progbits
  回复  更多评论   

# re: 讨论会结贴 2010-05-09 22:54 小时候可靓了

@ztz0223
嗯,是这样的!!!  回复  更多评论   

# re: 讨论会结贴[未登录] 2010-05-10 09:14

其实我那天也测试了。mingw要+2.
首先是函数返回地址,然后是ebp。

~ ztz0223

个人觉得搞这个问题有点无聊。当然这是必备功。  回复  更多评论   

# re: 讨论会结贴[未登录] 2010-05-10 09:16

饭中淹提到的第三点正解。  回复  更多评论   

# re: 讨论会结贴 2010-05-10 09:47 小时候可靓了

其实大致的内容,知道的人很多。 某些细节问题,就不一定了。  回复  更多评论   

# re: 讨论会结贴 2010-05-15 08:16 梦羽苍穹

飘过
  回复  更多评论   


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