サイト内の現在位置

JavaScriptで書ける!APIフックによるマルウェア動的解析

NECセキュリティブログ

2020年12月04日

早いものでもう年末ですね。
NECサイバーセキュリティ戦略本部セキュリティ技術センターの川北です。今回はマルウェアの動的解析でよく用いられるAPIフックを気軽に利用する方法を紹介します。

APIフックとは

WindowsやAndroidなどのOS上で動作するアプリケーションは、アプリケーションプログラミングインタフェース(API: Application Programming Interface)を呼び出すことで、ファイルやネットワークをはじめとするOS固有の機能を呼び出します。マルウェアも例外ではありません。APIの出入口に独自のコードを仕掛けて、本来の振る舞いとは異なる動作をさせる手法がAPIフックです。

マルウェアの動的解析において、ネットワークへ流れるデータが暗号化されていると、Wiresharknew window[1]等のパケットキャプチャツールでは容易に中身がわからないという問題があります。マルウェアの内部では平文で扱われているデータでも、パケットキャプチャツールが監視するネットワークレイヤーではすでに中身が暗号化されているためです。かわりに暗号化機能を提供するAPIをフックすれば、引数として渡された平文をトレースできます。平文を得ることで解析者がマルウェアの動作を理解しやすくなります。

APIフックを仕掛けるには振る舞いを変更したいプロセスに独自のコードを送り込む必要があり、DLL Injection(ATT&CK Tech ID: T1055.001)new window[2]がよく用いられます。DLL Injectionには様々な方法new window[3]があり、マルウェアでもexplorer.exeやsvchost.exe等のOSに信頼された実行ファイルの依存モジュールを装って活動するために利用されています。

APIフックやDLL Injectionを自前で実装すると骨が折れるので、オープンソースソフトウェアを用いて体験してみましょう。

JavaScriptでAPIフックが書けるFrida

Fridanew window[4]はいわばネイティブアプリケーション向けのGreasemonkeynew window[5]で、WindowsだけではなくAndroidやiOSでも動作可能なマルチプラットフォームAPIフックツールキットです。

従来、APIフックの出入口に仕掛けるコードはC/C++やアセンブリ言語で記述するのが一般的でしたが、FridaはC言語で記述されたコアの中に小さなJavaScriptエンジンを搭載しており、APIフックコードをJavaScriptで書くことができます。JavaScriptはGithubの過去5年間で最も利用されているプログラミング言語new window[6]で、親しみのある方が多いと思われます。試行錯誤のしやすさからAPIフック道15年目の私も使いやすく感じます。

64ビット版のWindows 10にPython 3.8を導入したのち、pipコマンドからfrida-toolsパッケージをインストールします。なお、前述のとおり、APIフックやDLL Injectionはマルウェアでも用いられている手法のため、アンチウイルスソフトウェアに疑わしいファイルとして検知されることがありますのでご注意ください。

図1. Fridaのインストール

(例1)EncryptMessage APIの引数に渡される平文を取得する

最近のWindows 10に標準搭載されているcurlコマンドを悪用して外部のhttpsサーバーへアクセスするマルウェアがあったという想定で、APIフックによってリクエストの中身を覗いてみます。

curlコマンドはhttpsサーバーへのリクエストをEncryptMessage APIで暗号化しています。MSDNやWindows SDKのヘッダーファイルからEncryptMessage APIの定義new window[7]を知り、引数の構造体のバイトオフセット等を確認しておきます。

frida-traceコマンドに今回フックしたいEncryptMessage APIとcurlを指定して実行します。実行結果はテキストファイルresponse.txtに出力します。

図2. ひな型スクリプトの生成

初回実行ではAPIフックのひな型スクリプトが自動生成されます。カレントディレクトリ以下の __handlers__\SSPICLI.DLL\EncryptMessage.jsを開いて、以下のように書き換えてください。先ほど調べた構造体のオフセットに従って、引数に渡される複数のバッファをlogに出力しています。同名のAPIを持つDLL(Secur32.dll)用のひな型も生成される場合がありますが、今回は無視してもかまいません。

図3. APIフックスクリプトでEncryptMessage APIの引数をlogに出力

再び、同じ引数でfrida-traceコマンドを実行すると今度は書き換えたJavaScriptコードが実行されます。続けて、実行結果が格納されているresponse.txtを開きます。

図4. logに出力されたhttpsリクエストの中身

すると、APIフックスクリプトのなかでトレースしたEncryptMessage APIの引数のひとつにhttpsリクエストの中身(赤枠部分)が含まれています。このようにして、パケットキャプチャツールでは取得しにくいhttpsリクエストの中身を簡単に得られます。

(例2)IsDebuggerPresent APIによるデバッガ検知に対抗する

次はトレースだけでなくAPIの振る舞いを書き換えてみます。

マルウェア解析の初歩的な課題であるデバッガ検知の回避をAPIフックで実現しましょう。ほとんどのマルウェアがIsDebuggerPresent APInew window[8]を使う最も単純な方法でデバッガを検出しています。このAPIを使ったサンプルプログラムは以下です。Visual Studio等でビルドして、実行ファイルantidebug.exeを生成してください。

図5. antidebug.cソースコード

antidebug.exeを実行すると「いまからデバッガーチェックします」というメッセージが表示されます。この間にデバッガ(Visual Studioやwindbg)でプロセスにアタッチしてください。APIフックのない状態ではIsDebuggerPresent APIがデバッガの存在を検出して戻り値をTRUEとし、「解析ご苦労様です」のメッセージが表示されます。実際のマルウェアでは静かにプロセスを終了するケースが多いです。

図6. 解析者をねぎらう(?)メッセージ

前例と同様にfrida-traceコマンドを使ってIsDebuggerPresent APIに対するフックのひな型スクリプトを生成します。

図7. IsDebuggerPresent APIフックのひな型スクリプトを生成

今度は __handlers__\KERNEL32.DLL\IsDebuggerPresent.js の中身を次のように書き換えます。デバッガの有無にかかわらずIsDebuggerPresent APIの戻り値をFALSEに強制しています。

図8. APIフックスクリプトでデバッガの有無によらず戻り値をFALSEに強制

もう一度先ほどのfrida-traceコマンドを実行すると、デバッガが検出されず、電卓が起動します。APIフックによってデバッガの検出を無効化しました。

ただし、APIフックは万能ではありません。IsDebuggerPresent APIはプロセスの状態を保持するPEB構造体のBeingDebuggedメンバーを参照してデバッガの有無を判断していますが、PEB構造体のインスタンスが存在するアドレスはmov eax, dword ptr fs:[30]などとしてCPUから直接得ることができ、APIを通さなくてもBeingDebuggedのチェックが可能です。このようなタイプに対してはAPIフックではなく、PEB構造体のインスタンスを書き換える別の手法が必要です。OllyDbgnew window[9]等のanti-anti-debug機能をもったデバッガはこのようなケースに対応しています。

おわりに

APIフックはマルウェアの動的解析において有用な手段でありながらも、手軽に利用できるものではありませんでした。本稿ではFridaを用いることでJavaScriptによりAPIの振る舞いを容易に変更できることを示しました。Windowsのほか、AndroidやiOSのAPIフックにも対応しているため、スマートフォン内のプライバシー情報を無断で外部に送信するスパイウェアの解析にも役立つのではないかと思われます。

参考文献

執筆者プロフィール

川北 将(かわきた まさる)
セキュリティ技術センター インテリジェンスチーム・デジタルシャドウ活用チーム
兼務 セキュリティ研究所

脅威インテリジェンス分析の研究開発に従事。
SECCON CTF国際大会の決勝戦に参加した過去も。
情報処理安全確保支援士(2020年11月現在)、システム監査技術者
人生の楽しみはうまい酒とうまいAPIフック。