说明:该文章基本上是对Matt Conover所写一篇关于堆溢出问题的翻译,原文可通过如下链接获得
http://www.w00w00.org/articles.html,文中蓝色部分为本人的理解、对文中程序的调试中的修改和结果
就可执行文件而言,例如ELF,其文件内容大概可分为如下几个部分 :PLT(程序链接表)、GOT(全局偏移表)、finit(初始化时执行的指令),以及ctors和dtors
有应用分配的内存称为堆,这里"由应用"这个词很重要,因为在内和这个层次看,对于好的系统来说大部分的内存
事实上都是动态分配的。而对于堆来说其内存的分配是由应用请求来的。[ 个人理解:就内核而言,其在加载应用程序的时候,会为该应用程序分配数据空间、指令空间、栈空间等,这些空间的大小是固定的,在整个运行过程中都是基本不变的,而对则是应用在运行过程中,根据需要向内核申请内存,这就是叫动态的缘由吧]
堆是内存中由应用动态分配的区域,而数据段则是在编译的时候就已经初始化了的。BSS段包含未初始化数据,是在运行时分配的。在未重写前,它始终为零(或者从应用角度看是这样的).
注:在本文中当我们谈及堆的溢出时,我们指的是包括堆和数据/BSS段的溢出。
对于多数系统来说,堆向高地址生长。因此,当我们说X在Y的下面,我们指的是在内存中X为与Y的地地址处。
[我在VC++6.0上进行编译调试时,其堆是向下生长的,即先分配的在高地址,后分配的在低地址]
在本文中,我们给出了几种不同的方法来引发堆/BSS溢出。这些例子适用于UNIX系列的X86系统,其中大多说也同样适用于DOS和WINDOWS
第一例:
-----------------------------------------------------------------------------
/* demonstrates dynamic overflow in heap (initialized data) */
#include <stdio.h>
#include <stdlib.h>
// #include <unistd.h>
#include <string.h>
#define BUFSIZE 16
#define OVERSIZE 8 /* overflow buf2 by OVERSIZE bytes */
typedef unsigned long u_long;
typedef unsigned int u_int;
int main()
{
u_long diff;
char* buf2 = (char *)malloc(BUFSIZE);
char *buf1 = (char *)malloc(BUFSIZE);
// char *buf1 = (char *)malloc(BUFSIZE), *buf2 = (char *)malloc(BUFSIZE);
diff = (u_long)buf2 - (u_long)buf1;
printf("buf1 = %p, buf2 = %p, diff = 0x%x bytes/n", buf1, buf2, diff);
memset(buf2, 'A', BUFSIZE-1), buf2[BUFSIZE-1] = '/0';
printf("before overflow: buf2 = %s/n", buf2);
memset(buf1, 'B', (u_int)(diff + OVERSIZE));
printf("after overflow: buf2 = %s/n", buf2);
return 0;
}
-----------------------------------------------------------------------------
执行结果:
[root /w00w00/heap/examples/basic]# ./heap1 8
buf1 = 0x804e000, buf2 = 0x804eff0, diff = 0xff0 bytes
before overflow: buf2 = AAAAAAAAAAAAAAA
after overflow: buf2 = BBBBBBBBAAAAAAA
[蓝色部分为我在VC上调试时的修改,执行结果如下:
buf1 = 00431CF0, buf2 = 00431D30, diff = 0x40 bytes
before overflow: buf2 = AAAAAAAAAAAAAAA
after overflow: buf2 = BBBBBBBBAAAAAAA
Press any key to continue
]
该程序依然可以工作,因为虽然buf1覆盖了buf2的内容,但是因为
buf2对应的空间依然是有效空间,该程序不会崩溃。
注意:
一个对于堆溢出的可能的修正方式是在堆空间的所有变量间放置一个
“金丝雀”值,该值在程序执行过程中不变。[比如我们可以对空间的变量间始终
预留一个4字节空间,该空间的值为全零,那么如果在运行过程中,该空间的值
发生了变化,则意味着发生了堆溢出]
上面只是一个最基本的例子,但却是大多数堆溢出的基本型,我们可以使用它来
改写一个文件的名字,一个口令等。(待续)