詳談Heap Exploit

前言

berming
berming
Jan 25, 2018 · 14 min read

各位好久不見,今天想來深入談談Heap Exploit,且會用CTF題目做為例子,會想寫這篇是因為前陣子在寫pwnable.tw的題目Secret Of My Heart,雖然整體概念不難,但heap這種東西就是有夠複雜,以前對於heap的知識都是很馬虎的了解,真正實戰還是吃了不少苦頭,再加上一開始方向錯誤,到最後足足搞了三天才解出來,之後決定更加系統化的深入學習heap exploit,並查了超級大量的資料,萃取成這篇文章作為精華。


初探-運氣流RCE以及神奇的check_action

關於heap exploit常見體位在這裡就不多做說明了,這個網站把常見的招數都介紹了一遍,有興趣可以到這邊了解,現在想來談談這些攻擊在實戰中的細節、以及一些變化。

這個題目存在著off-by-one NULL byte漏洞,令人可以迅速聯想到做shrinking chunk產生overlap,但有兩個問題馬上浮現出來

1. How to defeat ASLR (leak libc) ?

2. How to do arbitrary write ?

第一個問題比較簡單,由於free small chunk會記錄著fd、bk來維持small bin,而small bin的起頭在main_arena中(存在libc .data段),所以只要能leak這兩個pointer就能得知libc位址,一開始直覺的想到做兩個overlap chunk,free一個在printf另一個,但因為必須藉由printf(“%s”)做leak,所以兩塊overlap chunk必須起始位址一樣,否則當你printf另一個chunk時就會遇到含有fd、bk的chunk的prev_size、size位(有NULL byte)。

第二個問題在得到overlap chunk後馬上能想到fast bin attack,但最麻煩的就在要繞過煩人的size檢查 -malloc(): memory corruption (fast),再加上程式有開RELRO,造成能寫入的位址非常侷限,一開始我想到三個地方

  1. __free_hook
  2. __malloc_hook
  3. tls_dtor_list

__free_hook一定是第一人選,因為他幫我們準備好了參數(一個heap上的位址,可以拿來放/bin/sh),不像malloc參數是一個int,但不幸的是實際看了一下在__free_hook記憶體位址前面全部都是0,根本沒辦法拿來偽造size。

而往tls_dtor_list寫是我一開始決定的方式,因為在他記憶體位址前確實有些數字可以拿來偽造size,悲劇的是弄一弄發現他前方那塊竟然是Read-Only,害我浪費了不少時間......但我之後沒放棄,還是往這個方向繼續查資料,最後讓我找到了一篇害死我又幫助我很多的文章,在這裡面有個關鍵處:

But in late 2014, google’s project zero team found out a way to successfully bypass “corrupted double linked list” condition by unlinking a large chunk!!

他提到large bin unlink時的檢查是assert,並非__builtin_expect,而assert對於glibc來說只是debug用,並不會compile到release版本中,而我點進去那篇google’s project zero也只有挑實作的地方看,並沒有很仔細的看過,之後弄了將近兩天弄不出來,終於回去仔細地審視這篇文章,才看到了一個重點:

... Aside: there’s some evidence that Ubuntu glibc builds might compile these asserts in, even for release builds. Fedora certainly does not.

......fuck my life,看來unlink large bin這條路是不可......等一等,此時我又發現了另一篇文章 -Revisiting Defcon CTF Shitsco Use-After-Free Vulnerability — Remote Code Execution (你知道的,當你弄了兩天卻跟你說你這方法不行,你通常沒辦法很快接受事實),文章內提到了一個有趣的東西:

Disabling glibc protection by overwriting check_action

簡單來說libc內有個check_action的static variable (也就是固定offset),把它寫成0出錯時就不會abort了,對我來說是長姿勢了,第一次知道這個東西。雖說後來並不是用這個方法,但事後去研究一下我認為這的確可行,但應該僅限large bin,我們來看看unlink的code

#define unlink(AV, P, BK, FD) {
FD = P->fd;
BK = P->bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P, AV);
else {
FD->bk = BK;
BK->fd = FD;
if (!in_smallbin_range (P->size)
&& __builtin_expect (P->fd_nextsize != NULL, 0)) {
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))
malloc_printerr (check_action,
"corrupted double-linked list (not small)",
P, AV);
if (FD->fd_nextsize == NULL) {
if (P->fd_nextsize == P)
FD->fd_nextsize = FD->bk_nextsize = FD;
else {
FD->fd_nextsize = P->fd_nextsize;
FD->bk_nextsize = P->bk_nextsize;
P->fd_nextsize->bk_nextsize = FD;
P->bk_nextsize->fd_nextsize = FD;
}
} else {
P->fd_nextsize->bk_nextsize = P->bk_nextsize;
P->bk_nextsize->fd_nextsize = P->fd_nextsize;
}
}
}
}

如果要從FD、BK下手應該是不能,可以看到進入第一個if判斷式之後,即使malloc_printerr沒有call abort,他還是不會跑到else去執行unlink,但後面的large bin就不同了,檢查的地方只用if包起來而沒有後續的else,也就是如果malloc_printerr沒有中斷程式,它還是會跑回來繼續執行底下的unlink。

有個小細節是check_action沒有被export所以會找不到symbol,如果要找它的offset可以去 mallopt()看,這個function有用到check_action。

最後我的第一個解法是用__malloc_hook,這也滿出乎我意料的,由於malloc的參數是int,所以要不是餵one gadget rce的位址、不然就是要做ROP了,不過這個程式的操作基本上沒用到stack,所以要leak stack就麻煩了點(libc的environ),而one gadget rce由於要滿足一些條件滿吃運氣的,一開始我透過了最正常的方式讓程式調用malloc (也就是使用add功能)發現不行,而之後再嘗試一次,但這次呼叫malloc的方法是透過delete功能讓程式abort (double free corruption等等),而malloc_printerr裡面會呼叫malloc,此時就成功了。


再戰-fast bin與unsorted bin的組合計

第一個方法解出來讓我很空虛,感覺像是運氣好才過,此時我又想找找有沒有什麼更可靠的方法能夠成功的,以下會提到我做的幾個嘗試,以及發現了一個非常不錯的方法。

若想控制參數就必須使用__free_hook或是tls_dtor_list,而後者在之前已經提到有唯讀區塊所以不行,這時就剩下前者了,但__free_hook記憶體前面都是一堆0-起碼0x70個距離內都是(也就是最大的fastbin data size),那要怎麼辦呢?這時我的第一個想法是,有沒有辦法改變fastbin的範圍呢?雖然我之前沒看過相關資料,但直覺告訴我這個範圍畢竟是人為設計的,應該要可以給使用者客製化才合理,若我們能讓這個範圍加大,一直延伸到__free_hook前方非0的距離,這樣就能做fastbin attack了。

於是去閱讀了malloc的source code判斷fastbin的地方:

/*
If the size qualifies as a fastbin, first check corresponding bin.
This code is safe to execute even if av is not yet initialized, so we
can try it without checking, which saves some time on this fast path.
*/
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
...
-------------------------------------------------------------
#define get_max_fast() global_max_fast-------------------------------------------------------------
/* Maximum size of memory handled in fastbins. */
static INTERNAL_SIZE_T global_max_fast;

果然存在一個global_max_fast變數,若我們能把它覆寫成一個較大的值,就有機會覆寫__free_hook了,這時我想到了unsorted bin attack,可以將unsorted bin位址(一個很大的值)寫入到任意位址,如果寫到global_max_fast中,就可使之後申請的每塊chunk都被視為fastbin,這麼一來就可以到__free_hook前面A處找一個大的數字B作為fake fastbin size,之後藉由overlap來偽造一個chunk,弄好相應的fd和size後free掉這個chunk就能做fastbin attack了。

但悲慘的是這個程式限制了輸入的最大size為0x100,而0x100的範圍內也沒有0以外的數字,於是只能另尋方法。

此時我觀察到了unsorted bin的位址不只是一個很大的數字,它畢竟在libc內,所以位址一定是0x7f???????,而實驗一下會發現size 0x7f由於alignment以及低3-bit flag的原因其實會被視為0x70,這裡還是附上code:

/* pad request bytes into a usable size — internal version */#define request2size(req) 
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ?
MINSIZE :
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

這代表什麼呢?這代表我們能先藉由unsorted bin attack將0x7f???????寫在__free_hook前面,再藉由fastbin attack去拿到這塊chunk (size : 0x70),如此一來就能覆寫__free_hook了!這個方法沒有運氣成分,完全可靠阿。

解題之後-更多不同解法

解出來之後就能閱讀其他人的writeup了,以下大略的提提其他人的思路:

  1. file stream

關於file stream的exploit實在沒有篇幅介紹,有興趣的可以看看angelboy大神寫的簡報。粗略地說就是file stream內有個function table叫vtable,有辦法改寫掉裡面的東西就能控制程式,而解法裡面有人對stdout動手腳,另一個則是 _IO_list_all,在glibc abort routine內會用到(前面看到的malloc_printerr就是),更詳細的介紹在這裡

2. ROP

前面也有提到,必須leak stack導到buffer上稍微麻煩些。

後計

我自己打這篇文章前先做了一個筆記版本,裡面談了更多ptmalloc細節,點進去看可以發現內容遠多過這個文章,說也奇怪,當我還沒消化所有的資料時感覺有非常多東西可以講,但是概念全部釐清懂了之後反而不知道要講什麼......畢竟這篇不算是教學型的文章,若覺得有點艱澀難懂還請見諒。

另外這是我放在github的writeup,有興趣可以看看。

Ref

<malloc>

https://www.auto.tuwien.ac.at/~chris/teaching/papers/heap_jp03.txt

<exploit>

https://googleprojectzero.blogspot.tw/2014/08/the-poisoned-nul-byte-2014-edition.html

https://heap-exploitation.dhavalkapil.com/

<other>

https://pwnable.tw/

    berming

    Written by

    berming

    Security Consultant & CTF lover :)

    Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
    Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
    Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade