目錄
堆溢出點
利用步驟
創建第一個house,修改top_chunk的size
創建第二個house,觸發sysmalloc中的_int_free
創建第三個house,泄露libc和heap的地址
創建第四個house,觸發異常
一點疑惑
參考資料
堆溢出點
圖1 堆溢出點
edit函數中沒有對那么長度進行校驗。
利用步驟
創建第一個house,修改top_chunk的size
top_chunk的size也不是隨意更改的,因為在sysmalloc中對這個值還要做校驗
assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)); /* Precondition: not enough current space to satisfy nb request */ assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
所以要滿足:
-
- 大於MINSIZE(0X10)
- 小於所需的大小 + MINSIZE
- prev inuse位設置為1
- old_top + oldsize的值是頁對齊的
創建第二個house,觸發sysmalloc中的_int_free
如果要觸發sysmalloc中_int_free,那么本次申請的堆大小也不能超過mp_.mmap_threshold,因為代碼中也會根據請求值來做出不同的處理。
if (av == NULL || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max)))
如果請求的堆塊大小nb大於等於mp_.mmap_threshold就可能走mmap分支,而不是擴展原來heap的大小了。
觸發_int_free后,top_chunk就被釋放到unsortbin中了。以下將它稱作old_top。之所以要這樣操作,是因為程序本身的限制,讓我們不能分配到想要的內存區。
本身的限制:
-
- 一次操作的對象只能是最近一次創建的house指針
- 沒有釋放操作
通過這種方式,在空閑區有了堆塊,也就存在了鏈表指針,通過一定的方式就拿到想要的數據了。
創建第三個house,泄露libc和heap的地址
本次創建house分配的堆塊從unsortbin中分配,指定的大小要小於old_top,系統會將old_top切分成兩塊。
在把old_top從unsortbin鏈中取下后,會將其插入相應的largebin中
/* remove from unsorted list */ unsorted_chunks (av)->bk = bck;//將old_top從unsortbin中取下 bck->fd = unsorted_chunks (av); … victim_index = largebin_index (size);//計算old_top大小在largebin那個層次 bck = bin_at (av, victim_index); fwd = bck->fd; … victim->fd_nextsize = victim->bk_nextsize = victim;//將old_top的fd_nextsize和bk_nextsize都指向old_top本身 … mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//將old_top加入largebin鏈表中 bck->fd = victim;
在后面分割堆塊后,保存有指針信息的堆塊會被分配給用戶,而且這些信息malloc不會擦除。
圖2 old_top加入largebin鏈表中
剩下的堆塊(下文依然稱之為為old_top)又被加入到unsortbin中了。
然后輸入8個字節,調用see函數,那么bk處保存的指針信息就得到了。這個就是unsortbin的地址,這個地址處於libc中,減掉一個偏移就是libc的起始地址了。
重新輸入24個字節,調用see函數,那么bk_nextsize處保存的指針信息就得到了。這個是本次分割前的old_top塊起始地址。減掉一個偏移就是heap的起始地址了。
為第四步觸發異常布局內存。
本題的利用思路,是通過修改IO_list_all指針來控制異常處理的流程到我們指定的函數。如何觸發異常,第四步會說。
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) { bck = victim->bk; … unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);
IO_list_all的地址需要拿到libc.so后才能確定。通過覆蓋此時的old_top的size域和bk指針,來重寫IO_list_all。
將bk指針覆蓋為&IO_list_all -0x10,因此IO_list_all被重寫為unsortbin-0x10。
將size域設置為0x60。為什么是0x60而不是70、80呢?
先來看看,設置為0x60后會給后續的malloc造成什么影響。
unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av); … if (in_smallbin_range (size)) { victim_index = smallbin_index (size);// victim_index=6 bck = bin_at (av, victim_index);//bck=&av->bins[10]-0x10 fwd = bck->fd; } … mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim;//old_top被加入av->bins[10]的鏈表中了。 bck->fd = victim;
0x60屬於smallbin的范圍了,所以此時的old_top被加入到smallbin[4]的鏈表中。又為何要加入到smallbin[4]中呢?此時IO_list_all=&unsortbin-0x10,距離smallbin[4]的偏移是0x60,再來看看IO_FILE的結構體
struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; …
可以看到,IO_FILE偏移0x60的字段是struct _IO_marker *_markers,偏移0x68的字段是struct _IO_FILE *_chain。而這兩個的值恰恰是old_top的起始地址。
原來改為0x60是為了將old_top加入smallbin[4],而smallbin[4]的fd和bk指針恰好對應於IO_FILE結構體中的_markers和_chain字段。這個時候,算是明白參考文章所說,無法控制main_arena中的數據,但是通過chain鏈,將控制轉移到我們到我們能控制的地方。
圖3 IO_list_all被重寫后
那如何使用chain字段呢?
while (fp != NULL) { … fp = fp->_chain;
當fp指向old_top的時候,那一切都好說了。為了使用vtable,我們還要過一段校驗代碼。
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->_mode > 0和fp->_wide_data->_IO_write_ptr> fp->_wide_data->_IO_write_base必須成立
- 我們將vtable的值改寫成我們構造的vtable起始地址
- 將_wide_data字段改寫成IO_FILE中字段_IO_read_ptr的地址 //此處錯誤,在參考的利用腳本中並不是改成_IO_read_ptr的地址。而是vtable前0x28個字節,這些字節恰好能滿足fp->_wide_data->_IO_write_ptr> fp->_wide_data->_IO_write_base。當然,也可以改成IO_FILE中字段_IO_read_ptr的地址,但是在payload中需要額外構造一些數據來滿足條件了
而傳遞給over函數的參數是fp,fp指向old_top,起始處被寫為”/bin/sh\0x00”
創建第四個house,觸發異常
如上面第三步所述,old_top的size被改寫為0x60,本次分配的時候,會先從unsortbin中取下old_top,加入到smallbin[4],同時,unsortbin.bk也被改寫成了&IO_list_all-0x10,所以此時的victim->size=0那么不會通過校驗,進入malloc_printerr,觸發異常。
一點疑惑
關於前面調用_int_free將old_top釋放到unsortbin我是一筆帶過。但是這個地方有一點問題。
old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK; set_head (old_top, old_size | PREV_INUSE); chunk_at_offset (old_top, old_size)->size = (2 * SIZE_SZ) | PREV_INUSE; chunk_at_offset (old_top, old_size + 2 * SIZE_SZ)->size = (2 * SIZE_SZ) | PREV_INUSE; if (old_size >= MINSIZE){ _int_free (av, old_top, 1);
old_top被釋放之前,用最后的0x20個字節構造了兩個chunk的header信息。size都設置為2 * SIZE_SZ。但是就是這里有問題。
nextchunk = chunk_at_offset(p, size); nextsize = chunksize(nextchunk); if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0) || __builtin_expect (nextsize >= av->system_mem, 0)) { errstr = "free(): invalid next size (normal)"; goto errout;
這個校驗會不通過。但是事實卻是通過了。很困惑。不知道自己哪點理解出現了偏差。
近來才想起來還有這個問題沒處理,其實代碼已經告訴我答案了。
chunk_at_offset (old_top, old_size)->size = (2 * SIZE_SZ) | PREV_INUSE;
此時size設置了prev_inuse位,自然在后面的校驗中大於2 * SIZE_SZ
__builtin_expect (nextchunk->size <= 2 * SIZE_SZ
參考資料
[1] CTF Pwn之創造奇跡的Top Chunk
http://bobao.360.cn/ctf/detail/178.html
[2] HITCON CTF Qual 2016 - House of Orange Write up
http://4ngelboy.blogspot.jp/2016/10/hitcon-ctf-qual-2016-house-of-orange.html
[3] glibc-2.23源碼