PlaidDB [550]
程式行為及概述
如名稱所述是個 database 的 service ,可加入資料進 db,而每筆資料都配合一組 key,並以 binary tree 的方式去儲存,其中每個 node 都有個 row struct 大致上如下
struct row {
char *key
int size
char *content
row *left
row *right
row *parent
bool is_leaf
}
而他有的功能如下
- GET
- 分配放 key 的空間,輸入 key , 可獲得 db 的內容,最後再把剛分配的空間 free 掉
- PUT
- 一開使會先分配 0x38 byte 放置 row ,再分配 8 byte 放 key,再依據所輸入的 size 大小,分配相對應的空間給你 ,而在輸入 key 時會檢查所輸入 key 的空間夠不夠用,一旦不夠用就會重新 realloc 兩倍的空間給你
- DUMP
- 會將所有 key 的資訊 dump 出來
- DEL
- 分配放 key 的空間,輸入 key 之後,會先比對是否 key 是否有在 tree 中,若有,則將相對應的 key、content、row 及用來比對 key 依序 free 掉,*若無則返回 menu 選單(並沒有將剛剛用來比對的 key free 掉),這部分在後續排 heap 的階段頗好用 *
- EXIT
- 離開程式
漏洞
- NULL byte overflow
- 再所有輸入 key 的功能中一旦把 key 輸入完就會在結尾補上零,然而在輸入的 key 剛好是最大分配的空間時,並不會重新 realloc 而卻也會在最後補上
\x00
此時,造成了 overflow ,並蓋到了 malloc_chunk 中的 size 欄位
- 再所有輸入 key 的功能中一旦把 key 輸入完就會在結尾補上零,然而在輸入的 key 剛好是最大分配的空間時,並不會重新 realloc 而卻也會在最後補上
保護機制
- CANARY : ENABLED
- FORTIFY : ENABLED
- NX : ENABLED
- PIE : ENABLED
- RELRO : FULL
漏洞利用及思路
此題一開始想觸發 unlink,但發現難以利用,很難找到一個 pointer 指回自己,也沒有看出可以 leak memory 的地方,後來看到了 google project zero 及 Glibc Adventures: The Forgotten Chunks 的用法,才理解到這題主要在考的是讓 chunk overlap 的情況,其中大致上的概念如下
- 一開始先 malloc 三塊相鄰的 chunk
- 接著 free(B)
- Null byte overflow ,這時候 libc 會認為這塊 chunk 剩下 0x100 的空間,但 c chunk 的
prev_size
卻是紀錄還有 0x120 byte
malloc(D)
會先從剛加入 unsortbin 的 chunk 中切出 chunk
malloc(E)
一樣從未分配完的 chunk 在切出空間來給 user
free(D)
此時 D 也會被加入 unsortbin
free(C)
此時 free 會根據C 這塊 chunk 的prev_size
去找尋上一塊chunk 因此會認為 D 開始到 C 前都是同一塊 chunk 並認為這塊 chunk 有 0x120 byte 卻不知道中間已經有一塊已經分配出去的 chunk ,並將它們合併成 0x220 byte的 chunk
malloc(0x200)
再次 malloc 夠大的空間時,會將之前已經分配出去的 chunk 一併也取進來,但使用者可以任意寫入該區內容,也就是 E 這塊 chunk 可以任意被改動,這題剛好就是讓 plaiddb 的row struct
落在這一塊而被任意改動
利用上述的手法改道舊有的
row struct
其實就已經差不多了,可微調 size 大小,讓舊有的 key 也落入 overlap 的 chunk 中,並很巧妙的 free 掉時,讓 free 將 bin 的位置剛好填入舊有的 key 之中,並用DUMP
就可以 leak 出 libc 的 base ,接著將舊有的 row 夠造成下列的形式
payload = pack64(binsh)
payload += pack64(7)
payload += pack64(0)
payload += pack64(free_hook)
payload += pack64(free_hook-0x30)
payload += pack64(free_hook)
payload += pack64(system)
- 接著在 DELE 時 , 就會將 system 寫入 free_hook 的欄位中,當下次在 free 時等同於也執行了 system,不過當
DELE
動作快結束時就可以拿到 shell ,這邊其實只要稍微 trace 或是用 binary tree 刪除節點的概念就會知道他如何去寫入了,細節我就不多寫了
心得
- 很可惜的沒在兩天內解完,最後都排得差不多了,但時間還是差一點點,寫 explit 速度還是太慢,這題的關鍵主要在排 heap 的部分,因為 fastbin 並不會觸發 unlink 不容易達成上述描述的情況,必須想辦法利用 key 來製造 smallbin 的大小,至於排的過程就不詳細描述了,如果有時間的話大家可以去解解看慢慢體會一下 :-)