取締役CTOの小竹(aka tkmru)です。
先月、genpatchというIDA Pro上で当てたパッチと同じパッチを当てるPythonスクリプトを生成するIDAプラグインを公開しました。 この記事では、作成した「genpatch」の使い方、仕組みを紹介します。
IDA Pro上でバイナリに当てたパッチと同じパッチを当てるPythonスクリプトを生成するIDAプラグイン「genpatch」をOSSとして公開しました。SSL Pinningをバイパスするパッチを異なるバージョンのアプリに当てる時など同じパッチを複数回当てるときに便利です。https://t.co/E5Y2V5E1RB
— 株式会社ステラセキュリティ (@sterrasec) 2023年11月21日
困っていたこと
スマホアプリが対象の脆弱性診断では、準備段階でリバースエンジニアリングを行い、パッチを当てることがあります。 SSL Pinning(Certificate Pinning、証明書のピン留め)をお客様側で無効化していただけなかった場合には、解析を行い、無効化するパッチを当てた上で、APIとの通信を診断します。 SSL Pinningは、使用するSSL証明書をアプリ内にハードコートし、通信時にSSL証明書がアプリ内のものか検証を行うことで、SSL通信の中間者攻撃を防ぐ仕組みです。 Burp Suiteなどのプロキシツールを使ってSSL通信を閲覧する際には、PCにインストールしたプロキシツールの証明書を使用しています。 そのため、SSL Pinningが実装されているアプリに対して、プロキシツールを使用して通信を閲覧しようとすると、SSL証明書の検証に失敗し、通信が行えません。
診断期間中に診断対象のアプリがアップデートされた場合、パッチを当て直す必要があります。 その際に、IDA Proを起動し、パッチを当て直す1のは面倒です。 また、IDA Proの操作に慣れていないチームメンバーにパッチを当ててもらうのも、大変です2。 そのため、IDA Pro上で当てたのと同じパッチを当てるPythonスクリプトを生成するプラグインを作成しました。
作ったツール
genpatchというIDA Pro上で当てたパッチと同じパッチを当てるPythonスクリプトを生成するIDAプラグインを作成しました。 genpatchが生成したパッチを当てるPythonスクリプトを使用することで、 繰り返しバイナリにパッチを当てる際に、Pythonスクリプトを実行するだけで済みます。 このプラグインはPythonで実装しています。
インストール方法
リポジトリ内のgenpatch.py
、patch_script_template.txt
をIDA Pluginフォルダにコピーし、IDA Proを再起動するとgenpatchが使えるようになります。
Windowsの場合、コピー先のフォルダはC:\Program FilesIDA 8.x\plugins
です。
macOSの場合、コピー先のフォルダは/Applications/IDA Pro 8.x/idaq.app/Contents/MacOS/plugins
にあります。
パッチを当てるPythonスクリプトを生成する
IDA Pro上でパッチを当てた後、Edit -> Plugins -> genpatchボタンをクリックすると、IDA Proで読み込んだバイナリがあるディレクトリにパッチスクリプトが生成されます。
スクリプトの名前は、解析中のバイナリに接尾辞 _patch.py
を付けたものになります。
例えば、バイナリ名が a.out
の場合、パッチスクリプト名は a.out_patch.py
となります。
Pythonスクリプトが正常に生成されると、以下のようなダイアログが表示されます。
生成したPythonスクリプトを実行する
コマンドライン第一引数にパッチを当てたいバイナリへのパスを指定して実行することで、パッチを当てることができます。
$ python a.out_patch.py <target_binary_path>
生成されたPythonスクリプトは次のようになります。16進数のデータ列を置換します。
#!/usr/bin/env python # coding: UTF-8 import binascii import os import re import sys target_path = sys.argv[1] target_data = None with open(target_path, 'rb') as target_file: target_data = binascii.hexlify(target_file.read()).decode('utf-8') # address: 0x100000ecb # function name: __text: _main # comment: Keypatch modified this from: jz loc_100000EF6 Keypatch padded NOP to next boundary: 4 bytes matches = re.findall('0f8425000000', target_data) if len(matches) == 1: target_data = target_data.replace('0f8425000000', '752990909090') else: print("Patch pattern isn't unique") sys.exit() result_path = f'{target_path}_patched' with open(result_path, 'wb') as result_file: if sys.version_info[0] >= 3: result_file.write(bytes.fromhex(target_data)) else: result_file.write(target_data.decode('hex')) print("Successfully generated patched binary to '%s'" % result_path)
おわりに
弊社ではこのように脆弱性診断中に遭遇した課題を解決するべく、適宜ツールを開発し、業界内に知見を共有していきます。 また、「genpatch」が脆弱性診断、マルウェア解析などリバースエンジニアリングを伴う仕事に従事するエンジニアに広く使われるよう育てていきたいと思っています。 ぜひ、気づいたことがあればフィードバックしてもらえるとうれしいです。 みなさまからのIssueやプルリクエストをお待ちしております。