ファジングの基本的な仕組みを学ぶために、オープンソースであるboofuzzのコード読みをします。
テンプレートを見る
どのようなファジングを行うかユーザが定義するためのPythonスクリプトを、こちらのサイトに倣ってテンプレート (fuzzer template) と呼ぶことにします。
まずは、コードを読む前にテンプレートを見てみます。
リポジトリのexamples
ディレクトリに複数のテンプレートが用意されているので、ここではhttp_simple.py
を見ます。
def main(): session = Session( target=Target(connection=TCPSocketConnection("127.0.0.1", 80)), ) define_proto(session=session) session.fuzz() def define_proto(session): # disable Black formatting to keep custom indentation # fmt: off req = Request("HTTP-Request", children=( Block("Request-Line", children=( Group(name="Method", values=["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE"]), Delim(name="space-1", default_value=" "), String(name="URI", default_value="/index.html"), Delim(name="space-2", default_value=" "), String(name="HTTP-Version", default_value="HTTP/1.1"), Static(name="CRLF", default_value="\r\n"), )), Block("Host-Line", children=( String(name="Host-Key", default_value="Host:"), Delim(name="space", default_value=" "), String(name="Host-Value", default_value="example.com"), Static(name="CRLF", default_value="\r\n"), )), Static(name="CRLF", default_value="\r\n"), )) # fmt: on session.connect(req) (中略) if __name__ == "__main__": main()
上記のテンプレートは、見る限りでは、
- localhostの80番ポートをターゲットに設定しつつ、
Session
クラスのインスタンスsession
を定義する define_proto()
関数によって、HTTPプロトコルでファジングを行うよう設定するsession.fuzz()
でファジングを実行する
以上の処理を記述していると思われます。
fuzz()のコード読み
実際にファジングを行う処理はsession.fuzz()
関数にて行われてそうなので、boofuzz/boofuzz/sessions.py
にて定義されているfuzz()
関数からコードを読んでみます。
def fuzz(self, name=None, max_depth=None): if name is None or name == "": self._main_fuzz_loop(self._generate_mutations_indefinitely(max_depth=max_depth)) else: self.fuzz_by_name(name=name)
引数name
は、http_simple.py
では特に設定されていないので、_main_fuzz_loop()
関数に着目します。
_main_fuzz_loop()
の引数_generate_mutations_indefinitely()
は、イテレータを返すジェネレータ関数です。
また、_main_fuzz_loop()
は名前からファジングに関する何らかの処理を繰り返す関数であると推測できます。
_main_fuzz_loop()
とりあえず、_main_fuzz_loop()
関数を読み進めます。
def _main_fuzz_loop(self, fuzz_case_iterator): """Execute main fuzz logic; takes an iterator of test cases. Preconditions: `self.total_mutant_index` and `self.total_num_mutations` are set properly. Args: fuzz_case_iterator (Iterable): An iterator that walks through fuzz cases and yields MutationContext objects. See _iterate_single_node() for details. Returns: None """ self.server_init() try: self._start_target(self.targets[0]) if self._reuse_target_connection: self.targets[0].open() self.num_cases_actually_fuzzed = 0 self.start_time = time.time() for mutation_context in fuzz_case_iterator: if self.total_mutant_index < self._index_start: continue # Check restart interval if ( self.num_cases_actually_fuzzed and self.restart_interval and self.num_cases_actually_fuzzed % self.restart_interval == 0 ): self._fuzz_data_logger.open_test_step("restart interval of %d reached" % self.restart_interval) self._restart_target(self.targets[0]) self._fuzz_current_case(mutation_context) self.num_cases_actually_fuzzed += 1 if self._index_end is not None and self.total_mutant_index >= self._index_end: break if self._reuse_target_connection: self.targets[0].close() if self._keep_web_open and self.web_port is not None: self.end_time = time.time() print( "\nFuzzing session completed. Keeping webinterface up on {}:{}".format( self.web_address, self.web_port ), "\nPress ENTER to close webinterface", ) input() except KeyboardInterrupt: # TODO: should wait for the end of the ongoing test case, and stop gracefully netmon and procmon self.export_file() self._fuzz_data_logger.log_error("SIGINT received ... exiting") raise except exception.BoofuzzRestartFailedError: self._fuzz_data_logger.log_error("Restarting the target failed, exiting.") self.export_file() raise except exception.BoofuzzTargetConnectionFailedError: # exception should have already been handled but rethrown in order to escape test run pass except Exception: self._fuzz_data_logger.log_error("Unexpected exception! {0}".format(traceback.format_exc())) self.export_file() raise finally: self._fuzz_data_logger.close_test()
この関数のコメントによると、引数fuzz_case_iterator
はファズケースを走査し、MutationContextオブジェクトを生成するイテレータとのことです。現時点ではよく分かりません。
コードが長いので、順番に読んでいきます。
self.server_init()
WebUI関係の関数と思われます。
self._start_target(self.targets[0])
Sessionクラスのリスト型アトリビュートtargets
を引数にとった関数です。
targets
には、Sessionのインスタンス生成時の引数target
が格納されています。
http_simple.py
の例では、Target(connection=TCPSocketConnection("127.0.0.1", 80))
が格納されます。
_start_target()
はTarget型の引数targetを持ち、関数内でtarget.monitors
をイテレータとしたfor文の処理のみが実装されています。
本来の_start_target()
関数では監視関連の処理が実行されると思われますが、http_simple.py
の例ではインスタンスtarget
の生成時に引数monitors
を指定していない(つまり、target[0].monitorsは空のリストである)ので、何も処理されないと思われます。
if self._reuse_target_connection: self.targets[0].open()
self.reuse_target_connection
には、Sessionインスタンス生成時のbool型引数reuse_target_connection
が格納されます。
reuse_target_connection
については、ソースコードのコメントにて以下のように書かれています。
reuse_target_connection (bool): If True, only use one target connection instead of reconnecting each test case. Default False.
通常boofuzzがファジングを行う際はTCPコネクションの接続と切断を繰り返すので、reuse_target_connection
をTrueにした場合はファジング中のTCPコネクションを確立したままにするという事でしょうか。
いずれにしろ、デフォルトではFalseなのでこのコードもスルーします。
self.num_cases_actually_fuzzed = 0
self.start_time = time.time()
num_cases_actually_fuzzed
およびstart_time
は、この行で定義されるアトリビュートです。用途はまだ分かりません。
_main_fuzz_loop()
の以降のコードを理解するには_generate_mutations_indefinitely()
について理解する必要がありそうなので、次回は_generate_mutations_indefinitely()
から読み進めたいと思います。