エグゼクティブ サマリー
2024年後半、弊社はSLOW#TEMPESTキャンペーンに関連するマルウェアの亜種を発見しました。この調査記事は、マルウェア作成者が採用する難読化テクニックを探るものです。これらのマルウェアのサンプルを深く掘り下げることで、難読化テクニックを検出し、対抗するうえで使用できるメソッドとコードを紹介します。
このような進化する手口を理解することは、セキュリティ担当者が強固な検知ルールを開発し、高度化する脅威に対する防御を強化するために不可欠とされています。
弊社では、SLOW#TEMPESTキャンペーンで脅威アクターが使用した以下のテクニックに注目しました。
- 動的ジャンプを用いた制御フローグラフ(CFG)の難読化
- 難読化された関数呼び出し
パロアルトネットワークスのお客様は、以下の製品を通じて、本書で取り上げるツールに対する確実な保護を構築いただけます。
情報漏えいの可能性がある場合、または緊急の案件がある場合は、Unit 42インシデント レスポンス チームまでご連絡ください。
Unit 42の関連トピック | 反分析, DLLサイドローディング |
背景
本記事は、SLOW#TEMPESTキャンペーンから入手したマルウェア サンプル(SHA256ハッシュ: a05882750f7caac48a5b5ddf4a1392aa704e6e584699fe915c6766306dae72cc)の最近の亜種を分析するものです。攻撃者はマルウェアをISOファイルとして配布しており、これは複数のファイルをバンドルして最初の検知を逃れるためによく使われる手法です。このISOファイルには11のファイルが含まれており、2つは悪意のあるもので、残りは良性のものとされています。
最初の悪意のあるファイルはzlibwapi.dllであり、ここではローダーDLLと呼ぶことにし、埋め込まれたペイロードを解読して実行します。ペイロードは、一般的な方法ではローダーDLLに統合されません。代わりにipc_core.dllとして別のDLLの末尾に追加されます。
ローダーDLLは、DingTalk.exeという正規の署名付きバイナリによってDLLのサイドローディングを介して実行されます。DLLのサイドローディングとは、攻撃者が正規のプログラムを使って悪意のあるDLLファイルをロードし、正規のプログラムに攻撃者のコードを実行させる手法です。ペイロードをローダーDLLから分離すると、悪意のあるコードはローダーとペイロードの両方のバイナリが存在する場合にのみ実行されるため、検出が複雑になります。
以下のセクションでは、マルウェア作成者がローダーDLL内のコードを難読化するために使用したアンチ解析テクニックの掘り下げを行います。
動的ジャンプを用いた制御フローグラフ(CFG)の難読化
CFG難読化はプログラム命令の実行順序を変更し、静的・動的解析をより困難にするものです。そのため、プログラムのロジックを理解し、悪意のある機能を特定し、効果的なシグネチャ ベースの検出を行うことが難しくなります。
静的解析では、線形シーケンスや予測可能な制御フローに依存する従来のツールは効果がありません。また、誤解を招くような実行パスが実際の悪意のある操作を不明瞭にするため、動的解析も難しくなるとされています。CFGの難読化は、オリジナルのソースコードやコンパイルされたコードと実行時のコードとの間のマッピングを壊し、信頼性の高い検出ルールを作成することを著しく困難にします。
このテクニックを実証するうえで、特にローダーDLLのメイン関数への動的ジャンプを使用したCFG難読化の適用を分析しました。図1は、この関数のCFGを難読化ありと難読化なしの両方で示したものです。難読化を取り除くと、連続的なコードフローが明らかになり、2色の線で示されます。
- 緑:正しい分岐
- 赤:誤った分岐

この機能は広範囲に及び、17,000行を超えるアセンブリ命令で構成されています。ローダーDLLのメイン関数のサイズを考慮し、解析速度を上げるためにHex-Raysデコンパイラーを使用しました。しかし、Hex-Raysデコンパイラでは、図2に示すように、同じメイン関数に対して10行の擬似コードしか生成できませんでした。

Hex-Raysデコンパイラの出力が不完全なのは、サンプルが動的ジャンプ命令を使っているからです。動的ジャンプとは、コード内のターゲット アドレスを実行時に計算するものであり、
固定アドレスへの直接的なジャンプとは異なり、動的ジャンプでは、デコンパイラがプログラムを実際に実行することなく実行フローを決定することは不可能とされています。このように、明確で決められたパスがないため、デコンパイラが元の高レベルコードを再構築する能力が著しく低下し、不完全または不正確なデコンパイル結果になることが多くなります。
図3は、ローダーDLLのメイン関数のエントリー ポイント付近で、JMP RAX命令を使用した動的ジャンプの1つを示したものです。JMP命令は、実行フローを別のターゲット アドレスに迂回させています。
ターゲット アドレスは、メモリの内容、レジスタの値、実行中に行われる条件チェックの結果などの要因に依存します。この場合、先行する一連の命令によって実行時に計算され、CPUレジスタRAXに格納されます。

弊社はまず図4に示されるIDAPythonスクリプトを使ってこれらのジャンプのインスタンスをすべて特定することで、動的ジャンプを採用したCFG難読化に対抗しました。

上記のスクリプトを使用し、ローダーDLLのメイン関数で10個の動的ジャンプを特定することに成功しました。
動的ジャンプ ターゲットは、各JMP RAX命令の前にある「ディスパッチャー」と呼ばれる9つの命令シーケンスによって決定されます。これら10個のディスパッチャーは、ローダーDLLのメイン関数内にあり、同じような構造を共有しています。しかし、各ディスパッチャーは、ジャンプ先のアドレスを計算するために個別の命令セットを使用し、プログラムの制御フローを効果的に隠しています。
これを分かりやすく理解するために、プログラムが複雑なダンスのルーティンだと想像してみてください。通常、ダンサーはあるステップから次のステップへと予測通りに動くことが期待されます。しかしこの場合、プログラムには 「ジャンプ ポイント」が隠されています。各ジャンプの前には、秘密の握手のようなミニ ルーチンがあり、次のジャンプの着地点を正確に決めます。これらの秘密の握手はすべて少しずつ異なっており、結果としてダンスの真の道筋を予測するのは非常に難しくなり、すべて事前にプログラムされているにもかかわらず、ダンサーたちは即興で次にどこへ行くのかを考えているかのように映るというわけです。
各ディスパッチャーは双方向の分岐メカニズムを実装しています。ディスパッチャーが入力されたときのゼロ フラグ(ZF)またはキャリー フラグ(CF)の状態によって、実行されるコードパスは変わります。これらのフラグは、演算の結果(ゼロやオーバーフローなど)を示すために前の命令によってセットされ、どの分岐を取るかを決定するものです。各ディスパッチャーには、条件付き移動(CMOVNZ)またはセット(SETNL)命令と間接ジャンプ(JMP RAX)のペアがあります。このため、実行時の条件やメモリの内容に依存する動的な制御フローが生まれ、静的解析が難しくなるとされています。
ZFとCFは、算術演算と論理演算の結果を反映するCPUのステータス フラグです。これらのフラグは内部スイッチとして機能し、事前の計算結果に基づいた動的なプログラム実行を可能にします。各条件付き移動またはセット命令には、2つの可能なターゲットアドレスがあり、1つは真の条件、もう1つは偽の条件です。例えば、ゼロでない場合の条件付き移動(CMOVNZ)命令は、ZFが0の場合にのみデータを移動します。ZFが1の場合(前の結果が0であったことを意味する)、CMOVNZ命令はデータを移動せず、異なるターゲットアドレスに実行を継続します。
図5はディスパッチャーの1つです。宛先アドレスがどのように計算されるかを説明するために、インストラクションに注釈をつけています。

マルチ プラットフォーム マルチ アーキテクチャCPUエミュレータ フレームワークであるUnicornを使用することで、宛先ジャンプ アドレスの特定が自動化されます。これは、バイナリ全体を実行するのではなく、各JMP RAXに先行する9つの命令を制御された方法で実行することによって達成されます。これにより、各ディスパッチャーのジャンプ アドレスを決定することができます。
これらの命令のバイトコードを抽出するために、図6に示すコードを使用します。

次に、それぞれのディスパッチャーをエミュレートします。各ディスパッチャは、2つのターゲット アドレスを持つ双方向分岐メカニズムを使用しているため、両方の宛先アドレスを決定するために、各ディスパッチャでエミュレーション プロセスを2回繰り返す必要があります。図7はディスパッチャーのエミュレートに使われたコードを示したものです。

2つのデスティネーションアドレスを計算した後、ディスパッチャー命令をこれらのアドレスへのダイレクトジャンプ命令に置き換え、CFGの難読化を効果的に取り除くことに成功しました。これにより、IDA Proでオリジナルのコードフローを簡単に見ることができるようになりました。図8は、IDAデータベース内のインストラクションにパッチを当てるコードを示したものです。

最後に、図9に示すコードを使用して、パッチを当てた関数全体をIDA Proに再分析させました。これはIDA Proが難読化解除された命令に基づいてCFGを更新するためのトリガーとなりました。

動的ジャンプを使用したCFG難読化解決スクリプトの完全版はemu_jmp_rax_idapython.pyでごご確認いただけます。
このスクリプトを実行すると、Hex-RaysデコンパイラがローダーDLL内のメイン関数のデコンパイルに成功しました。図10にデコンパイル出力の一部を示します。

しかしながら、コードにはさらなる難読化が残っていることが確認されました。具体的には、ほとんどの関数は動的に呼び出され、Windows APIが直接呼び出されることはありませんでした。このため間接的な関数解決によって実際の機能が不明瞭になり、コードの目的を即座に見分けることが難しくなっています。
難読化された関数呼び出し
難読化された関数呼び出しでは、関数をその名前で直接呼び出すのではなく、関数のアドレスを実行時に動的に計算し、ポインタを介して呼び出す間接呼び出しが使用されます。攻撃者はこのテクニックを使って静的解析を妨げ、実際の標的関数がコードの中ですぐに明らかにならないようにしていました。これによりプログラムの動作を理解し、悪意のある動作を特定することが難しくなっています。
main 関数のアセンブリコードを解析した結果、複数の難読化された関数呼び出しの存在が明らかになりました。CALL RAX 命令は、関数アドレスがコード内で直接指定されるのではなく実行時に動的に決定されることを示しており、これが重要な手がかりとなります。動的ジャンプと同様、これらの関数呼び出しのターゲットアドレスも実行時に計算されていました。そのため、バイナリを実行せずにターゲットアドレスを特定することはできませんでした。図11に、難読化された関数呼び出しの例をいくつか示します。

難読化された関数コールのターゲット アドレスを決定するために、弊社では動的ジャンプに使用したものと同様のアプローチを適用しました。これは、どちらの手法も実行時にターゲットアドレスを計算することになるからです。
このスクリプトは、難読化された関数呼び出しの宛先アドレスを計算することに成功しました。しかし、図12が示すように、宛先アドレスが正しく解決されているにもかかわらず、IDA Proが標準Windows APIの引数を識別できないことが確認されました。これはWindows APIのアドレスと難読化された関数コールをリンクする関数シグネチャ情報が欠落していたためです。

IDA Proが関数の引数を正しく識別し、ローカル変数の名前を変更し、適切な解析を実行できるようにするには、図13に示すコードを使用して、難読化された各関数呼び出しの「callee」アドレスを明示的に設定する必要がありました。実施した結果、IDA Proはその関数を既知のWindows APIとして認識するために必要な情報を得ることに成功しました。

図13に示すコードを追加してキャリー アドレスを設定すると、IDA Proは難読化された各関数呼び出しに対して、関数の引数に自動的にラベルを付け、ローカル変数の名前を変更します。これによりコードを読んで分析する能力が大幅に向上し、関数の目的をより簡単に理解できるようになりました。図14は、IDA Proがラベル付けした関数引数の一部を示したものです。

難読化された関数呼び出しを解決するための完全なスクリプトはemu_call_rax_idapython.pyで確認できます。
このスクリプトを実行した結果、ローダーDLL内の制御フローと関数コールの両方を難読化解除することに成功しました。コードの可読性が大幅に向上し、Windows APIコールが適切に特定されたので、調査をコア機能の分析に進めることができるようになりました。最後のセクションでは、ローダーDLLの主な目的を検討します。
ローダーDLLの解析
emu_call_rax_idapython.pyとemu_call_rax_idapython.pyスクリプトを使用して難読化を取り除いた後、ローダーDLLの主な機能を簡単に見つけることができました。
まず、Windows APIのGlobalMemoryStatusExを使用して、システムで利用可能な物理メモリの合計を決定する、サンドボックス防止チェックを観察しました。ローダーDLLは、ターゲットマシンに6GB以上のRAMがある場合にのみ、そのペイロードを解凍し、メモリ上で実行するものです。図15にローダーDLLのコアコンポーネントの擬似コードを示します。

結論
SLOW#TEMPESTキャンペーンの進化は、マルウェアの難読化技術、特に動的ジャンプと難読化された関数呼び出しに焦点を当てています。これはセキュリティ専門家が最新のマルウェアを効果的に解析し理解するうえで、静的解析と並行して高度な動的解析技術(エミュレーションなど)を採用する重要性を強調するものです。
これらのテクニックを使ったSLOW#TEMPESTキャンペーンの成功は、高度な難読化が組織に与える潜在的な影響を実証しており、検知と緩和を著しく困難なものにしています。脅威アクターがこれらの手法をどのように活用しているかを理解することは、強固な検知ルールを開発し、複雑化する脅威に対する防御を強化する上で極めて重要です。
パロアルトネットワークスのお客様は、以下の製品を通じて、上記の脅威に対する確実な保護を構築いただけます。
- Advanced WildFireは、この記事で取り上げたマルウェアのサンプルを検出することができます。
- Cortex XDRおよび XSIAMは、既知の悪意のあるマルウェアの実行を防止し、振る舞い脅威防御とローカル分析モジュールに基づく機械学習によって未知のマルウェアの実行も阻止できるように設計されています。Cortex Shellcode AIモジュールは、シェルコード攻撃の検出と防止に役立ちます。
情報漏えいの可能性がある場合、または緊急の案件がある場合は、Unit 42インシデント レスポンス チームまでご連絡ください。
- 北米:フリーダイヤル: +1 (866) 486-4842 (866.4.UNIT42)
- 英国: +44.20.3743.3660
- ヨーロッパおよび中東: +31.20.299.3130
- アジア: +65.6983.8730
- 日本: +81.50.1790.0200
- オーストラリア: +61.2.4062.7950
- インド: 00080005045107
パロアルトネットワークスは、本調査結果をサイバー脅威アライアンス(CTA)のメンバーと共有しています。CTAの会員は、この情報を利用して、その顧客に対して迅速に保護を提供し、悪意のあるサイバー アクターを組織的に妨害しています。サイバー脅威アライアンスについて詳細を見る。
侵害のインジケーター
- SHA256 hash: a05882750f7caac48a5b5ddf4a1392aa704e6e584699fe915c6766306dae72cc
- ファイルサイズ: 7.42 MB
- ファイルの説明: SLOW#TEMPESTキャンペーンで配布されたISOファイル
- SHA256ハッシュ:3d3837eb69c3b072fdfc915468cbc8a83bb0db7babd5f7863bdf81213045023c
- ファイルサイズ: 1.64 MB
- ファイルの説明: ペイロードのロードと実行に使用されるDLL
- SHA256ハッシュ:3583cc881cb077f97422b9729075c9465f0f8f94647b746ee7fa049c4970a978
- ファイルサイズ: 1.64 MB
- ファイルの説明: オーバレイ セグメント内の暗号化されたペイロードを持つDLL
参考文献
この記事で言及されている難読化解除スクリプトへのリンク集:
- Python xcript: emu_call_rax_idapython.py - Unit 42 GitHub, パロアルトネットワークス
- Python スクリプト:emu_jmp_rax_idapython.py - Unit 42 GitHub, パロアルトネットワークス