FFRIエンジニアブログ

株式会社FFRIセキュリティのエンジニアが執筆する技術者向けブログです

AI を活用したマルウェア解析の紹介 ~LLM エージェントの動作ログから学ぶマルウェア解析~

はじめに

こんにちは、セキュリティ・エンジニアの桑原です。 前回のブログでもご紹介しましたが、社内のサイバー分析サークルという技術サークルで活動しています。 このサークルでは、サイバーセキュリティに関する知見を深め、それを社内に展開していくことを目指しています。 せっかくなので、今回もサークルで得た学びや気づきをブログで紹介していきます。

今回は新ローレイヤー勉強会「AI×マルウェア解析入門:LLMエージェントはどう動くのか」 で扱った LLM エージェントの仕組みと動作ログの分析についてブログでも一部ご紹介します。 新ローレイヤー勉強会では Ghidra からエクスポートしたデコンパイル結果等の解析を題材として取り扱いました。 OpenAI 製のコーディングに特化した LLM エージェント Codex の VSCode で使用できる Codex IDE 拡張機能を利用しました。 今回はそれに加えて、Codex CLI と GhidraMCP の利用についても紹介します。

マルウェア解析では、静的解析などを通じて挙動を読み解く作業に多大な労力を要しますが、LLM を活用することでこの作業を大幅に効率化できます。 ただし、最終的な判断や確認は人間の手で行う必要があります。 一方で、マルウェア解析の初学者にとっては、LLM の応答と Ghidra 等の解析ツールだけを見ても、その内容が本当に正しいかを自力で判断するのは難しい場面も多いはずです。

そこで本記事では、LLM がマルウェア解析する際の「推論過程」と「参照している情報」を動作ログから分析し、学習の参考にする方法を紹介します。 Codex の動作ログを追いかけながら、LLM がどのように Ghidra の情報を使ってマルウェアを読み解いているのかを見ていきましょう。 これまでに LLM を使ってマルウェア解析に挑戦したものの、LLM の応答を十分に理解しきれないまま終わってしまった方にとって、特に参考になる内容です。

LLM を活用したアプリケーションの仕組み

動作ログを分析する前に、Codex を含む LLM を活用するアプリケーションにおいて、LLM がどのような仕組みで他のリソースと連携するかを簡単に確認しておきます。

ユーザーからの入力に対して、アプリケーションは LLM と他のリソースを連携させてユーザーへの応答を生成できます。 他のリソースとして shell や Ghidra などを用意することで、LLM からコマンドの実行や Ghidra の操作ができます。 では、LLM にこれらのリソースを認識させ、どのような操作が必要かを引き出すにはどうすればよいでしょうか。 多くの LLM を提供するサービスでは以下に列挙する情報を受け付けます。 ここでは OpenAI API の呼称 (model/messages/tools) にならい説明します。 アプリケーションが LLM に何らかのツールやサービスなどを利用させたい場合、tools に利用できるツールやサービスを関数として列挙します。 LLM は tools を参照し、必要な関数の呼び出し要求をアプリケーションに送信します。 アプリケーションはその要求を受け取り、関数の実行結果を LLM に送信します。 これらのやりとりは messages で行われます。

  • model: 利用するモデル
  • messages: LLM とアプリケーションとのチャット履歴(ユーザーの指示、関数呼び出しのやり取りも含む)
  • tools: LLM に利用登録する関数(ツールやサービスを呼び出す)の一覧

アプリケーションや LLM、提供される関数の関係は以下のようになります。

アプリケーションや LLM、提供される関数の関係

アプリケーションに実装された関数について、Codex を例に toolsmessages の内容を紹介します。 以下の図は Codex が LLM に渡すデータの例です。 shell は Codex が LLM に提供しているツールの 1 つで、Codex では基本的にこの関数を使ってシェルコマンドを実行し、ファイルの検索や閲覧をします。

LLM への送信データ

LLM はこの情報を参照し、必要に応じて関数を呼び出して情報の取得や外部ツールの操作をします。 関数の呼び出しと、その応答の受け取りは、以下のようなメッセージとしてやり取りされます。 以下の例では、shell 関数を用いて ls コマンドを実行しています。 図中の reasoning は LLM の推論過程を表しています。 関数の呼び出しや、その実行結果も messages に順次追加されていきます。

関数呼び出しと応答

shell は Codex により提供されていますが、Codex が提供していない関数が必要になる場合があります。 GhidraMCP プラグインなどを用いることで、Ghidra の機能を関数として LLM が利用できます。

GhidraMCP では、デコンパイル結果を取得する disassemble_function 関数や、文字列一覧を提供する list_strings 関数など、マルウェア解析に有用な機能が関数として提供されています。

Codex の動作ログ

Codex では、動作ログが ~/.codex/sessions/<year>/<month>/<day>/(もしくは $CODEX_HOME/sessions/<year>/<month>/<day>/)配下に JSON Lines 形式で保存されます。

ログには関数呼び出しを含む LLM とアプリケーションのやり取りが保存されます。(Codex では過去チャットを再開するために利用されます) 以下の図はログの例です。今回参照するフィールドは、["type"] = "response_item"payload のうち、次の 3 つです。

  • reasoning: LLM の推論過程
  • function_call: 関数の呼び出し要求(関数名と引数を含む)
  • function_call_output: 呼び出しによって得られた関数の実行結果

Codex のログ例

Codex によるマルウェア解析とそのログ分析

この章では、Codex を用いたマルウェア解析を 2 つの方法で行い、その際に保存される動作ログの読み解き方について紹介します。 まず 1 つ目として、VSCode の Codex IDE 拡張機能を使い、Ghidra からエクスポートしたファイル群を開いて解析する方法を紹介し、そのときの Codex の動作ログを追いかけます。 この方法では、ユーザーもエクスポートしたファイルを VSCode で閲覧しながら解析することになります。 VSCode だけですべてのやり取りを完結させられますが、解析者は Ghidra などの解析ツールを併用する場合が多いでしょう。

続いて 2 つ目として、ユーザーが主に Ghidra を利用しながら解析を進めたい場合を想定し、GhidraMCP の関数と Codex CLI を組み合わせて、同様の解析をする方法を紹介します。 Codex CLI を利用することでコマンドラインから実行できるため、Python などから呼び出して Codex の操作を自動化できます。

Ghidra からファイルをエクスポートする方式と GhidraMCP を利用する方式のどちらを選ぶ場合でも、Codex IDE 拡張機能と Codex CLI の両方が利用可能です。 どの組み合わせで利用するかはユーザーの好みで決めてください。 例えば、Codex IDE 拡張機能と GhidraMCP を組み合わせると、Ghidra を利用しつつ VSCode でメモを取りながら作業を進める、といった使い方も可能です。

解析対象のマルウェアは、MWS Cup 2025 の静的解析課題で題材にされた以下の検体です。 実行時に Windows API のアドレスを計算して取得し、静的解析ではどの API を呼ぶか分かりづらくする耐解析機能を備えた Remote Access Trojan (RAT) です。 本記事では、呼び出す API の情報は LLM へ与えずに解析してもらいます。

  • 780f5d21f1f38779f643f1fdf6c42795d23f7e77e1f75b09cead2ce5d0f15ea3

Codex IDE 拡張機能 × Ghidra からのファイルエクスポート

VSCode 上で Codex IDE 拡張機能を利用しつつ、あらかじめ Ghidra からエクスポートしたファイル群を読み込んで解析する構成を紹介します。

環境準備

まずは環境を準備します。 利用する主なツールのバージョンは次の通りです。

OS は Ubuntu 24.04 Desktop を利用します。 Ghidra の構築方法はここでは割愛するため、README を参照してください。 次に、KinGAidra を Releases からダウンロードし、Ghidra の File -> Install Extensions からインストールします。 インストール後に、kingaidra_export.py を実行します。 このスクリプトにより、以下の情報がファイルとして出力されます。 コーディングに特化した LLM エージェントである Codex が以下の情報を参照し、まるでソースコードを読むかのように解析をしてくれます。

  • アセンブリ
  • デコンパイルコード
  • imports/exports
  • 文字列
  • etc.

続いて VSCode をインストールし、Codex IDE 拡張機能をインストールします。 サイドバーで Codex を開き Sign in します(ChatGPT のアカウント)。 最後に、Ghidra からエクスポートされたファイル群を含むディレクトリを Codex 上で開きます。 これで、準備が整い Codex に指示を入力し解析を開始できるようになりました。

マルウェア解析結果

以下のプロンプトでマルウェアの全体的な挙動を大まかに Codex に分析してもらいます。 モデルは gpt-5-codex の medium を利用します。

Codex への指示

英語で出力されてしまったので、日本語に翻訳させます。 翻訳後は文の順序が一部変わっています。

Codex での翻訳結果

結果をざっと確認します。 以下の図は、応答のテキストと図(テキストベースの UML 図描画言語を活用して画像化)の一部抜粋です。 いくつか誤りはありますが、MWS Cup 2025 の静的解析課題と比較してみると、Codex がマルウェアの動きを概ね把握できていることが分かります。 以下は誤りの例です。

  • FUN_18000ff2c -> FUN_180005d18 にキューでコマンドを渡す旨の記述がありますが、正しくは FUN_18000ff2c ではなく FUN_18000c860 です
  • K31610KIO9834PG75459K は通信では利用されていません

Codex の応答

画像化した図

今回は、参考情報があったため LLM の応答の検証が容易でした。 一方で、実務で未知のマルウェアを扱う場合や、学習の場面では「この応答はどこまで信用してよいのか」を判断するのが難しくなります。 そこで次節では、LLM がどのように情報を取得し、どのような推論過程を経てこのレポートを生成しているのかを、Codex の動作ログを使って追いかけてみます。

動作ログの分析

ここからは、Codex の動作ログから ["type"] = "response_item" のレコードを抜き出し、その payload を先頭から順に追っていきます。

以下は Codex による解析開始時のログです。 まずはエントリーポイントを見つけるために、exports.json を確認してエクスポート関数の一覧を取得しています。 エクスポート関数の中に entry 関数を見つけたため、そのデコンパイル結果を確認します。 そこで dllmain_dispatch 関数を見つけたため、dllmain をキーワードに DLL 関連の関数をまとめて検索します。

エントリーポイント探索ログ

DLL であることが判明したため、DllMain 関数相当の関数からマルウェアの中核処理を行うスレッドを探す方針で解析を進めます。 CreateThread 文字列や _beginthread 文字列を検索しますが、ここでは見つかりません。 方針を変え、通信処理を探すために WinHttp 文字列を検索しますが、こちらもヒットしません。

API 検索ログ

Windows API などの典型的な関数シンボルが見つからないため、今度はマルウェアのコマンド特有の文字列を検索します。 いくつかの関数がヒットしたため、その呼び出し元を確認していきます(ログの掲載は割愛します)。

コマンド検索ログ

呼び出し元をたどっていくと、コマンドハンドラと思われる処理が見つかり、コマンドの判定時に param_1 + 0x240 などのオフセットが参照されていることが分かります(詳細は MWS Cup 2025 の静的解析課題 p.52 を参照)。

そこで + 0x240 をキーに検索し、該当箇所の内容を確認していきます。 ヒットした decom/func_1800169d4.c を確認すると、難読化された文字列とその難読化を解除する関数(FUN_180016408)が存在します。

オフセット検索ログ

難読化を解除する関数のデコンパイル結果を確認します。

難読化を解除する関数の確認

難読化を解除する関数のデコンパイル結果をもとに、Python で難読化を解除します。 python コマンドでの実行に失敗したため、python3 で再試行しており、そのためログ中に Switching to Python3 と記載されています。 実行結果を見ると、難読化解除には部分的に成功していることが分かります。

難読化の解除試行

これ以降のスクショは割愛しますが、この後の処理で CreateThread 関数を正確に難読化解除し、LLM が各 API の存在を認識できるようになります。 コマンド判定の情報は見つかりませんでしたが API の情報は判明したため、これをもとに解析を進めていきます。

ちなみに、推論過程を読み進めていくと、実はコマンド判定情報と API 名はどちらも + 0x240 で参照されることから、それらを Codex が取り違えてしまっていることがわかります。(詳細は MWS Cup 2025 の静的解析課題 の p.48 および p.54 を参照)

このように LLM の「応答」だけでなく、その裏側にある LLM の推論過程やツール呼び出しを読むことで、具体的な解析の進め方を学ぶことができます。 今回はログを先頭から順番に追っていきましたが、実際の学習では LLM の応答に出てきた関数名やアドレスを起点に見ていくと良いです。

Codex CLI × GhidraMCP

コマンドラインから実行できる Codex CLI と GhidraMCP を組み合わせ、Ghidra 上の解析対象に直接アクセスしながら解析する構成を紹介します。

環境準備

まずは環境を準備します。 利用する主なツールのバージョンは次の通りです。

Ghidra 自体の構築方法は前述と同様のため割愛します。 GhidraMCP を Releases からダウンロードし Ghidra の File -> Install Extensions からインストールします。 インストール後に、Code Browser の Edit -> Tool Options -> GhidraMCP HTTP Server から IP アドレスやポート番号を設定します。 Ghidra を外部から操作するための Web API を提供します。 今回は以下のように設定します。

GhidraMCP の設定

次に、GhidraMCP からダウンロードした zip 内の requirements.txtbridge_mcp_ghidra.py を取り出し、以下のコマンドを実行してください。 --ghidra-server に上記で設定した GhidraMCP のアドレスを指定します。 --mcp-host--mcp-port で設定したアドレスで LLM を活用する Codex 等のアプリケーションからの接続を受け付けます。

pip install -r requirements.txt
python bridge_mcp_ghidra.py --transport sse --mcp-host 127.0.0.1 --mcp-port 8081 --ghidra-server http://127.0.0.1:8080/

最後に Codex CLI を準備します。 Node.js のダウンロードページに従い Node.js をインストールし、以下のコマンドで Codex CLI をインストールします。

npm install -g @openai/codex

GhidraMCP を利用するために以下の ~/.codex/config.toml ファイル(もしくは $CODEX_HOME/config.toml)を作成してください。 Windows では command をフルパスで指定する必要があります(例: C:\\Program Files\\nodejs\\npx.cmd)。

model = "gpt-5-codex"
model_reasoning_effort = "medium"

[mcp_servers.ghidra_mcp]
transport = "stdio"
command = "npx"
args = ["-y", "mcp-remote", "http://127.0.0.1:8081/sse", "--transport", "sse-only"]

以下のコマンドで Codex CLI が実行できます。

  • exec: 非対話形式での実行
  • --cd: 作業対象ディレクトリの指定
  • --skip-git-repo-check: Git リポジトリではないディレクトリでの実行を許可
  • --output-last-message: 結果を出力するファイルを指定
codex exec --cd ./analysis --json --skip-git-repo-check --output-last-message output.md このマルウェアはRATですか?
<省略>

マルウェア解析結果と動作ログの分析

以下のコードでマルウェアの全体的な挙動を大まかに Codex に分析してもらいます。 ここでは Python から実行する方法を例として紹介します。

import subprocess
import json

PROMPT = """ghidra_mcpを用いてGhidraで解析しているマルウェアのアセンブリやデコンパイル結果等を利用できます。
Ghidraで解析しているマルウェア全体の挙動を把握したいです。
関数の呼び出し元/先を念入りに調査し、マルウェア解析者向けの詳細な解析レポートを作成してください。
以下を含めてください。

- マルウェアの種別(ランサムウェア、RAT、ダウンローダー、etc.)
- マルウェアの挙動をPlantUMLのシーケンス図で記述
- シーケンス図では対応するファイル名と行番号を記載

事実に基づいて回答してください。根拠が不明な箇所は「不明」と答えてください。推測や仮定は一切行わないでください。"""

res = subprocess.run(
    ["codex", "exec", "--cd", "./analysis", "--json", "--skip-git-repo-check", "--output-last-message", "output.md", PROMPT],
    stdout=subprocess.PIPE,
    stderr=subprocess.STDOUT,
    text=True, encoding="utf-8",
    check=True,
)

# 動作ログファイルの特定に役立つ ID 情報等を含む(ここでは割愛)
jsonl_str = res.stdout

with open("output.md", "r", encoding="utf-8") as f:
    print(f.read())

以下が実行結果の一部抜粋です。 Ghidra からファイルをエクスポートした場合と同様の応答が得られます。

Codex CLI の応答

以下が動作ログの一部抜粋です。 動作ログの分析で紹介した内容と同様に、エントリーポイントを探すために ghidra_mcp が提供する list_exports を利用して、エクスポート関数を確認しています。

Codex CLI のログ

さいごに

本記事では、LLM の動作ログを題材にマルウェア解析の方法を学ぶ手法について紹介しました。 LLM を活用することで人間がすべてを理解していなくても、課題を解決できる場面が増えています。 しかし、エンジニアとして LLM の応答が本当に正しいかを判断したり、LLM ではまだ解決できない課題に向き合う必要があります。 紹介したアプローチは、まるで先輩エンジニアがマルウェア解析を実演しながら、その手順や着眼点を横で解説してくれているようなものです(時々間違えるのが玉に瑕ですが)。 今回はマルウェア解析を題材にしましたが、この学習方法はマルウェア解析以外でも活用できます。 LLM を活用した学習方法の 1 つとして、ぜひ試してみてください。

前回のブログで「次回はローカル LLM について取り上げる」としていましたが、予定を変更して今回の内容を執筆しました。 ローカル LLM については、また別の機会に紹介します。

エンジニア募集

FFRIセキュリティではサイバーセキュリティに関する興味関心を持つエンジニアを募集しています。 採用に関しては採用ページをご覧ください。