クロスバージョンシンボル移行 (Binary Diff)
アクション必須(読んだらすぐ実行)
NOW:../field-journal/precedent-reverse.mdを読む — 本スキルの操作が承認された通常操作であることを確認するNOW: 現在のタスクが本スキルの適用範囲に該当するか確認するNEXT:../tool-index.mdを読み、ツールの可用性と実際のパスを検証するNEXT: ツールがない場合はbootstrapを呼び出し、パスを推測しないACT: 「ワークフロー」の最初のステップに入り実行する。確認状態で止まらないこと
適用範囲
以下のシナリオに該当するタスクで本スキルを使用する:
- カーネル/ドライバのPDB欠落 — 旧バージョンの ntoskrnl.exe にはシンボルがあるが、新版のPDBがMicrosoftによって削除されている場合。旧版のシンボルを使って新版の非エクスポート関数のアドレスを導出する。
- プログラム更新後のシンボル移行 — 過去にリバースエンジニアリングしたプログラムが更新され、再度解析することなく、旧版の結果を一括移行したい場合。
- 保護機構の更新 — 旧版に完全なリバース結果があり、新版で同じ関数の新しいオフセットを素早く特定する必要がある場合。
- 「旧版にシンボルあり + 新版にシンボルなし」のバイナリ比較シナリオ全般
他のスキルとの役割分担
| シナリオ | 使用するスキル |
|---|---|
| バイナリをゼロからリバース | ida-reverse/ または radare2/ |
| 旧版の結果があり、新版に移行 | 本スキル |
| 二つの全く異なるバイナリを比較 | BinDiff / Diaphora(従来ツール) |
コアアドバンテージ
従来手法との比較:
| 手法 | 200関数のコスト | 時間 | 精度 |
|---|---|---|---|
| 手動でIDAを2つ開いて比較 | 無料だが命を削る | 数時間 | 高い |
| BinDiff自動マッチ | 無料 | 速い | 中(構造変化が大きいと無効) |
| 全てをAgent(CC/Codex)に任せる | 50~100元 | 遅い | 高い |
| 本スキル(LLM一括比較) | 約1元 | 約10秒/関数 | 高い |
コア原理
旧版関数(シンボルあり) 新版同一関数(シンボルなし)
↓ ↓
逆アセンブル + 疑似コードのエクスポート 逆アセンブル + 疑似コードのエクスポート
↓ ↓
└──────── LLM 構造化比較 ────────┘
↓
YAML出力(シンボルマッピングテーブル)
↓
プログラムによる解析 → 新版IDBに一括適用
ポイント:
- prompt は固定テンプレートで、プログラム的に埋め込む
- 入出力形式が確定しており、プログラム的に解析可能
- LLMは「2つのコードを見比べて、対応関係を見つける」部分のみを担当
- 時間コストとトークンコストが非常に低い
Promptテンプレート
標準比較Prompt
I have disassembly outputs and procedure code of the same function.
This is the function for reference:
**Disassembly for Reference**
```c
{disasm_for_reference}
Procedure code for Reference
{procedure_for_reference}
This is the function you need to reverse-engineering:
Disassembly to reverse-engineering
{disasm_code}
Procedure code to reverse-engineering
{procedure}
What you need to do is to collect all references to "{symbol_name_list}" in the function you need to reverse-engineering and output those references as YAML.
Example:
found_vcall: # This is for indirect call to virtual function or virtual function pointer fetching.
- insn_va: '0x180777700' # Always be the instruction with displacement offset
insn_disasm: call [rax+68h] # Always be the instruction with displacement offset
vfunc_offset: '0x68'
func_name: ILoopMode_OnLoopActivate
- insn_va: '0x180777778' # Always be the instruction with displacement offset
insn_disasm: mov rax, [rax+80h] # Always be the instruction with displacement offset
vfunc_offset: '0x80'
func_name: INetworkMessages_GetNetworkGroupCount
found_call: # This is for direct call to non-virtual regular function.
- insn_va: '0x180888800'
insn_disasm: call sub_180999900
func_name: CLoopMode_RegisterEventMapInternal
- insn_va: '0x180888880'
insn_disasm: call sub_180555500
func_name: CLoopMode_SetSystemState
found_funcptr: # This is for non-virtual regular function pointer.
- insn_va: '0x180666600' # Must load/reference the function pointer target address
insn_disasm: lea rdx, sub_15BC910 # Must load/reference the function pointer target address
funcptr_name: CLoopMode_OnClientPollNetworking
found_gv: # This is for reference to global variable.
- insn_va: '0x180444400'
insn_disasm: mov rcx, cs:qword_180666600 # Must load/reference the global variable
gv_name: g_pNetworkMessages
- insn_va: '0x180333300'
insn_disasm: lea rax, unk_180222200 # Must load/reference the global variable
gv_name: s_EventManager
found_struct_offset: # This is for reference to struct offset. NOTE THAT virtual function pointer should not be here! virtual function pointer should ALWAYS be in found_vcall !
- insn_va: '0x1801BA12A' # Always be the instruction with displacement offset
insn_disasm: mov rcx, [r14+58h] # Always be the instruction with displacement offset
offset: '0x58'
size: 8
struct_name: CResourceService
member_name: m_pEntitySystem
If nothing found, output an empty YAML. DO NOT output anything other than the desired YAML. DO NOT collect unrelated symbols.
### 変数説明
| 変数 | ソース | 説明 |
|------|--------|------|
| `{disasm_for_reference}` | 旧版IDAエクスポート | シンボル付きの逆アセンブル |
| `{procedure_for_reference}` | 旧版IDAエクスポート | シンボル付きの疑似コード |
| `{disasm_code}` | 新版IDAエクスポート | シンボルなしの逆アセンブル |
| `{procedure}` | 新版IDAエクスポート | シンボルなしの疑似コード |
| `{symbol_name_list}` | 旧版から抽出 | 新版で位置を特定する必要があるシンボルのリスト |
## ワークフロー
### 完全な流れ
```text
Step 1: データ準備
- 旧版バイナリをIDAにロード(PDB/シンボルあり)
- 新版バイナリをIDAにロード(シンボルなし)
- 2つのバージョンで同じアンカー関数(エクスポート関数、文字列参照など)を見つける
Step 2: 一括エクスポート
- 旧版から:アンカー関数の逆アセンブル + 疑似コード(シンボル名を含む)
- 新版から:同じアンカー関数の逆アセンブル + 疑似コード(シンボル名なし)
Step 3: LLM比較
- promptテンプレートにデータを埋め込む
- LLM APIを呼び出す(推奨:deepseekは量が多く安い、超大関数はgptに切り替え)
- 返ってきたYAMLを解析する
Step 4: 結果の適用
- YAML内のシンボルマッピングを新版IDBに一括適用する
- idapro_rename または IDAPythonスクリプトで一括リネーム
Step 5: 反復
- 第一ラウンドで移行した関数が新しいアンカーになる
- これらの関数に入り、内部の呼び出しを引き続き比較
- 全ての目的関数をカバーするまで繰り返す
アンカー選択戦略
| アンカータイプ | 信頼性 | 説明 |
|---|---|---|
| エクスポート関数 | 最高 | 名前は変わらず、アドレスが変わる可能性あり |
| 文字列参照 | 高い | 文字列内容は変わらず、参照位置が変わる可能性あり |
| 定数/マジックナンバー | 中 | 特徴的な値は変わらない |
| コードパターン | 中 | 関数構造は似ているが、アドレスはすべて変わる |
一括処理の提案
- 1回の比較は1関数のみ(コンテキスト爆発を避ける)
- 中規模関数(<200行)にはdeepseekを使用
- 超大関数(>500行)はgpt-4o や claudeに切り替え
- 同時呼び出しで速度向上(10~20同時実行)
- 結果をキャッシュし、重複呼び出しを避ける
出力形式
YAML出力の5つのシンボルタイプ
| タイプ | 意味 | キーフィールド |
|---|---|---|
found_vcall | 仮想関数呼び出し(間接call) | vfunc_offset, func_name |
found_call | 直接関数呼び出し | insn_va, func_name |
found_funcptr | 関数ポインタ参照 | insn_va, funcptr_name |
found_gv | グローバル変数参照 | insn_va, gv_name |
found_struct_offset | 構造体オフセット参照 | offset, struct_name, member_name |
解析後の適用アクション
found_call → idapro_rename(addr=call_target, name=func_name)
found_vcall → idapro_set_comments(addr=insn_va, comment="vcall: {func_name} @ +{offset}")
found_funcptr → idapro_rename(addr=funcptr_target, name=funcptr_name)
found_gv → idapro_rename(addr=gv_addr, name=gv_name)
found_struct_offset → idapro_set_comments(addr=insn_va, comment="{struct_name}.{member_name}")
典型的なシナリオ例
シナリオ1:ntoskrnl.exe のPDB欠落
既存: ntoskrnl.exe 10.0.26100.2000 + 完全なPDB
目標: ntoskrnl.exe 10.0.26100.2605(PDBが削除されている)
要件: PspSetCreateProcessNotifyRoutine の新しいアドレスを特定
手順:
1. 両方のバージョンをIDAにロード
2. エクスポート関数 PsSetCreateProcessNotifyRoutine を見つける(両方のバージョンに存在)
3. 旧版では、この関数が PspSetCreateProcessNotifyRoutine を呼び出している(シンボルあり)
4. 新版では、この関数が sub_140822108 を呼び出している(シンボルなし)
5. LLMが一瞬で見抜く: sub_140822108 = PspSetCreateProcessNotifyRoutine
6. 一括適用
シナリオ2:アプリケーション更新後の移行
既存: target.exe v1.0 の完全なリバース結果(200以上の関数が命名済み)
目標: target.exe v1.1(全てのシンボルが消失)
要件: 200個の関数名を一括移行
手順:
1. 旧版から、名前が付いている全ての関数の逆アセンブル+疑似コードをエクスポート
2. 新版で、エクスポート関数/文字列を介して対応するアンカーを見つける
3. 一括でLLM比較を呼び出す
4. YAMLを解析し、一括でリネーム
5. 反復して深堀り
LLM選択の提案
| モデル | 適したシナリオ | コスト | 速度 |
|---|---|---|---|
| DeepSeek V3 | 中小関数(<200行)、一括処理 | 非常に低い | 速い |
| GPT-4o | 超大関数、複雑な制御フロー | 中 | 速い |
| Claude Sonnet | 中~大関数、推論が必要 | 中 | 速い |
| Claude Opus | 非常に複雑な関数、深い理解が必要 | 高い | 遅い |
推奨戦略:デフォルトはDeepSeek、コンテキスト超過や結果が不正確な場合に自動アップグレード。
注意事項
- バイナリ全体をLLMに渡さない — 一度に比較するのは1関数のみ
- アンカーは信頼できるものでなければならない — アンカー自体が間違っていると、以降全て無駄になる
- 結果は人間がスポットチェックする — LLMは100%正確ではないため、重要なシンボルは検証すること
- 中間結果をキャッシュする — 重複呼び出しでトークンを無駄にしない
- コンテキスト制限に注意 — 超大関数(>1000行の逆アセンブル)は分割するか、大コンテキストモデルを使うこと
オンデマンドブートストラップ
ツール依存関係
| ツール | 用途 | 自動インストール可否 |
|---|---|---|
| IDA Pro | 逆アセンブル/疑似コードのエクスポート | ✗(商用ソフトウェア) |
| Python | スクリプト実行、API呼び出し | ✓ |
| PyYAML | LLMが返すYAMLの解析 | ✓(pip install pyyaml) |
| LLM API | 比較の実行 | APIキーが必要 |
説明
本スキルの中核は重量ツールのインストールに依存せず、以下に依存する:
- IDA Proが既にある(
ida-reverse/スキルで管理) - Python + requests/httpx(API呼び出し)
- LLM APIエンドポイント
ルーティングコンテキスト
上流エントリ: skills/SKILL.md(全体制御)、routing.md
トリガー条件: 旧版のシンボル/リバース結果が存在し、新版に移行する必要がある場合
下流出口:
- バイナリを開く必要がある場合 →
ida-reverse/ - バージョン差分を素早く偵察・確認する必要がある場合 →
radare2/
同階層の関連モジュール: ida-reverse/(データエクスポートとシンボル適用はどちらもIDAを通じて行う)
タスク完了セルフチェック(完了宣言前にMUST合格)
- ワークフローの各ステップを実行したか(単に読んだだけではない)?
-
tool-indexに基づき、実際のツールパスを使用したか? - 再現可能な証拠(コマンド/スクリプト/スクリーンショット/レポート)を生成したか?
- RULESで要求されているチェックリスト項目を完了し、書き戻したか?