Heap exploitation
ptmalloc2
- ptmalloc - glibc
- tcmalloc - google
- jemalloc
…
malloc
- 要用多少分配多少,提升記憶體分配效率以及避免記憶體空間的浪費
- void *ptr = malloc(size): 找到一塊>=size的free chunk,mark為in-use,如果找不到就從系統要memory(brk/mmap)
workflow of malloc
預設的mmap大約為128KB

- size >= 128kb
mmap跟kernel要memory
- size < 128kb
- 使用heap(
brk)跟kernel要memory
- 使用heap(
main arena
對 binary來說,heap 還沒開始用,因為還沒呼叫 malloc()。
但 kernel 早就把記憶體(heap) 分配好了,交給 glibc 管理,等 binary 要用時(第一次 malloc)才會從那塊記憶體分出來用。
簡單來說就是: 對 kernel 已經分配好了,但對 binary 還沒。

chunk
malloc拿到的那塊chunk是一個header + data
因為size alignment的機制heap會以16(0x10)個bytes為基準去對齊

Allocated chunk: malloc拿到的Free chunk: 釋放掉的chunk,未被重新使用Top chunk: 未被分配的
chunk header (metadata)
Allocated chunk
- pointer指在user data的地方,不是header
- 第一個8bytes,prev_size: 連續記憶體上一塊如果是free chunk,則記錄size,如果是allocated chunk則同時為他的data
- 第二個8bytes是size,代表這個chunk的大小,pointer沒有size的資訊,所以pointer就會指到header,就可以藉由這個pointer-8來知道這個chunk的size(包含flags)
- chunk size with 3 flags
- 每個flags佔一個bit
- 4個bit裡有一個沒有用到(保留位元)
- 第0個bit是P(PREV_INUSE)
- 如果這個bit是1,上一個chunk是allocated chunk,已經被分配出去正在使用中
- 如果是0,表示前一個chunk是free的,那這個chunk的開頭就有一個prev_size 欄位(所以才能合併)
- 第1個bit是M(IS_MMAPED)
- chunk是否是透過mmap出來的
- 第2個bit是N(NON_MAIN_ARENA)
- 這個chunk是不是不屬於main arena

- 這個chunk是不是不屬於main arena
Free chunk
- 如果把chunk free掉,user data就不會被用到
- user data可以存metadata
- chunk free掉以後,除了header,data的前16個bytes也被存了兩個metadata分別是
fd跟bkfd(forward) : 指向 bin 中下一個chunk(靠近 list 尾端)bk(back) : 指向 bin 中前一個chunk(靠近 list 頭端)
fd跟bk不是連續記憶體的前一塊跟後一塊,是linked list鍊上的

Top chunk
- 第一次malloc後,剩下的空間為top chunk,分配空間時視情況從top chunk切割分配
- free Top chunk連續記憶體上一塊chunk時,若不是fastbin則會與Top chink merge,top chunk PREV_INUSE恆為1

bin
- 回收free chunk的資料結構
- 主要依據size的大小,分為:
- fast bin
- small bin
- large bin
- unsorted bin
Fast bin
- size < 0x90 bytes
- bin中依據size劃分為,0x20,0x30,0x40…
- global_max_fast = 0x80
- Sinaly linked list,fd指向前一個,bk沒用到
- LIFO(Last in, First OUT)
- free時不會將下一塊chunk P flag設為0
Small bin
- Circular doubly linked list
- 依據size劃分為62個bin
- 0x20,0x30 ~ 0x3f0(1008)
- 0x20 ~ 0x80的大小與fast bin重疊,會根據機制放到fast bin or small bin
- FIFO(First in, First out)
- free掉時會將下一塊chunk P設為0
Large bin
- Circular doubly linked list(依據大小遞減排序)

- size >= 1024bytes(0x400)
- 63 bins
- 多了兩個metadata(header)
- fd_nextsize
- bk_nextsize
Unsorted bin
- Circular doubly linked list
- free的chunk size>fast bin時,不會直接放到對應的bin裡,會先丟到unsorted bin中
- malloc fast bin size大小時會先去fast bin list裡,若沒有則會至unsorted bin找,如找到一樣大小則回傳,若無但找到大小大於所需大小的chunk則切割回傳,剩下的部分會丟回unsorted bin,若都沒有則從top chunk切出來回傳
demo
demo.c:
1 |
|
先做malloc

現在fastbin裡都是空的

然後跳到free

這裡可以看見0x30都是空的是因為malloc(0x30)的關係,他分配了0x30的chunk

addr - 0x10是header的位址,可以看到prev_size是空的,因為上面的chunk沒有free掉也沒有data
size的部分是0x41,因為size是0x30 + 0x10的header再加上p flag(0x1)

這就是一個chunk,把這個chunk free掉可以發現傳進去的pointer是0x4052a0,而不是整個chunk的頭

demo.c:
1 |
|
ptr指向0x4052a0,p2指向0x4052e0

free掉ptr

free掉p2

第一個chunk的memory layout

第二個chunk的memory layout

Heap Overflow
- 在stack segment發生的overflow
- 和stack overfow的精神相似,stack overflow目標是掌控stack上可利用的資訊,如位於stack上的return address,控制rip
- 而heap overflow則是掌控heap中的利用目標,如果某個chunk分配來是一個object struct,透過heap overflow overwrite來進行偽造,或是控制chunk header,並結合glibc malloc free的記憶體管理機制,做到進一步的利用
UAF(USE AFTER FREE)
- free(ptr);
- free完pointer後未將ptr清空(ptr = NULL),稱為dangling pointer
- 意思是有一個pointer指著一塊已經被釋放的記憶體(dangerous)
- 根據不同的存取方法,有各種利用的方法,可以進一步去做後續exploit的利用
- 用來information leak,存取殘留的data
- Struct Type Confusion
- Double free就是因為存在dangling pointer所以造成free一塊已經釋放的chunk,一樣可透過一些技巧達到進一步的利用
UAF - information leak
- free兩個同size的fastbin,fd指向heap,如果存在UAF,將此chunk user data印出來或任何得知其值,透過印出fd來leak出heap address
- malloc一塊非fastbin size的chunk,free掉他使他被放入unsorted bin裡,或任何製造出unsorted bin的方法,unsorted bin的fd與bk會是一個libc address,直接UAF印出fd來leak出libc address,或是malloc拿回這塊chunk,印出殘留的fd等等
Fastbin attack
- fastbin檢查double free時,只會檢查現在linked list第一個chunk是否等於現在即將要free掉的chunk
- 若存在double free,可以透過free (A) free(B) free(A)的方式繞過限制
- 下次再malloc這個fastbin size時,會拿到chunk A,而同時chunk A依舊存在於free chunk linked list中,藉此寫入data時修改掉fd,接下來連續malloc兩次後,第三次則會回傳拿到我們偽造的address
Fastbin attack - constraints
- malloc 時會檢查chunk size是否正確
- 在目標address + 0x8 的地方偽造size
- 尋找附近存在正確size的目標位置
- 增加可行性的利用技巧
- address alignment weakness
- libc address hardcode
- 檢查 size 時是抓取4 bytes int
Hooks
- glibc 中有存在許多function hooks,在攻擊時若能達到arbitrary write或任意寫,hooks會是一個很好的寫入目標,來做到control flow。
- __malloc_hook
- __free_hook
- __realloc_hook
- 在執行該fcuntion時,發現該function hook有值,則當作function pointer 跳上去執行
- 結合fastbin attack拿到位於hooks附近位置的fake chunk,來overwrite hooks的值
one gadget
- 跳過去即執行execve(“/bin/sh”, argv[], envp[]),跳上去即開shell
- magic gadget
- 有一些前提(constraints)需要滿足
- 常搭配 hooks 來使用
Tcache
-
glibc >= 2.26
- Ubuntu 17.10之後出現
-
新的機制,提升performance

-
第一次malloc時,會先分tcache中的順序會與bin中相反
- 配一塊記憶體,存放tcache_perthread_struct,一個thread一個tcache_perthread_struct
-
根據size分為大小不同的tcache
-
smallbin大小範圍的chunk都會使用tcache

-
以fastbin來說,free時不會直接放到fastbin裡,而是放到對應的size的Tcache中,當滿七個時,再free才會放到fastbin裡
-
fastbin的fd是指向整個chunk的頭,也就是header,而tcache fd 則是指向user data
-
malloc 時優先從tcache取出,tcache為空才會從原本的bin裡開始找
-
若tcache為空,而原本的bin中有剛好大小的chunk時,會從bin中填補到tcache中填滿為止,再從tcache中取出,tcache中的順序會與bin中相反
- 對於fastbin來說,會先將bin中第一塊取出,才將後面做填補
-
為了提升效能,而降低安全性
- 沒有檢查double free
- malloc時沒檢查size是否合法
-
不需要偽造chunk,偽造size,就能拿到任意memory位置
-
進階玩法跟技巧
- 如透過漏洞掌握整個tcache_perthread_struct
Heap overlap
- 可能一開始無法直接性做到太多事情
- 透過各式偽造chunk size的方式,以及玩弄malloc free的流程,使得不同的chunk發生重疊的情形
- 假設chunk A的data與chunk B的不可寫部分重疊
- chunk B可能是一個struct有如char* data pointer,又或是function pointer,則可以透過chunk A來偽造,overwrite data pointer達到任意讀寫,overwrite function pointer則可以hijack control flow
- 可以更進一步偽造chunk header,偽造heap chunk,玩弄記憶體管理機制
- 舉個例子,只有一個byte的heap overflow,且該byte的值不可控只能是NULL byte 0x0
- off-by-one null byte overflow
- “The posioned NUL byte” - Google project Zero
- 某種程度上算很容易發生的漏洞,像是宣告size剛剛好的字元陣列,一些libc function操作會自動append null byte或自行邊界操作不當等等