Boofuzzで始めるネットワークプロトコルファジング
Posted on
環境準備
Windows端末にboofuzzをソースコードからインストールします。手順はboofuzzのドキュメントを参照してください。
ドキュメントには記載されていませんが、Python2.7を使用しないとFuzzing対象プロセスを監視するprocess_monitor.pyが動作しませんので注意が必要です。
Boofuzzは、Fuzzing toolであるSulleyの後継として作られたOSSです。BoofuzzのBooはMonsters incのBooから取っているそうです。
Sulleyはもうメンテナンスされていないので、これからネットワークプロトコルファジングを始める方はBoofuzzを使うのが良いでしょう。
BoofuzzはSulleyよりもネット上に落ちている情報が少ないと感じるかもしれませんが、Sulleyについて書かれた情報はBoofuzzでも活用できるものが多いはずです。
Fuzzing開始
1. Fuzzing対象とするバイナリを用意
今回はWebサーバのSavant 3.0を実例に使用します。
2. process_monitor.pyを実行
python process_monitor.py
を実行するとプロセス監視サーバが26002番ポートで立ち上がります。
3. Boofuzzを実行するPythonスクリプトを用意
以下に今回用意したサンプルスクリプトのソースコードを記載します。
start_commands
とstop_commands
を指定することで、テストケース毎にSavant.exeを実行しなおすようにしています。これをやらない場合、あるテストケースでクラッシュが発生したとしても、そのテストケース単体を入力してもクラッシュが発生しない場合があるので注意が必要です。
procmon=bf.pedrpc.Client(host, 26002)
ではprocess_monitor.pyで立ち上げたプロセス監視用サーバを指定しています。
bf.s_initialize()
にて、テストケースとするパケットの構造をブロックとして指定します。今回はベーシックなHTTPプロトコルの構造を指定します。
プロトコル構造の指定方法については、以下サンプルスクリプトと読めばだいたい想像がつくかと思います。詳細を知りたい方はBoofuzzのドキュメントを参照してください。
import boofuzz as bf
import datetime
def main():
port = 80
host = 'localhost'
protocol = 'tcp'
start_commands = ['C:\\Savant\\Savant.exe']
stop_commands = ['wmic process where (name="Savant.exe") delete']
now = datetime.datetime.now()
csv_log_name = now.strftime('%Y%m%d_%H%M%S') + '_fuzzing.csv'
csv_log = open(csv_log_name, 'w')
my_logger = [bf.FuzzLoggerCsv(file_handle=csv_log)]
target = bf.Target(
connection=bf.SocketConnection(host, port, proto=protocol),
procmon=bf.pedrpc.Client(host, 26002),
procmon_options={
'start_commands': start_commands,
'stop_commands': stop_commands,
}
)
session = bf.Session(
target=target,
fuzz_loggers=my_logger,
)
bf.s_initialize(name='Request')
bf.s_group(
'Method',
[
'GET',
'HEAD',
'POST',
'PUT',
'DELETE',
'CONNECT',
'OPTIONS',
'TRACE',
]
)
bf.s_delim(' ', name='space-1')
bf.s_string('/index.html', name='Request-URI')
bf.s_delim(' ', name='space-2')
bf.s_string('HTTP/1.1', name='HTTP-Version')
bf.s_static('\r\n', name='Request-Line-CRLF')
bf.s_string('Host:', name='Host-Line')
bf.s_delim(' ', name='space-3')
bf.s_string('example.com', name='Host-Line-Value')
bf.s_static('\r\n', name='Host-Line-CRLF')
bf.s_static('\r\n', 'Request-CRLF')
session.connect(bf.s_get('Request'))
session.fuzz()
if __name__ == '__main__':
main()
4. 作成したPythonスクリプトからBoofuzzを実行
http://localhost:26000へアクセスするとプロセス監視サーバのGUIにて、発生したクラッシュの情報を確認することが出来ます。
赤の下線で示した部分を見ると、今回は00409167と0040a2e5の2か所でmemory access violationが発生していることが分かります。
画面左の506と書かれた数字をクリックすると460番のテストケースに関する詳細情報を見ることが出来ます。
入力パケットが ’\’ を大量に繰り返していることが分かります。
シンプルなPythonスクリプトでテストケースをパケットとして送信してみると実際にSavant 3.0がクラッシュすることを確認できます。
そのときのPCAPをWiresharkで確認してみるとこんな感じです。
余談ですが、RawCapを使えばWindowsでlocalhost宛のループバック通信をキャプチャすることが可能です。
5. クラッシュした原因をリバースエンジニアリングで調査
0040a2e5でmemory access violationが発生していましたが、そこからIDA Pro等の逆アセンブラ/デバッガにて実行パスを逆に遡っていくと、char FileName\[260]
としてHTTPのRequest URIを保存していることが分かります。
※ 皆さんのFuzzing環境とデバッガのある環境が別の端末の場合には、リモートデバッグが便利です。やり方はIDA Proによるリモートデバッグに記載しています。
ここから順に実行パスをたどると、Request URIの末尾が ’\’ である場合に、GetFileAttributesA(Filename);
をCallしています。(デコンパイラ上ではエスケープされて’\‘と表示)
GetFileAttributesA(Filename);
が失敗した場合には、Fuzzingにてクラッシュが発生した関数をCallすること流れとなっています。
以上より、テストケース506ではRequest URIが260バイト以上のサイズを超える ’\’ で埋められているためにバッファオーバフロー(BOF)が発生したことで、0040a2e5にてmemory access violationが発生したと判明します。
Boofuzzを使ってBOFの脆弱性を見つけることができました。