verychen / 编程 / 你了解你所运行的程序的桢栈结构以及什么...

分享

   

你了解你所运行的程序的桢栈结构以及什么是缓冲区吗? - C/C++ - 课堂 - 话题 -...

2010-12-09  verychen

你了解你所运行的程序的桢栈结构以及什么是缓冲区吗?

4张博 2010-11-15 22:13


 

昨晚查找一些关于利用缓冲区溢出漏洞获得系统root方面的资料,在IBM Developerworks上看到一篇很好的文章:Linux下缓冲区溢出攻击的原理及对策
其中很详细的讲解了linux下关于缓冲区溢出的一些知识,其中有一段作为预备知识是关于linux下进程的地址空间的布局和堆栈帧相关资料,我仔细读了好几遍,才弄懂了其中的原理,现在把一些体会写出来。
注:以下内容部分和图片摘自”
Linux下缓冲区溢出攻击的原理及对策“原文。

之前我所了解的linux下进程的地址空间的布局的知识,是从APUE第2版的P430得来的,之后上网查了一些资料,大概弄了明白。一个linux进程分为几个部分(从一个进程的地址空间的低地址向高地址增长):
1.text段,就是存放代码,可读可执行不可写,也称为正文段,代码段。
2.data段,存放已初始化的全局变量和已初始化的static变量(不管是局部static变量还是全局static变量)
3.bss段,存放全局未初始化变量和未初始化的static变量(也是不区分局部还是全局static变量)

以上这3部分是确定的,也就是不同的程序,以上3部分的大小都各不相同,因程序而异,若未初始化的全局变量定义的多了,那么bss区就大点,反之则小点。

4.heap,也就是堆,堆在进程空间中是自低地址向高地址增长,你在程序中通过动态申请得到的内存空间(c中一般为malloc\free,c++中一般为new\delete),就是在堆中动态分配的。
5.stack,栈,程序中每个函数中的局部变量,都是存放在栈中,栈是自高地址向低地址增长的。起初,堆和栈之间有很大一段空间,然后随着,程序的运 行,堆不断向高地址增长,栈不断向高地址增长,这样,堆跟栈之间的空间总有一个最大界限,超过这个最大界限,就会出现堆跟栈重叠,就会出错,所以一般来 说,Linux下的进程都有其最大空间的。
6.再往上,也就是一个进程地址空间的顶部,存放了命令行参数和环境变量。

整个进程地址空间布局如下图所示:

程序运行开始,由系统为进程地址空间中的text\data\bss段进行映射,由系统的缺页异常处理程序按需将磁盘上程序文件中的真正代码、数据写入进程。此外,bss区域中的所有变量都会被清零。

通过上面的讲述,我们知道了程序的代码,全局的变量,static变量是怎么在进程空间中分配空间的,接下来讲一下局部变量是怎么分配空间,函数是怎么调用的。其实也就是讲解栈区的具体使用过程。
首先,我们要知道,栈中存放的是一个个被调函数所对应的堆栈帧,当函数fun1被调用,则fun1的堆栈帧入栈,fun1返回时,fun1的堆栈帧出栈。 什么是堆栈帧呢,堆栈帧其实就是保存被调函数返回时下一条执行指令的指针、主调函数的堆栈帧的指针、主调函数传递给被调函数的实参(如果有的话)、被调函 数的局部变量等信息的一个结构。

堆栈帧结构如图所示:

首先,我们要说明的是如何区分每个堆栈帧,或者说,如何知道我现在在使用哪个堆栈帧。和栈密切相关的有2个寄存器,一个是ebp,一个是esp,前 者可以叫作栈基址指针,后者可以叫栈顶指针。对于一个堆栈帧来说,ebp也叫堆栈帧指针,它永远指向这个堆栈帧的某个固定位置(见上图),所以可以根据 ebp来表示一个堆栈帧,可以通过对ebp的偏移加减,来在堆栈帧中来来回回的访问。esp则是随着push和pop而不断移动。因此根据esp来对堆栈 帧进行操作。
再来讲一下上图,一个堆栈帧的最顶部,是实参,然后是return address,这个值是由主调函数中的call命令在call调用时自动压入的,不需要我们关心,previous frame pointer,就是主调函数的堆栈帧指针,也就是主调函数的ebp值。ebp偏移为正的都是被调函数的局部变量。

好了,说了这么多,很枯涩,我们通过一个实例来讲下。

 
int function(int a, int b, int c) 
{

  char buffer[14];   int sum;
sum = a + b + c;
return sum;
}  
void main()
{
  int i;
 i = function(1,2,3);
}

程序很简单,不解释。
在Linux下,我们通过 gcc -S example1.c 来生成汇编文件,然后我们查看上面这个程序对应的汇编程序:

1    .file   "example1.c"
2 .version "01.01"
3 gcc2_compiled.:
4 .text
5 .align 4
6 .globl function
7 .type function,@function
8 function:
9   pushl %ebp           //ebp这时指向的还是上一个堆栈帧
10 movl %esp,%ebp   //上一步中已将ebp(上一个堆栈帧的ebp)压入保存,
11 subl $20,%esp //esp下移20个字节,就是申请20个字节的空间
12 movl 8(%ebp),%eax //ebp加8后,指向第1个实参,放入eax.
13 addl 12(%ebp),%eax //ebp+12后,指向第2个实参,与第一个实参相加.
14 movl 16(%ebp),%edx //第3个实参放入edx
15 addl %eax,%edx //就是3个实参相加,结果存入edx
16 movl %edx,-20(%ebp) //将结果放入刚才申请的sum中
17 movl -20(%ebp),%eax
18 jmp .L1
19 .align 4
20 .L1:
21 leave //leave和ret见下面的解析
22 ret
23 .Lfe1:
24 .size function,.Lfe1-function
25 .align 4
26 .globl main
27 .type main,@function
28 main:
29 pushl %ebp
30     movl %esp,%ebp
31 subl $4,%esp //申请4个字节(给局部变量i)
32 pushl $3 //压入实参3
33 pushl $2 //压入实参2
34 pushl $1 //压入实参1
35 call function
36 addl $12,%esp //加上12就释放了刚才压入的3个实参
37 movl %eax,%eax //function返回值在eax中
38 movl %eax,-4(%ebp) //返回值赋值给了刚才申请的空间(变量i)
39 .L2:
40 leave
41 ret
42 .Lfe2:
43 .size main,.Lfe2-main
44 .ident "GCC: (GNU) 2.7.2.3"

其中函数function的堆栈帧

注释:
1.function中,buffer是14个字节,sum是4个字节,照例应该是申请18个字节,但是第11行,程序申请了20个字节。这是时间效率和 空间效率之间的一种折衷,因为Intel i386是32位的处理器,其每次内存访问都必须是4字节对齐的,而高30位地址相同的4个字节就构成了一个机器字。因此,如果为了填补 buffer[14]留下的两个字节而将sum分配在两个不同的机器字中,那么每次访问sum就需要两次内存操作,这显然是无法接受的。这些都是跟编译器 相关的优化技术。

2.我们再来看一下在函数function中是如何将a、b、c的和赋给sum的。前面已经提过,在函数中访问实参和局部变量时都是以堆栈帧指针为 基址,再加上一个偏移,而Intel i386体系结构下的堆栈帧指针就是ebp,为了清楚起见,我们在图7中标出了堆栈帧中所有成分相对于堆栈帧指针ebp的偏移。这下图6中12至16的计 算就一目了然了,8(%ebp)、12(%ebp)、16(%ebp)和-20(%ebp)分别是实参a、b、c和局部变量sum的地址,几个简单的 add指令和mov指令执行后sum中便是a、b、c三者之和了。另外,在gcc编译生成的汇编程序中函数的返回结果是通过eax传递的,因此在图6中第 17行将sum的值拷贝到eax中。

3.我们再来看一下函数function执行完之后与其对应的堆栈帧是如何弹出堆栈的。图6中第21行的leave指令将堆栈帧指针 ebp拷贝到esp中,于是在堆栈帧中为局部变量buffer[14]和sum分配的空间就被释放了;除此之外,leave指令还有一个功能,就是从堆栈 中弹出一个机器字并将其存放到ebp中,这样ebp就被恢复为main函数的堆栈帧指针了。第22行的ret指令再次从堆栈中弹出一个机器字并将其存放到 指令指针eip中,这样控制就返回到了第36行main函数中的addl指令处。addl指令将栈顶指针esp加上12,于是当初调用函数 function之前压入堆栈的三个实参所占用的堆栈空间也被释放掉了。至此,函数function的堆栈帧就被完全销毁了。前面刚刚提到过,在gcc编 译生成的汇编程序中通过eax传递函数的返回结果,因此图6中第38行将函数function的返回结果保存在了main函数的局部变量i中.

over.本人也是新手,有任何不正确的理解希望能指教,谢谢

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多
    喜欢该文的人也喜欢 更多

    ×
    ×

    ¥.00

    微信或支付宝扫码支付:

    开通即同意《个图VIP服务协议》

    全部>>