MacOS系統上的堆介紹及利用

分享一下之前總結的一些MacOS系統的堆介紹及利用思路。

MacOS下的堆介紹

MacOS高版本系統使用Magazine Allocator進行堆分配,低版本使用Scalable Allocator,詳細結構這裡不做介紹,它在分配時按照申請大小將堆分為三類tiny,small,large
其中tiny&small用一個叫做 **Quantum ( Q )**的單位管理

  • tiny (Q = 16) ( tiny < 1009B )
  • small (Q = 512) ( 1008B small < 127KB )
  • large ( 127KB large )

每個magazine有個cache區域可以用來快速分配釋放堆

堆的元數據(metadata)

MacOS的堆分配方式和其他系統不同,沒有采用Linked List方式的分配,堆的前後並沒有帶堆的元數據,而是將元數據存放在了其他地方,並且做了一系列措施方式防止堆溢出修改元數據。


每個進程包含3個區域,分別為tiny rack, small rack, large allocations

tiny racksmall racklarge allocationsmagazinemagazinemagazinemagazinemagazinemagazine……magazinemagazine

每個區域包含了多個活動可變的magazine區域
magazine中有n多個"Region"
這個叫"Region"的區域大小在tiny rack和small rack中是不同的,
“Region” in Tiny rack = 1MB
“Region” in Small rack = 8MB

"Region"中包含三樣東西,一個是以Q為單位的內存block, 還有個是負責將各個"Region"關聯起來的trailer另外一個就是記錄chunk信息的metadata

large allocations保存在cache中,直接記錄地址和大小,除非是分割嚴重,否則一般不會被unmmap

堆的釋放 - chunk本身的變化

tiny堆

tiny堆在釋放時,將該chunk掛在freelist上,這裡和Linux類似

比較有意思的一點是,tiny堆在釋放時,會在chunk上寫入元數據,我們值得關心的就是這一點

這裡有兩個pointer和Linux上chunk的頭極其相似,同樣的,它們的作用也一樣,在freelist上獲取chunk時將會用這個pointer來進行鏈表的操作,還有chunk在free時,會進行合併檢查,然後用這兩個pointer進行unlink操作。
***但是***這裡如果按照Linux的方式去攻擊堆時,就會發現這裡的checksum會阻止堆的元數據被溢出修改。後面會大致介紹這裡的checksum

關於tiny堆釋放時的需要注意的另外一個點:

small堆

small堆與tiny堆不同,釋放後會先移動到cache中,等到下一個small堆被free時,當前的才會被移動到freelist中

堆的釋放 - chunk元數據(metadata)的變化

mag_free_list

這裡便是要講上文提到的freelist,mag_free_list是個負責存放地址的列表,一共包含32個元素,各個元素處儲存著已經free的對應Q值的chunk地址,前31個分別是從1Q~31Q的chunk freelist,第32個存放比31Q還要大的chunk freelist。


當新的chunk被free時,將按照chunk的大小,存放在對應Q值的freelist上,並按照雙向鏈表設置好checksum(prev_pointer), checksum(next_pointer) {參照Linux的freelist}

mag_free_bit_map

這個則如名字所示,按位來標記Q(n)是否具有freelist

堆的釋放 - checksum

程序在運行時,都會隨機生成一個cookie,這個cookie會pointer進行下面的計算生成一個checksum, 然後將(checksum << 56 ) | (pointer >> 4)運算後將checksum保存在高位上,以便檢測堆的元數據是否被溢出破壞

magazine_t

這個則包含了上述介紹過的各種數據,比如chunk cache, 以及mag_free_bit_map, mag_free_list, 以及最後一個被使用的region, 以及所有region的鏈表

堆的申請

整個申請流程是首先從cache中尋找是否有對應的堆,如果沒有接著從freelist中尋找,沒找到再從region中去申請

題目攻擊思路

首先題目保護全開,具有PIE,再分析程序流程。
程序整個流程就是以下面的結構體進行堆數據操作。

  • 溢出

發現在update()更新mem時,可以隨意設定當前mem->nameSize的大小,導致修改name時,可溢出修改name後的下一塊mem的數據。
但是修改的size發現做了限制,導致數據溢出最大隻能修改到mem結構的前三項
mem->StyleTableIndex
mem->ShapeTableIndex
mem->Time

MacOS系統上的堆介紹及利用

1.png988×82 14.3 KB

  • leak

在show()顯示時,可以用StyleTable[offset/8]來leak數據

因為有PIE的存在,程序每次運行堆棧地址都會隨機,所以整個利用思路就是先leak libsystem_c.dylib的地址,接著利用heap操作產生的漏洞去將包含的execv(’/bin/sh’)代碼運行地址寫入可以劫持到程序流程的地方。

利用MacOS堆的特性leak libsystem_c.dylib

查看程序運行時的vmmap,可以看到程序下方有個Malloc metadata的region,這裡開頭存放的就是DefaultZone

MacOS系統上的堆介紹及利用

2.png1077×181 12.5 KB


我們可以看下libmalloc的源代碼

值得我們仔細關注的是這裡的
struct malloc_introspection_t * MALLOC_INTROSPECT_TBL_PTR(introspect);

繼續查看源代碼

用之前介紹過的堆資料,可以知道
所以DefaultZone->introspect->enumerator這裡儲存了enumerator對應的函數
szone_ptr_in_use_enumerator的地址

libsystem_malloc.dylib地址

所以
libsystem_malloc.dylib的地址 = leak出的
szone_ptr_in_use_enumerator地址 - sznoe偏移量(0x0000000000013D68)

libsystem_c.dylib地址

這裡有個很有趣的現象,就是MacOS的PIE會保證程序每次運行時都會隨機堆棧以及加載地址,但是引入的動態庫地址不會產生變化,似乎只會在開機時變化。
所以可以看下vmmap,確定下libsystem_c.dylib與libsystem_malloc.dylib加載地址,得到偏移量。
libsystem_c.dylib = libsystem_malloc.dylib - 偏移量(0x161000)

OneGadget RCE

分析了libsystem_c.dylib,發現了與Linux libc中同樣的execv(’/bin/sh’)代碼片段


onegadget rce = libsystem_c.dylib + 0x0000000000025D94

MacOS系統上的堆介紹及利用

3.png1520×200 46.1 KB

劫持程序流 - 前置

這裡利用MachO的Lazy Bind機制,複寫libsystem_c.dylib的la_symbol_ptr表中的函數存放地址(不寫原程序的原因是無法leak原程序加載地址)
查看一週發現最優的選擇為exit_la_symbol_ptr
我們可以在add()函數階段輸入不被認可的Size,可讓程序執行exit()進而執行我們寫入的地址。

這裡發現libsystem_c.dylib的TEXT和DATA region地址相差較大,不像原程序緊挨在一起,所以這裡還需要再leak一次libsystem_c.dylibd的DATA region地址。

libsystem_c.dylib DATA

分析原程序時發現在.got內有個FILE **__stdinp_ptr
可以看到開頭的_p指向了某塊內存的地址,這樣就可以利用這個來完成leak DATA地址,這裡buffer與DATA起始地址的偏移量分析下就可以得到

libsystem_c_DATA = libsystem_c_stdinptr - 0x4110

劫持程序流 - 核心

根據前面堆的申請介紹,我們可以構造一些tiny堆,讓再次申請堆時保證從freelist上獲取,然後完成
tiny_malloc_from_free_list(),使內部的unlink操作完成next->previous = ptr->previous任意數據寫任意地址的操作

但是這裡有個問題,就是在unlink前,會有個unchecksum的檢查,因為程序每次運行時,都會對當前的zone生成隨機的cookie,導致這裡無法繞過去

但萬幸的是MacOS在對生成的cookie和pointer進行checksum後,只使用了4個有效位來保存checksum值,所以可以設定個checksum進行爆破,讓程序生成的cookie在與我們的pointer在checksum後恰好等於我們自己設定的值。

value = p64(((libsystem_c_exit_la_symbol_ptr >> 4) | int(checksum, 16)))

getshell

下面是完整的exp

MacOS系統上的堆介紹及利用

4.png2980×1832 823 KB


分享到:


相關文章: