はじめに
FFRIセキュリティ ソフトウェアエンジニアの松本です。 PlugX というバックドアは非常に古くから存在しており、現在でも利用されています。 今回はその PlugX のローダーの変遷について見ていきます。
PlugXの概要
PlugX は標的型攻撃に利用されている Remote Access Tool(以下 RAT と記載)であり、主に政府機関などを狙う標的型攻撃に利用されるツールです。 機能としては情報収集・窃取、侵入後の PC のコントロールなどがあります。
2012 年には既に TrendMicro の記事[1]があるほど PlugX は古くから存在します。 その一方、ここ数か月の間にも TA416 (通称 Mustang Panda[2]) というグループが利用しているという報告[3]が上がっております。
日本国内では株式会社 JTB が PlugX に感染し情報漏洩したという事例が存在します[5][6]。 また、株式会社ジャストシステムのワープロソフトである一太郎の脆弱性を利用し感染を狙う事例もありました[7][9]。
PlugX ローダーは世代に関わらず以下のような流れで動作しています。
- ドロッパーを添付したメール経由などにより、対象組織内部でドロッパーを実行してもらう
- ドロッパーが①正規 exe、②マルウェア dll、③バイナリファイルの 3 つを生成する
- ドロッパーが①正規 exe を実行する
- ②マルウェア dll は①正規 exe が利用する正規 dll を偽装しており、①正規 exe はそのまま②マルウェア dll を読み込む
- ②マルウェア dll が③バイナリファイルを読み込む
- ③バイナリファイルが子プロセスを生成し、次の段階に移る
読み込まれるバイナリファイルの内部に暗号化されたコードなどが入っているため、実際にはその複合など更に細かい動作が存在しますが、動作の流れは以下の概要図のようになります。
実際にドロップされる具体例としては下の図のようになります。 この図では RasTls.exe は正規の exe であり、RasTls.dll へのリンクが存在しています。 マルウェアバイナリにもアイコンが付いていますが、これは本来 msc が Microsoft 管理コンソールに紐付けられている拡張子であるためです。 実際の中身は全く関係無いバイナリファイルとなっています。
exe が不正な dll を読み込んでしまう理由は、exe が dll を読み込む際に同じディレクトリの dll を優先する仕様のためです。
次の図は、LoadLibraryW 関数で同ディレクトリの dll を動的に読み込む箇所です。
このような動的な dll 読み込みの際、通称 exe は自身の現在のパスから読み込みたい dll のパスを生成しますが、 PlugX は同ディレクトリ内にファイルを生成するため、結局すり替えられた dll を読み込むことになります。
この手法は DLL Side-Loading[4]と呼ばれ、デジタル署名された正規の exe を利用することで、アンチウイルスソフトの検知回避によく利用されます。
一般に dll 内部の悪意のあるコードは entry 関数か、exe から必ず呼ばれる export 関数のどちらかに実装されていますが、PlugX は entry 関数に悪意のあるコードが実装されています。 ここで dll における entry 関数は DllMain 関数であり、リンクなどで読み込まれた段階で必ず呼ばれる処理です。 そのため、exe の初期化処理である dll 読み込みの段階で PlugX ローダーが動作します。
次の段階では、子プロセスもしくはサービス登録によるサービスプロセスを生成します。 実行プロセスに管理者権限が付与されているときには、サービスプロセスを生成し、管理者権限がない時には、子プロセスを生成します。
さて、ここまでは全世代の PlugX 共通な動作の流れについて説明しましたが、次に世代によって変化してきた内容を説明します。
PlugXの変化
PlugX は時間と共に進化していきました。 その流れを簡潔に纏めると以下のようになります。
年代 | 世代 | 追加機能の例 | 引用 |
---|---|---|---|
2012 | 第一世代 |
|
[1] |
2013 | 第二世代 |
|
[8][9] |
2015 | 第三世代 |
|
[10] |
2017 | 第四世代 |
|
[11] |
現状 | 第五世代 |
|
[3][12] |
第一世代
PlugX では前述通り、まずは正規 exe から dll が読み込まれ、dll からバイナリを読み込んだ上で次のプロセスを生成するという動作をします。 まず注目すべきは、マルウェアバイナリのコード内部でスタック上に文字列を生成する stack strings という手法が利用されている点です。 この手法はバイナリに文字列を直接埋め込まないため、解析をより困難にします。
stack strings は x86 の mov 命令や lea 命令を利用してスタック上に文字列を手動で生成する方法です。
上の図であれば、4 byte 長の mov 命令を利用することでスタック上に文字列を構築できます。 構築後に esp レジスタが指している箇所を文字列として扱えば Windows API などで認識できる文字列になります。 上記の例では char 型でしたが、wchar_t 型でも同様に作成可能です。
スタック上に生成された文字列はバイナリ内部で利用する API のアドレスを動的に取得するために利用されます。
ここで LZNT1 復号に利用する RtlDecompressBuffer という関数も動的解決しています。 ここで得た LZNT1 復号と XOR 復号により、子プロセスの生成に進みます。
この復号により、メモリ内部に PE ヘッダーを持つコード領域を展開することが確認できます。
第二世代
第二世代は機能面の変化の他、メモリ内部で復号される PE ファイルのマジックナンバーが PE ではなくなっているといった変更点が報告されています[8][9]。 ドロッパーに自己解凍書庫が利用されているパターンもこの辺りから出てきます。 ここではローダー部分の変化について見ていきます。
マルウェア dll には上から順番にディスアセンブルすると、実際に実行される命令とは異なってしまうという難読化が施された関数も追加されています。
赤枠は以下のような役割を担っています。
- 二行続けて同じ所にジャンプするように指定されているため、無条件ジャンプと同等
- その直後の call 命令は、直前のジャンプを無条件ジャンプと解釈できなかった為に発生
- e8 から命令を開始することで、意図的にコード外にジャンプする不正な call 命令と解釈させる
他にも、不要な XOR なども追加されており、処理を追いかけにくい構造になっています。
これらの難読化処理は Ghidra のデコンパイル結果にも影響しますが、除去整理することで以下のようにデコンパイル結果が綺麗になります。 デコンパイル結果から dll 内部のバイナリを復号するコードが出てきたことが確認できます。
この処理以降は第一世代と同じ stack strings を利用した API 動的解決の処理に入ります。 RtlDecompressBuffer も LZNT1 復号に利用されており、XOR 復号のコードは変わっているもののこの辺りのローダーとしての処理は第一世代と同じと見て良いでしょう。
第一世代と同じく PE ヘッダーを持つバイナリが復号されメモリに展開されますが、マジックナンバーが改変されている物となっております。
第三世代
第三世代の機能面における変化は P2P 機能の搭載などが報告されています[10]。
しかし、ローダー部分に関しては XV というマジックナンバーの出現個数の変化などの変更はあるものの、 XOR 復号はもちろん大まかな流れには変化が無いように見受けられました。
第四世代
PoisonIvy のコードを一部流用している[11]ということで第四世代として分類されるこの世代では、 ドロッパー自体は第三世代から引き続き自己解凍書庫であるものの、ローダー部分にも多くの変更が入っています。 例えば、簡単なものであればマルウェア dll の entry 関数から早速 NOP、DEC、INC 命令を使った無駄な処理を大量に配置しています。
ただし、Ghidra のデコンパイル結果には影響しておらず、比較的除去が簡単な処理になっています。
また、今までの世代では VirtualAlloc で取得したメモリへの展開は memcpy を利用していましたが、この世代からはバイナリファイルを直接 ReadFile で読み込む処理に変わっています。 ReadFile 経由の読み込みはユーザーランドにおける WinDbg のハードウェアブレークポイントでは止まらないので、 メモリアドレスへの書き込みイベントで止めることが出来ず、より解析が困難になっています。
WinDbg のクエリ結果から見ても、突然メモリに 0x3f の値が出てきていることが確認できます。
上記のクエリ結果の中にある Write は復号処理であることが確認できます。
また、子プロセス生成までに必要な API の取得において stack strings は利用しなくなり、代わりに随時ハッシュ値から関数アドレスを復元する形になっています。
この関数アドレス取得の手法は子プロセス生成後にも沢山利用されております。 この手法は Poison Ivy と酷似していることが報告されています[11]。
API 解決に必要なモジュールのアドレス解決においては、対象のモジュール名がメモリ上にそのまま存在しています。
現在
PlugX と呼ばれる検体は現在も変化し続けていますが、 基本的に正規 exe、マルウェアである dll、そしてバイナリファイルを同じディレクトリに配置するというドロップ方式は変わっていないようです。
一方で解析をより困難にするため C2 サーバーと接続出来なければ次の段階に進まないような改良が見られるようになりました。
また、Go 言語製の PlugX ローダーが存在していることも報告されています[11]。 この dll のインポート関数からも確認できます。
この Go 言語製の dll も C2 サーバーと接続できなければ動作しないようになっていたため、 現在の主流は解析を困難にするため、 C2 サーバー側にドロップさせたい物を配置する形式になってきたと思われます。
終わりに
PlugX は長い活動期間を通して、機能の高度化以外にもローダー部分における難読化も施していることが確認できました。 こうした変化によりマルウェア解析用のスクリプトが正常に動作しないこともあり、検知を回避し、解析の妨害に一役買っています。
今回調査した PlugX の検体など多くのマルウェアは、以上のような検知回避や難読化などを行っておりますが、FFRI yarai は検知エンジンの新規開発や改善などを継続的に行うことで、検知可能となっておりますのでご安心ください。 (※ただし、全ての PlugX が検出できることを保証するものではありません)。
エンジニア募集
FFRIセキュリティではマルウェアの解析などセキュリティの研究開発を行っています。採用に関しては こちら をご覧ください。
参考文献
[1] 標的型攻撃に利用されるPlugXの脅威とは | トレンドマイクロ 脅威データベース
[2] Mustang Panda, TA416, RedDelta, BRONZE PRESIDENT, Group G0129 | MITRE ATT&CK®
[4] Hijack Execution Flow: DLL Side-Loading, Sub-technique T1574.002 - Enterprise | MITRE ATT&CK®
[5] JTB、約793万人分の個人情報流出の恐れ - 有効パスポート番号4,300件含む | マイナビニュース
[6] 「PlugX」はどんなマルウェア? JTBを狙った標的型攻撃をファイア・アイが解説 | マイナビニュース
[7] 一太郎の脆弱性を突くマルウェア、人事情報装うメールで日本に「着弾」 | ITmedia エンタープライズ
[8] PlugX - The Next Generation | Sophos
[9] From the Labs: New PlugX malware variant takes aim at Japan | Naked Security
[10] マルウエアPlugXの新機能 (2015-01-22) - JPCERT/CC Eyes | JPCERTコーディネーションセンター公式ブログ
[11] Poison Ivyのコードを取り込んだマルウエアPlugX(2017-01-12) - JPCERT/CC Eyes | JPCERTコーディネーションセンター公式ブログ
[12] TA416 Goes to Ground and Returns with a Golang PlugX Malware Loader | Proofpoint US