Pwn-heap note 1
Author:堇姬
筆記連結
heap note 1:
https://hackmd.io/@naup96321/S1knTRjVR
heap note 2:
https://hackmd.io/@naup96321/ryDzdYbjA
版本問題解決
首先,在老舊版本ubuntu18.04、16.04,要把環境架起來很奇怪。
可以用這個docker image來用
https://hub.docker.com/r/roderickchan/debug_pwn_env/tags
1 | docker run -i -t --name ubuntu1804 2e99549a1323 bash |
進去先
1 | sudo apt install nano |
裡面有pwndbg+pwngdb很多東西可以用
另外這個docker內的glibc 2.27用的Ubuntu版本較新,所以已經有檢察tcache key機制了,所以我們用patchelf+glibc all in one
甚麼是heap?
- 用多少給多少,避免浪費
- 動態分配的記憶體(ex: malloc、new)
- 呼叫malloc或new之前是不會有heap segment的
1 | void *prt=malloc(size) |
ptmalloc2
tcmalloc
jemalloc
有哪些
作業系統 | 實作方法 |
---|---|
glibc | ptmalloc |
windows | winheap |
freebsd | jemalloc |
OSx | zone allocator |
根據大小分配
要求memory大小小於128k: 會跟heap pool要求適合大小的空間
<128分配
首先先透過sys_brk
分配一塊132kb heap segment(rw-),叫做arena
,由主執行緒分配的所以叫做main arena
每個thread都有一個Arena,每個Arena中有多個chunk,這些chunk用linked list串接起來,linked list的head稱為bin
- 之後需要申請空間
空間會從這132kb中的chunk先分配,不夠再呼叫brk()來增加空間 - 減少空間
呼叫s_brk()來減少空間
1 | struct malloc_state |
Chunk
glibc實作動態memory管理的一個資料結構
用malloc拿到的記憶體就是chunk
- allocate chunk(inuse)
- free chunk
- top chunk
大小固定為0x10的倍數(舉例假如malloc申請一塊0x19大小的,會拿到0x20)
chunk=chunk header 0x10 + user data
1 |
|
allocate chunk(inuse)
正在使用的chunk
- prev_size/data: 上一塊如果是free chunk則紀錄該chunk的size,否則同時是他的data
- P bit: 上一個chunk是否還在使用 -> 1
- M bit: chunk是否透過mmap出來的 -> 2
- N bit: 該chunk是否屬於main arena -> 4
N M P bit
這邊注意一下,這裡是使用bit,也就是用gdb看會同時顯示在最後一位
舉個例子
1 |
|
在新線程中,我malloc一塊0x40大小chunk,會發現bit變4,是該malloc不在main arena,所以+4
free chunk
- fd: 指向同一個bin中前一個chunk(linkkist)
- bk: 指向同一個bin中後一個chunk(linklist)
top chunk
在heap最頂端,第一次malloc完後,剩下的是top chunk,之後分配空間時會從top chunk切割
main arena
管理及實現process中的heap,在main process中的arena稱為main arena(存在於libc段)
他記錄了很多東西
- bins鏈表
- top chunk位址
source code
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L1655
1 | struct malloc_state |
bin
回收free chunk的資料結構
glibc有幾種處理freed chunk的方式依chunk大小、性質、何時free掉區分成
- Fast bin
- Unsorted bin
- Small bin
- Large bin
- Tcache(Glibc 2.26後新增)
Fast bin
- bin中依size分成0x20、0x30 … 0x80(小於global_max_fast)
- fd指向前一個、bk則未使用(singly linklist)
- Free時不會將下一塊 P bit 重設
Unsorted bin
- 若一個被free的chunk沒被放到Tcache或Fastbin時候,會放到這
- 鄰近上一塊chunk是free跟上一塊合併
- 鄰近下一塊是Top chunk跟Top chunk合併
- 鄰近下一塊不是Top chunk,檢查是不是看是不是free,是就合併,並加入Unsorted bin
希望給free掉的chunk再有一次機會被malloc
demo
source code
1 |
|
流程
先malloc六個chunk
第一個是tcache_perthread_struct(詳細請見後面Glibc 2.31機制)
接下來就分別是我們malloc的了
另外為了防止被合併掉,所以中間用malloc 0x20隔開,這樣他就會確實進入unsorted bin
再來開始看free,三個都free掉後長這樣,可以看出是個循環
large bin
https://xz.aliyun.com/t/12751?time__1311=GqGxu7G%3DD%3DitKGN4eeqBKGQKi%3DBrkOu03EbD#toc-0
glibc source code
首先分析一下large bin結構,發現large bin還多了 fd_nextsize、bk_nextsize,橫向+縱向的列表來管理free chunk
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L1048
1 | struct malloc_chunk { |
tcache
glibc 2.27引入的新機制,主要是為了優化heap的機制,引入了tcache_entry和tcache_perthread_struct(source code取glibc 2.31)
tcache_entry
1 | typedef struct tcache_entry |
fd指向data的地方
2.31引入key機制,檢查double free
tcache_perthread_struct
1 | typedef struct tcache_perthread_struct |
tcache usage
這邊在複習一次tcache流程
- 第一次malloc放入
tcache_perthread_struct
- 若chunk小於small bin size,進tcache前會先放入fastbin或unsorted bin
- 若要進tcache,先看對應大小的tcache有沒有滿,沒有就放入,滿的話放進fastbin或unsorted bin(另外tcache中的chunk不會合併)
- 當重新申請 chunk 且申請的大小符合 tcache 的範圍時,會先從 tcache 中取出 chunk,直到 tcache 為空。當 tcache 為空時,會從 bin 中查找。
- tcache 為空時,如果 fastbin、small bin、unsorted bin 中有符合大小的 chunk,會先將這些 bin 中的 chunk 放入 tcache,直到填滿,之後再從 tcache 中取出。
malloc一塊tcache上的chunk
1 | if (tc_idx < mp_.tcache_bins && tcache && tcache->entries[tc_idx] != NULL) |
往上追mp_
1 | static struct malloc_par mp_ = |
1 | define TCACHE_MAX_BINS 64 |
簡單來說判斷free的時候,tcache上的一些idx範圍跟tcache->entries存不存在
1 | /* Caller must ensure that we know tc_idx is valid and there's |
get的時候幾乎沒有檢查
libc_free
1 | void |
int_free
https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L4165
1 |
|
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L4153
1 | static void |
tcache_put
https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L2925
1 | static __always_inline void |
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L2917
1 | static __always_inline void |
malloc & free 流程
malloc
free
Glibc 2.26 以前malloc & free機制(2.23為例)
malloc(0x20)三個拿到了三塊0x30大小的記憶體
free掉ptr1,fast bins 0x30 bins指向ptr 1 header,並且ptr1 fd設為0
注意這邊ptr 1 被free掉後沒有重設P bit
free掉ptr2
0x30 bins -> ptr2 chunk header
ptr2 fd -> ptr1 chunk header
ptr1 fd -> 0
free掉ptr3
0x30 bins -> ptr3 chunk header
ptr3 fd -> ptr2 chunk header
ptr2 fd -> ptr1 chunk header
ptr1 fd -> 0
之後又malloc一次,會從bins裡面抓最後尾端的ptr3 free來用
source code分析
Glibc 2.26 以後malloc & free機制(2.31為例)
Tcache
- 於Glibc2.26新增的加速效率的機制
- 從0x20、0x30、0x40、…0x410
- 每個Tcache只能收7個chunk
- tcache_perthread_struct結構管理Tcache
- free掉不會清空P bit
count計算目前已經收的chunk數量
code
1 |
|
分析
備註:下列插圖fd沒有指到上一個chunk是因為我開2.35,所以fd會是怪怪的地方
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c
一開始未malloc前,heap還不存在,main arena未初始化
沒有heap段
當第一次malloc時,像kernel申請一大塊記憶體(Top chunk),並且從top chunk割空間給malloc申請的記憶體,並初始化main arena。
第一段0x290為Tcache(tcache_perthread_struct)
memset後會把0x20個A,寫到ptr1(觀察圖片那段0x41,那就是user data段)
觀察一下0x555555559290,左邊都是0(因為上一塊是Tcache,所以這塊是Tcace的data),右邊是size跟p bit,上一個chunk還在使用為1,大小為0x40,故顯示0x41
之後開始free掉ptr1
ptr1 那塊 fd指向 0x0,並且後面那塊被蓋成了奇怪的東西(要注意,這裡不是bk,而是tcache的key)
接下來開始看free掉ptr1
可以看到tcache的0x40 entry -> free掉的ptr1(跟fast bin不一樣,指向的不是header,是fd、bk那行)
可以先看到第二行,可以看到0x40 cnt為1
另外0x5555555590a0為0x40 entry -> 0x5555555592a0
另外你會看到bk變成了怪怪的一串數字,那是tcache key,用於保護,而ptr1 fd -> 0x0
free掉ptr2
cnt 0x40變2,tcache的0x40 entry -> 0x00005555555592e0(ptr2)
ptr2 的 fd會指向 ptr1
以此類推最後像這樣
malloc & calloc & reaclloc
malloc
https://tw.gitbook.net/c_standard_library/c_function_malloc.html
void *malloc(size_t size)
size_t size -> 大小
calloc
https://tw.gitbook.net/c_standard_library/c_function_calloc.html
void *calloc(size_t nitems, size_t size)
size_t nitems -> 元素數
size_t size -> 每個元素size
realloc
https://tw.gitbook.net/c_standard_library/c_function_realloc.html
void *realloc(void *ptr, size_t size)
void *ptr -> 曾經分配過的空間pointer
size_t size -> 分配大小
條件 | 等價 |
---|---|
ptr = NULL | malloc(size) |
size = 0 | free(*ptr) |
brk & sbrk
1 |
|
會去改變program break的位置
program break 是 data segments end point,也就是heap結束位置
heap overflow
將資料寫入heap時,沒有控制輸入長度,導致可以覆寫到其他heap位置
demo
1 |
|
分析
看一下parseheap你會看到malloc了兩塊,一塊0x30,一塊0x40
第一塊read了0x40,大於40(0x28),可以heap overflow
像這樣你就發現輸出的User已經變成了一堆a了
第二塊架構是
1 | typedef struct { |
1 | 0x5555555592c0: 0x0000000000000000 0x0000000000000041 (header) |
然後看到read,會把東西讀到第一塊chunk
要蓋掉User、privilege
1 | 0x555555559290: 0x0000000000000000 0x0000000000000031 |
script
1 | from pwn import * |
這樣就成功了
UAF(use after free)
我們把指標丟給free,會把指標指向的chunk free掉
如果free掉後,沒有清空pointer就可能會有問題(可以用來information leak)
又稱dangling pointer
後續繼續在這個chunk上做操做就是UAF
讀取pointer
1 |
|
舉這個程式為例,你就會發現當你已經free掉這三塊chunk後你在針對三個ptr去做讀取,可以讀到fd三個值
double free
可以free同一塊chunk兩次
會出現幾個狀況
Tcache or fast bin鍊上會出現兩次相同的chunk
那如果我再次malloc他呢?
那就會出現他記憶體給你了變成allocated chunk,但他還在鏈表,變成了既被free,又被allocate的狀況
hook
在malloc/free/realloc/calloc 進到分配相關演算法之前,若有設定hook function,會先進入hook
若可以寫入hook,那就可以就可以control執行流程
也可以寫入Onegadget來get shell
Glibc source code
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L3022
1 |
|
可以看到他在往下執行前先去read __malloc_hook
並執行他
demo
1 |
|
告訴你shell在哪裡(可以無視PIE了),然後給你printf(也給你libc base了,可以推算hook function在哪裡)
然後要你寫入一個adress跟value,並往那個adress寫入value
那如果你把malloc_hook位置改寫成shell,那你進malloc讀出來的東西就是shell的adress,然後他就會去執行shell
1 | from pwn import * |
fastbin dup(glibc 2.23)
可以double free讓chunk被free兩次
之後我malloc,此時chunk會同時是allocate和free chunk,這時候我可以寫入蓋掉fd,讓fd指向任意位置
這時候再次malloc會讓我拿到一塊我剛剛改寫fd指向的地方
這時候就可以任意寫入了
示意
1 | fastbin |
現在我們先free掉chunk 1、chunk 2
1 | fastbin->chunk2->chunk1->NULL |
再來再free一次chunk 1
1 | fastbin->chunk1->chunk2->chunk1->chunk2->... |
這時候malloc
會拿到chunk 1(然後fastbin退鏈改指chunk 2)
1 | fastbin->chunk2->chunk1->chunk2->... |
這時候對當前malloc到的那塊寫入fd,到一個的pwn_address
1 | fastbin->chunk2->chunk1->pwn_address->?? |
再來malloc拿到 chunk 2,fastbin退鏈(隨便寫啥都行)
1 | fastbin->chunk1->pwn_address->?? |
再malloc拿到chunk 1,fastbin退鏈指到惡意address(隨便寫啥都行)
1 | fastbin->pwn_address->?? |
再malloc拿到pwn_address,達成任意寫,fastbin->???(這裡須注意,在glibc 2.26以前,fd指向header,但開始寫會從user data,所以選擇address時要往前指0x10左右)
然後注意,你指向的位置的chunk size需要符合該fastbin 所屬size
安全機制
first
你free的下一塊鄰近chunk size是否正確
1 | if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0) |
second
若fastbin上的第一個free chunk,又被做了一次free chunk,就會偵測到然後crash
所以為甚麼前面是free 1->free2->free1
這樣fastbin上第一個free chunk就是free2,來bypass
https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3929
1 | mchunkptr old = *fb, old2; |
third
malloc 時候檢查拿到的chunk是否跟fastbin一樣
1 | if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) |
fourth
2.27加入tcache機制,但這版本拔掉了原本的檢查double free(鍊上第一個是否是現在free的),且尚未加入key之類的機制,利用起來比2.23簡單
demo
source code
1 |
|
分析
可以輸入1~5,分別對應
1 | void menu() |
create() -> 1
malloc一個chunk,並可以指定一個size
並且把東西存到這三個global variable
1 | char *g_ptrs[0x20]; |
get() -> 2
先確認該chunk存不存在,以及是否是allocated,你可以指定index,然後給你他的內容
set() -> 3
先確認該chunk存不存在,以及是否是allocated
指定index,並可以把內容寫進去chunk裡面
delete -> 4
輸入一個index,若該ptrs存在,則把它free掉,然後把used改成0
1 | if (g_ptrs[idx]) { |
觀察一下這裡,他是去找g_ptrs裡面有沒有然後再free掉,但很顯然的g_ptrs就算你free掉了,也不會把它清掉所以就可以double free
並且打這題目前要有一個先備知識,要怎麼leak libc,我們打算透過fastbin dup來改malloc_hook,那就必須要有libc base,這邊可以用unsorted bin來leak,因為當unsorted bin被free掉時候,fd、bk會指向一個libc address
像是這樣,我先malloc一個
然後free,就可以發現有了
接下來我透過在malloc一次一樣大小的來拿到同一塊,並且fd、bk沒有被清空,用get,就可以把它印出來了
拿offset
1 | >>> 0x72db424aab78-0x72db420e6000 |
目前這樣可以leak libc
1 | from pwn import * |
再來就是要寫malloc hook了,這邊先做一件事,我們先到malloc hook去看看
我們需要bypass一個安全機制,較chunk size要一樣
觀察一下會發現,他有很多0x7c之類的東西
如果能把它當header size(推到header最尾端)就可以bypass
推一下就可以把他推到後面了(malloc hook - 0x33)
所以我們應該跳到(malloc hook - 0x23)
跳到libc base + malloc hook offset - 0x23
bypass
接下來要做fastbin dup
1 | chunk 0 -> 0x440 |
free 1、2
1 | fastbin -> chunk 2 -> chunk 1 |
再free 1
1 | fastbin -> chunk 1 -> chunk 2 -> chunk 1 -> chunk 2 -> ... |
這邊再free 3
1 | fastbin -> chunk 3 -> chunk 1 -> chunk 2 -> chunk 1 -> chunk 2 -> ... |
這邊注意一點,我想把/bin/sh
寫進去到heap裡面,所以我要先leak heap的位置,這邊可以挑1 或 2,都沒差,反正到時候都用不到,我這邊挑chunk 1,因為我把chunk 3 free掉後fd指向chunk 1,所以我malloc回來get就可以拿到了
1 | from pwn import * |
現在fastbin chain
1 | fastbin -> chunk 1 -> chunk 2 -> chunk 1 -> chunk 2 -> ... |
先malloc 1(ptr index 6)
1 | fastbin -> chunk 2 -> chunk 1 -> chunk 2 -> ... |
把chunk 1 寫入 malloc_hook 位置
1 | fastbin -> chunk 2 -> chunk 1 -> malloc_hook |
現在
malloc三次,拿到了
1 | chunk 2(ptr index 7) |
把chunk 1寫入/bin/sh
,/bin/sh
位置就會在chunk 1 address + 0x10
那malloc hook現在到底在哪裡?
1 | header 0x10 |
所以你要先padding,0x13
個a在寫掉malloc hook成system
最後傳入/bin/sh
1 | malloc("/bin/sh") -> (*__malloc_hook)("/bin/sh") -> system("/bin/sh") |
script
1 | from pwn import * |
Tcache bin dup(Glibc 2.26~2.28)
基本上跟Fastbin dup很像,但換成了Tcache
Glibc 2.27沒有檢查鍊表上第一個是不是現在要free掉的chunk,所以可以直接連續free兩次chunk不會有問題
另外注意,chunk fd指的是data
如果是glibc 2.28後加入了key會被寫成Tcache相關東西
流程
1 | Chunk 1(malloc) |
free掉chunk 1
1 | Tcache -> Chunk 1 |
再free一次
1 | Tcache -> Chunk 1 -> Chunk 1 -> Chunk 1 -> ... |
malloc拿到chunk 1並改寫fd
1 | Tcache -> Chunk 1 -> Pwn_chunk |
malloc兩次就可以拿到你要寫的東西了
demo
1 |
|
分析
這邊踩了一個坑,就是我們使用的的docker Glibc雖然是2.27,但是2.27保護機制跟裸奔一樣,所以有patch上保護,在2.27-3Ubuntu1.3加入key機制,所以我這邊找了比較舊的libc來patch上
1 | LD: |
首先先觀察一下,會發現這題跟原本很像,主要有兩個不同
第一個是這,這裡有BOF跟partial overwrite
1 | char name[100]; |
第二個不同是,原本的ptr array變成了變數,一次只能操作當前最新malloc的chunk
首先我們先leak libc,蓋0x78個a+一個\n會蓋到stack上的libc前面,printf會把它印出來,之後offset用動態抓扣出來
我們可以看到double free完後他變成了一個指向自己的循環
1 | tcache bin -> chunk 1 -> chunk 1 -> ... |
目前腳本
1 | from pwn import * |
接下來malloc會拿到chunk 1
之後我們先找兩個東西的offset,free hook(0x3ebc30)跟system(0x4f440)
我們把malloc的fd改成free hook,如圖已經改寫成功,所以tcache bin變成
1 | tcache bin -> chunk 1 -> free hook |
create兩次拿到free hook,寫入system,之後直接malloc一塊,把記憶體寫成/bin/sh,這樣就會有一個free(ptr)
,ptr->/bin/sh
1 | free("/bin/sh") -> (*__free_hook)("/bin/sh") -> system("/bin/sh") |
script
1 | from time import * |
備註: 可以加個sleep,這樣就不會送太快導致一些問題
large bin(Glibc 2.23)
source code
glibc 2.31機制及保護
這版本引入了Tcache key的安全機制,在free之前會檢查key
__libc_malloc
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L3021
1 | void * |
__libc_free
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L3085
1 | void |
malloc-tcache 安全機制
無檢測
free-tcache 安全機制
glibc 2.31
free(): double free detected in tcache 2
tcache裡面的chunk,bk會放tcache key,在free的時候會去檢查
可以透過UAF蓋掉tcache key,或是把tcache打滿七個去打fastbin
glibc 2.32
free(): unaligned chunk detected in tcache 2
tcache key引入加密機制,會檢查解密結果是否正確
glibc 2.35
free(): too many chunks detected in tcache
遍歷所有tcache chunk並計算數量,最後與mp_.tcache_count比較,若小於則crash,防止對tcache->counts[tc_idx]竄改
malloc-fastbin
all
malloc(): memory corruption (fast)
glibc 2.32
malloc(): unaligned fastbin chunk detected 1/2/3
free-fastbin
free(): invalid next size (fast)
double free or corruption (fasttop)
invalid fastbin entry (free)
Unlink
corrupted size vs. prev_size
corrupted double-linked list
corrupted double-linked list (not small)
malloc-unsorted bin
ref
https://bestwing.me/Education_Heap_Exploit_glibc_2.31.html
Tcache dup(glibc 2.31)
demo
source code
1 |
|
分析
可以做這些
1 | void menu() |
這邊可以發現他有兩種create,malloc跟calloc,這裡有個重點,就是calloc拿到的chunk不會是從tcache裡面拿
剩下就是常規操作,可以拿到chunk內容、設定chunk內容、刪掉chunk
現在版本是glibc 2.31,所以說會有tcache key來防double free
我們可以透過把tcache塞滿,讓chunk進到fastbin來做操作,這樣就可以做fastbin dup
那我們先leak libc,這邊用unsorted bin,先malloc一個大chunk,然後delete在malloc,就可以讀出fd的libc
1 | from pwn import * |
再來刪掉7個chunk塞滿tcache bin
剩下的就會進到fastbin,來bypass key檢查,接下來就是fastbindup
申請兩塊chunk(chunk 1 -> idx 9、chunk 2 -> idx 10)
然後
delete chunk 1
delete chunk 2
delete chunk 1
1 | fastbin -> chunk 1 -> chunk 2 -> chunk 1 -> ... |
calloc拿到chunk 1(idx 11)
寫入malloc_hook
1 | fastbin -> chunk 2 -> chunk 1 -> malloc hook |
寫到這裡偽造chunk(現在的fastbin又變回了指向header)
之後在malloc_hook寫入one gadget,之後呼叫malloc就可以跳到onegadget了
get shell~
script
1 | from pwn import * |
另外解法
這邊嘗試用另外一種解法,就是把malloc hook改成system,然後把其中一個位置寫成/bin/sh傳入(另外還要leak libc、leak heap)
consolidate
共有三種合併方式
- forward consolidate
- backward consolidate
- malloc consolidate
若要將兩塊free chunk合併可以採用,調整一塊大小,並把另外一塊從鍊表中移掉
(backward consolidate)
接下來可以看到下面的unsafe unlink,來看攻擊手法
source code(Glibc 2.31)
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L4326
1 | /* consolidate backward */ |
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L1451
1 | /* Take a chunk off a bin list. */ |
how2heap(glibc 2.23 fastbindup consolidate)
1 |
|
tcache overlap
讓不同的chunk發生重疊的狀況
假設chunk A 的data跟chunk B不可寫部份發生重疊可能發生問題
source code(glibc 2.23)
https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L4001
這邊看看backward consolidate source code
1 | static void malloc_consolidate(mstate av) |
2.31後會多檢查prev size跟chunk size大小是否一樣,這邊則沒有
接下來一樣進入unlink
後面都一樣,詳情見下方unlink
how2heap(glibc 2.23)
1 | /* |
首先先malloc了三塊,然後memeset到next chunk size前面
1 | parseheap |
1 | pwndbg> x/80xg 0x9f2000 |
現在我們free掉chunk 2,他會進到unsorted bin
1 | last_remainder: 0x0 (size : 0x0) |
接下來假設有溢出問題,可以蓋掉chunk2 size
1 | pwndbg> x/80xg 0x9f2000 |
0x101變成0x181
1 | last_remainder: 0x0 (size : 0x0) |
這時候我們去malloc一個0x178大小的chunk
此時chunk 4和chunk 3大小重疊
可以覆蓋到其他chunk上
how2heap(glibc 2.23 -2)
1 | /* |
首先先malloc了五個chunk
1 | pwndbg> parseheap |
然後memset
1 | pwndbg> parseheap |
free掉chunk 4
1 | unsortbin: 0x2159bd0 (size : 0x3f0) |
接下來改寫chunk2的大小,讓他以為下個chunk是 4 (修改為chunk 2 + chunk 3大小 0x3f0+0x3f0=0x7e0)
1 | addr prev size status fd bk |
free掉chunk 2後會把chunk 3一起包進去
malloc 2000就可以任意寫chunk3了
Unsafe unlink(Glibc 2.31)
原理
先假設我們現在有兩個ptr,ptr1、ptr2(存在一個陣列)指向兩塊malloc的記憶體
1 | | | #ptr-0x18 |
1 | -------------------------- |
內容具體長這樣,兩塊0x430大小的chunk
1 | -------------------------- |
這邊有個越界寫1 byte的漏洞,我這樣寫
1 | --------------------------- <-ptr1 |
首先看到0x430,P bit是0他會以為上一塊是free chunk,想跟上一塊做合併
看下方source code可以看到他抓offset抓prev size抓到了0x420所以往前抓到了0x421那行我們做的那邊free chunk,開始consolidate
1 | /* consolidate backward */ |
https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L4326
進六步檢查
先檢查pre size,因為我們改寫所以相等 -> OK
1 | if (chunksize (p) != prev_size (next_chunk (p))) |
再來,這邊簡單來說就是讓我們做的fake chunk fd指向fd內容,bk指向bk內容
1 | mchunkptr fd = p->fd; |
所以變成
1 | --------------------------- |
1 | fake chunk fd -> | | #ptr-0x18 |
再來進檢查,fake chunk fd指向chunk的bk(就是ptr1)指向是否跟p(就是剛剛算的offset)指向位置相同
1 | if (__builtin_expect (fd->bk != p || bk->fd != p, 0)) |
這邊把陣列當作chunk的話
1 | |prev/data| #ptr-0x18 |
很顯然的p跟ptr1指向fake chunk跟p指向的位置相同 -> OK
再來是,fake chunk bk指向chunk的fd(就是ptr1)指向是否跟p(就是剛剛算的offset)指向位置相同
這邊把陣列當作chunk的話
1 | | | #ptr-0x18 |
如同上面,很顯然的p跟ptr1指向fake chunk跟p指向的位置相同 -> OK
這邊成功bypass保護機制
最後unlink
1 | fd->bk = bk; |
就是
1 | |prev/data| #ptr-0x18 <- fake chunk fd |
fake chunk fd指向的bk(ptr1)會指向fake chunk bk指向的位置(ptr1-0x10)
1 | | | #ptr-0x18 <- fake chunk fd |
fake chunk bk指向的fd(ptr1)會指向fake chunk fd指向的位置(ptr1-0x18)
Unlink結束,最後發現ptr1現在指向的位置是ptr1-0x18
通常我們的ptr1是可以任意寫他指向的位置的
所以可以寫ptr1-0x18,一路往下寫,把ptr1改寫成任意位置,來達到任意寫
1 | | aaa... | #ptr-0x18 |
ptr1->pwn addr
可以任意寫入pwn addr(可能是malloc hook之類的來RCE)
一些越界1 byte的例子
demo1
1 |
|
for循環寫入沒做好,多寫了一個byte
1 | pwndbg> x/30xg 0x555555559290 |
demo2
1 |
|
strlen()跟strcpy()不一致產生的問題,strlen在計算長度時並沒有把\x00
算進去,導致strcpy()進chunk1多寫一個\x00
1 | pwndbg> x/30xg 0x555555559290 |
這邊輸入24個a,發現下個chunk size被蓋成400,蓋掉P bit了
1 | pwndbg> x/30xg 0x555555559290 |
unsorted bin attack
原理
看到這段,在拿unsored bin的時候,會根據以下
1 | victim = unsorted_chunks (av)->bk |
加入一張圖方便理解
他會把main arena中的bk給 victim,也就是victim就是chunk 3
把chunk 3 給 chunk bck,也就是bck = chunk 2
之後把main arena的bk給chunk 2
bck 的fd 指回 main arena,完成unlink
可以從第二行下手
如果我們透過overflow偽造 chunk 3(victim) 的 bk
我們可以將main arena + 88/96 address 寫入到 bck 的 fd 指向的位置
how2heap
1 |
|
我們先malloc兩塊chunk
chunk 1 size 400
chunk 2 size 500
之後free chunk 1
chunk 1進入到unsorted bin中
目前bk會指回main arena
我們目標是把stack某位置改寫
我們UAF去修改掉unsorted bin中chunk 的bk 成stack - 0x10
malloc回來就可以寫main arena address 到stack上
1 | bck = victim->bk (stack - 0x10) |
book
https://github.com/limitedeternity/HeapLAB/blob/main/HeapLab%20-%20GLIBC%20Heap%20Exploitation.pdf
一些連結
basic
https://blog.csdn.net/songchuwang1868/article/details/89951543
Pwngdb
https://github.com/scwuaptx/Pwngdb
link
https://0x434b.dev/overview-of-glibc-heap-exploitation-techniques/#smallbin
https://www.openeuler.org/zh/blog/wangshuo/Glibc%20Malloc%20Principle/Glibc_Malloc_Principle.html
題目
https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/unlink/2016_zctf_note2
https://github.com/veritas501/hctf2018/blob/master/pwn-heapstorm_zero/heapstorm_zero.c
https://nocbtm.github.io/2020/02/28/off-by-null/#%E5%8F%A6%E4%B8%80%E7%A7%8D%E5%B8%83%E5%B1%80
https://github.com/CTFTraining/HuXiang_2019_pwn_HackNote/tree/master
https://makabaka-yyds.github.io/2022/05/09/glibc2-29%E4%BB%A5%E4%B8%8A%E7%9A%84off-by-null/
ref
https://bbs.kanxue.com/thread-257901.htm
https://nopnoping.github.io/off-by-one%E5%88%A9%E7%94%A8%E6%80%BB%E7%BB%93/
https://makabaka-yyds.github.io/2022/05/09/glibc2-29%E4%BB%A5%E4%B8%8A%E7%9A%84off-by-null/
https://github.com/kaiiiz/NTU-Computer-Security-2021-Fall/blob/main/Pwn/Pwn%20II/malloc_internal.c
https://blog.csdn.net/qq_41202237/article/details/113400567
https://github.com/limitedeternity/HeapLAB/tree/main?tab=readme-ov-file
https://ctf-wiki.org/en/pwn/linux/user-mode/heap/ptmalloc2/tcache-attack/#tcache-poisoning