作者:lyc125@sina.com
[前言: ]
[ 这篇文章主要是基于w00w00发表的: ]
[w00w00 on Heap Overflows]
[By: Matt Conover & w00w00 Security Team ]
[-----------------------------------------------------------------------]
[Copyright (C) January 1999, Matt Conover & w00w00 Security Development]
[ 也补充了一些程序和自己的想法.]
[ 非常感谢Matt Conover给予的热情帮助. ]
[ (Thank Matt for his great work and help) ]
[ 你可以从下面的地址获取原文:]
[ http://http://www.w00w00.org/articles.html]
[ 由于时间较紧,疏漏之处难免,任何意见和建议请发给warning3@hotmail.com ]
虽然基于Heap(堆)/BSS的溢出现在是相当普遍的,但并没有多少介绍它的资料。
本文将帮你理解什么是Heap溢出,也介绍了几种常用的攻击方法,同时给出了一些可
能的解决方案。阅读本文,您需要了解一些汇编,C语言以及堆栈溢出的基本知识。
一.为什么Heap/BSS溢出很重要?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
堆栈溢出的问题已经广为人知,越来越多的操作系统商家增加了不可执行堆栈的补
丁,一些个人也提供了自己的补丁,象著名的Solar Designer提供的针对Linux的不可
执行堆栈的kernel patch(目前已经推出了用于2.2.13内核的patch),也有一些人开发
了一些编译器来防止堆栈溢出,象Crispin Cowan等开发的StackGuard等等。这些方法
都一定程度上可以减少由堆栈溢出导致的安全问题,但是并却不能防止Heap/BSS的溢出。
在大多数的操作系统中,Heap和BSS段都是可写可执行的。这就使得Heap/BSS的溢出成
为可能。
大部分的基于heap的溢出都是不依赖于系统和硬件结构的,这将在后面进一步介绍。
二.一些概念
~~~~~~~~~~~
一个可执行的文件(比如常见的ELF--Executable and Linking
Format格式的可执行
文件)通常包含多个段,比如:PLT(过程连接表),GOT(全局偏移表),init(包含在初始化
时执行的指令),fini(包含程序终止时要执行的指令),以及ctors和dtors(包含一些全
局构造指令和析构指令)
所谓HEAP,就是由应用程序动态分配的内存区。在这里,"由应用程序"来分配是值得特别注
意的,因为在一个好的操作系统中,大部分的内存区实际上是在内核一级被动态分配的,而
Heap段则是由应用程序来分配的。它在编译的时候被初始化。
BSS段包含未被初始化的数据,在程序运行的时候才被分配。在被写入数据前,它始终保持
全零(至少从应用程序的角度看是这样的)
在大部分的系统中,Heap段是向上增长的(向高址方向增长)。因此,当我们说"X在Y的
下面"时,就是指"X的地址低于Y的地址"。
注意:下面提到的"基于heap的溢出"既包含HEAP段的溢出,也包含BSS段的溢出。
三.Heap/BSS溢出攻击
在这一部分中我们将介绍几种不同的利用Heap/BSS溢出的方法。大部分的例子都是针对
x86
Unix系统的。做一些适当的改变,也可以用于DOS和Windows系统。我们也介绍了几种专
门针对DOS/Windows的攻击方法。
注意:
在本文中,为了简单起见,我们使用了精确的偏移量。偏移量必须与实际的值相等,攻
击程序才能工作。当然你也可以象通常的堆栈攻击方法那样,通过提供多个返回地址及插入
空指令等方法以增加成功的机率。
下面的这个例子是给那些不熟悉Heap溢出的人看的,我会做一些简单的解释:
-----------------------------------------------------------------------------
/* 演示在heap段(已初始化的数据)发生的动态缓冲区溢出*/
#include
#include
#include
#include
#define BUFSIZE 16
#define OVERSIZE 8 /* 我们将覆盖buf2的前OVERSIZE个字节 */
int main()
{
u_long diff;
char *buf1 = (char *)malloc(BUFSIZE), *buf2 = (char *)malloc(BUFSIZE);
diff = (u_long)buf2 - (u_long)buf1;
printf("buf1 = %p, buf2 = %p, diff = 0x%x (%d)bytes\n", buf1, buf2,
diff, diff);
memset(buf2, 'A', BUFSIZE-1), buf2[BUFSIZE-1] = '\0';/*
将buf2用'A'填充 */
printf("before overflow: buf2 = %s\n", buf2);
memset(buf1, 'B', (u_int)(diff + OVERSIZE)); /*
用diff+OVERSIZE个'B'填充buf1 */
printf("after overflow: buf2 = %s\n", buf2);
return 0;
}
-----------------------------------------------------------------------------
当我们运行它后,得到下面的结果:
[warning3@testserver basic]$ ./heap1 8
buf1 = 0x8049858, buf2 = 0x8049870, diff = 0x18 (24)bytes
before overflow: buf2 = AAAAAAAAAAAAAAA
after overflow: buf2 = BBBBBBBBAAAAAAA
我们看到buf2的前8个字节被覆盖了。这是因为往buf1中填写的数据超出了它的边界进入了
buf2的范围。由于buf2的数据仍然在有效的heap区内,程序仍然可以正常结束。另外我们
可以注意到,虽然buf1和buf2是相继分配的,但他们并不是紧挨着的,而是有8个字节的间
距,这个间距可能随不同的系统环境而不同。
buf1 间距buf2
覆盖前:[xxxxxxxxxxxxxxxx][xxxxxxxx]低址 -----------------------------------高址覆盖后:[BBBBBBBBBBBBBBBB][BBBBBBBB][BBBBBBBBAAAAAAA]注意:一个阻止heap溢出的可能的方法就是在heap段的所有变量之间放一个"canary"值(就象StackGuard中所做的那样),若这个值在执行中被改变,就认为发生了溢出。为了解释BSS段的溢出,我们来看下面这个例子:-----------------------------------------------------------------------------/* 演示在BSS段(未被初始化的数据)的静态缓冲区溢出 */#include#include#include#include#include#define ERROR -1#define BUFSIZE 16int main(int argc, char **argv){u_long diff;int oversize;static char buf1[BUFSIZE], buf2[BUFSIZE];if (argc{fprintf(stderr, "Usage: %s\n", argv[0]);fprintf(stderr, "[Will overflow static buffer by]\n");exit(ERROR);}diff = (u_long)buf2 - (u_long)buf1;printf("buf1 = %p, buf2 = %p, diff = 0x%x (%d) bytes\n\n",buf1, buf2, diff, diff);memset(buf2, 'A', BUFSIZE - 1), memset(buf1, 'B', BUFSIZE - 1);buf1[BUFSIZE - 1] = '\0', buf2[BUFSIZE - 1] = '\0';printf("before overflow: buf1 = %s, buf2 = %s\n", buf1, buf2);oversize = diff + atoi(argv[1]);memset(buf1, 'B', oversize);buf1[BUFSIZE - 1] = '\0', buf2[BUFSIZE - 1] = '\0';printf("after overflow: buf1 = %s, buf2 = %s\n\n", buf1, buf2);return 0;}-----------------------------------------------------------------------------当我们运行它后,得到下面的结果:[warning3@testserver basic]$ ./heap2 8buf1 = 0x8049874, buf2 = 0x8049884, diff = 0x10 (16) bytesbefore overflow: buf1 = BBBBBBBBBBBBBBB, buf2 = AAAAAAAAAAAAAAAafter overflow: buf1 = BBBBBBBBBBBBBBB, buf2 = BBBBBBBBAAAAAAA和heap溢出类似,buf2的前8个字节也被覆盖了。我们也可以注意到,buf1和buf2是紧挨着的,这意味着我们可以不用猜测buf1和buf2之间的间距.buf1buf2覆盖前:[BBBBBBBBBBBBBBBB]低址 ----------------------高址覆盖后:[BBBBBBBBBBBBBBBB][BBBBBBBBAAAAAAA]从上面两个简单的例子,我们可以应该已经了解Heap/BSS溢出的基本方式了。我们能用它来覆盖一个文件名,口令或者是保存的uid等等...下面这个例子演示了一个指针是如何被覆盖的:-----------------------------------------------------------------------------/* 演示在BSS段(未被初始化的数据)中的静态指针溢出 */#include#include#include