Needle:マルウェアが自ら鍵を漏らしたモジュール型仮想通貨窃取C2の内部

調査メモ:この基盤の全容を調べるために使用したAPIキーは、MalwareBazaarで一般公開されていたマルウェアから抽出したものです。これは、脅威アクター自身が自らのエージェントに組み込み、感染端末からの通信に使っていた認証情報そのものです。ここで報告する統計情報以外の被害者データは一切保持しておらず、また設定の変更も一切行っていません。


TL;DR

  • 今回の分析は、当社のエージェント型CTIプラットフォームであるCaronteが自律的に実施したもので、MalwareBazaarの1件のサンプルから、C2の特定、被害者の洗い出し、オンチェーンでの資金追跡まで、手動でのリバースエンジニアリングを一切介さずに数分で完了しました
  • Needleは現在も稼働中のMaaS型仮想通貨窃取プラットフォームで、2つのモジュールが稼働中です。MetaMask、Phantom、Trust Walletを標的とするブラウザ拡張機能なりすましモジュールと、Exodus、Trezor、LedgerになりすますRust製デスクトップエージェントです
  • このRust製エージェントは、C2用のAPIキーを何の保護もなく内部に埋め込んでいました。このキーを利用して、1,932件の被害者と、6つのブロックチェーンにまたがる攻撃者の出金設定全体を洗い出すことができました
  • パネルのReact SPAは、認証処理を完全にクライアント側だけで行っていました。構造的に妥当なトークンをlocalStorageに書き込むだけで管理ダッシュボード全体が表示され、UIルーティング層にはサーバー側の検証が一切ありませんでした
  • フロントエンドのバンドルには、出金設定を書き換えるためのエンドポイントが定義されており、これは読み取り用と同じエージェントキー認証方式を使用していました。つまり、感染端末から送信されているのと同じ認証情報を使えば、今後のすべての自動出金を書き換えられる可能性があります

主な調査結果の概要

項目 詳細
サンプル Rust製PE(Windows)、v1.3、SHA256: d6ca3760...a490
C2スタック React 19 SPA・Express.js API・nginx 1.29.8
稼働中モジュール デスクトップウォレットなりすまし・ブラウザウォレットなりすまし
被害者数 合計1,932件(ブラウザ拡張機能111件、デスクトップセッション1,821件)
標的ウォレット MetaMask・Trust・Phantom・OKX・Exodus・Trezor・Ledger・Atomic・Guarda・Zelcore
キャンペーン期間 2026年4月18日から活動中、本稿執筆時点でも継続中
認証面の不備 無防備なエージェントキー・クライアント側のみの認証・認証なしのビルダー
C2基盤 130.12.180.135(ポート3000、8080、8181)
確認された資金移動 EVMホットウォレット:約148ドル相当のETHをコールドストレージへ転送

発見の経緯:MalwareBazaarからC2全体の可視化まで

Windows向け仮想通貨窃取マルウェアとフラグ付けされたサンプルが、MalwareBazaarに出現しました。私たちはこれを、当社のエージェント型CTIプラットフォームであるCaronteに投入しました。このサンプルをアップロードするだけで、Caronteは分析パイプライン全体を自律的に実行し、手動でのリバースエンジニアリングは一切必要ありませんでした。

Caronteはまずバイナリの分類から着手しました。シンボル情報を除去済みのRust製PEで、サイズは8.9MB、署名なし、エントロピー6.71。MalwareBazaar上ではRustyStealerと分類されており、Phorpiexスパムボットネット経由で配布されていました。特徴的な文字列パターンからeguiデスクトップGUIフレームワークを特定し、Rustのパニックハンドラのパスを辿ることでウォレットなりすましの標的(zelcoretrezorledger)を復元。さらにバイナリのデータセグメントから埋め込み設定のスキーマを再構築しました。

静的なリバースエンジニアリングでは、ウォレットなりすまし以外の機能も明らかになりました。GetAsyncKeyStateによるキーロギング(シードフレーズを送信時だけでなく入力の都度キャプチャしていることと一致)、Windowsのレジストリ「Run」キーによる永続化、そしてSetUnhandledExceptionFilterやソフトウェアブレークポイントトラップを含むデバッグ対策です。C2のIPアドレスの解決先はASN 202412(オランダ・アムステルダムのOmegatech LTD)で、既知のバレットプルーフホスティング事業者であり、分析時点でOTX上に27件の悪性パルスの記録がありました。

この分析全体からは、94ノード・135エッジから成る脅威グラフが生成され、ローカルおよびMalwareBazaarのルールセット全体で40件のYARAルールにマッチしました。これには、キーロギング、アンチVM回避、Rust製ステーラーのパターンに関する高深刻度の検出結果も含まれます。サンプルを投入してから数分のうちに、Caronteはプラットフォームの正体、C2のアドレス、APIキー、そして約2,000件の被害者を管理する稼働中のパネルという、完全な運用者プロファイルを作り上げていました。

Caronteが自律的に生成した成果物

成果物 内容
バイナリ分類 シンボル除去済みRust製PE、8.9MB、RustyStealerファミリー
フレームワークの特定 eguiによるGUI、Phorpiex経由の配布
機能の抽出 キーロギング、永続化、アンチデバッグ
埋め込み設定の復元 バイナリのデータセグメントからC2のURLとAPIキー
基盤の帰属特定 ASN 202412 Omegatech、OTXパルス27件
脅威グラフ 94ノード、135エッジ
検出範囲 YARAルール40件がマッチ
分析にかかった時間 手動リバースエンジニアリングなら数日かかるところを数分で完了

ここから先の内容は、Caronteが自律的に出力した成果を土台に、人間が検証・掘り下げを行った部分、すなわちヒューマン・イン・ザ・ループの工程です。


Needleとは何か

Needleは、モジュール型のMaaS(Malware-as-a-Service)プラットフォームです。パネルは購入可能なモジュールを軸に構成されており、運用者は必要なモジュールだけを有効化し、基盤自体は共有されます。今回の分析では、2つのモジュールが稼働していました。

モジュール 状態 説明
Needle Core 非稼働 フォームグラバー、クリッパー、システムコア
Extension Base 非稼働 サイトの差し替え、バックアップドメインの管理
デスクトップウォレットなりすまし 稼働中 Rust製エージェント、偽ウォレットUI、シードフレーズの取得
ブラウザウォレットなりすまし 稼働中 拡張機能の差し替え、パスワードとシードの傍受
Farm 非稼働 残高が一定額に達すると出金をトリガーする遅延出金機能
Launch Panel 非稼働 トラフィック管理とランチャー生成
Captcha (Win+R) 非稼働 ソーシャルエンジニアリングによる配布手段

このモジュール構成は、複数の運用者に販売または貸与されるプラットフォームと一致しており、非稼働のモジュールは、現在の運用者が契約していない機能を表していると考えられます。これは独立した調査結果とも符合します。同じ月に、Malwarebytesは全く別のNeedleキャンペーンを報告しており、そちらはハッシュ値もC2のIPアドレスも完全に異なる基盤を使用し、偽の取引サイトとDLLハイジャッキングによる配布に焦点を当てたものでした。両者のサンプルには重複が一切ありません。あちらの分析が被害者への感染経路を明らかにしたのに対し、本レポートは被害者を処理する基盤の内部に踏み込んでいます。


このバイナリはデバッグシンボルを含まない、シンボル除去済みのRust製PEです。Rustのパニックハンドラの仕組みは、リリースビルドであってもソースファイルのパスを保持しており、バイナリのデータセクションに残るこれらの文字列から、ウォレットなりすましの標的を特定できます。

src/views/zelcore/app.rs
src/views/trezor/seed_panel.rs
src/views/ledger/screens/seed_recovery.rs

このバイナリには完全なeguiデスクトップGUIフレームワークも含まれており、バイナリ内の特徴的な文字列パターンから識別できます。これは、被害者が操作することになる偽ウォレットのダイアログを描画するために使われています。

バイナリのデータセグメントと設定構造の分析を通じて、Caronteはエージェントに埋め込まれた設定ブロックを次のように復元しました。

{
  "version": "1.3",
  "api_url": "http://130.12.180.135:3000/api/v2",
  "api_key": "alk_776...fc1",
  "open_original_wallet_on_valid_seed": true
}

open_original_wallet_on_valid_seedというフィールドは注目に値します。これがtrueに設定されていると、エージェントはシードフレーズの取得に成功した後、正規のウォレットアプリケーションを起動します。そのため被害者には本物のウォレットが正常に立ち上がったように見え、何か問題が起きたと疑う直接的な理由がなくなります。これは設定に組み込まれた、意図的な検知回避策です。

この設定は難読化や暗号化を一切施さずに保存されていました。さらに重大なのは、api_keyフィールドが、感染端末がC2に認証する際に使うものと同一の認証情報だという点です。エージェント側の認証とAPIの読み取りアクセスの間に区別が一切なく、新規被害者を登録するキーが、被害者リスト全体を読み取ることもできてしまいます。


APIの全体像を把握する:フロントエンドバンドルの解析

サーバーへリクエストを送信する前に、Caronteはポート3000で配信されているReact SPAのwebpackバンドルを解析しました。バンドルされたJavaScriptの中には、すべてのAPIルート文字列が含まれていました。

/api/v2/wallets
/api/v2/antiledger/settings
/api/v2/antiledger-v2/seed-phrases
/api/v2/panel-access/evaluate
/api/v2/backup-domains/active
/api/v2/browser-spoofer/build/{buildId}/status
/api/v2/builds/{buildId}/{filename}

これにより、サーバーへの能動的な調査を一切行うことなく、バンドルを1回取得しただけでエンドポイントの全体像を把握できました。


パネル内部への侵入:2つの独立したアクセス経路

このC2の分析には、それぞれ異なる種類の可視性をもたらす2つの手法が必要でした。両者は明確に区別しておく価値があります。

フロントエンドUI:クライアント側のみの認証

React SPAの認証ロジックは、localStorageを読み取ってダッシュボードを表示するかログイン画面へリダイレクトするかを判断していました。サーバーはこのルーティング判断に一切関与していません。

ブラウザ上でlocalStorageに2つの値を直接書き込むだけで十分でした。

localStorage.setItem("auth_token", "<structurally valid JWT, exp:9999999999>");
localStorage.setItem("auth_user", JSON.stringify({ username: "admin", role: "admin" }));

/loginへのリクエストをインターセプトしてアプリが認証状態を再取得するのを防いだところ、管理ダッシュボード全体が表示されました。これはJWTライブラリの検証を回避したというものではなく、UIルーティング層にサーバー側の認証ゲートがそもそも存在しない、つまりSPAが表示判断の際にlocalStorageを無条件に信頼しているという問題です。

この手法で得られたもの:パネルの構造、モジュールの配置、設定ページ、UI全体の様子です。C2スタックについては、HTTPレスポンスヘッダー(Server: nginx/1.29.8)とバンドルのメタデータ(webpack出力内のReact 19のバージョン文字列)から確認しました。本レポートに掲載しているスクリーンショットは、このレンダリングされたフロントエンドを写したものです。

この手法では得られなかったもの:実際のデータです。権限を要する操作を行うバックエンドAPIエンドポイントには、有効な署名付きJWT(HS256)が必要です。偽のトークンのみでは、SPAからのAPI呼び出しは401エラーを返しました。

データへのアクセス:埋め込まれたエージェントキー

被害者の全記録、出金設定、セッション統計はすべて、Caronteがバイナリから抽出したエージェントキーを使ったAPI呼び出しから直接得られたものです。このキーは管理者用のJWT方式とは独立してエージェント向けエンドポイントを認証しており、感染端末が自らを登録する際に使うのと同じ認証情報だけで、被害者リスト全体と運用者の設定を読み取ることができました。

この2つの手法は互いに補完し合うものであり、完全に独立しています。フロントエンドの回避によってパネルの構造が明らかになり、これが稼働中の運用者向けプラットフォームであることが確認できました。データはすべてエージェントキーによってもたらされました。


二本立ての攻撃戦略

ブラウザウォレットなりすまし

このブラウザモジュールは、正規の仮想通貨ウォレット拡張機能を差し替えるか、その通信を傍受します。被害者から見れば普段どおりのMetaMaskやPhantomの拡張機能に見えますが、実際には偽物のバージョンがパスワードやシードフレーズの入力を傍受し、C2へと流出させます。

認証がほぼ機能していないポート8080上で稼働するビルダーでは、運用者が標的とするウォレット、インストーラーの自己削除挙動、待機モード、スマートピン機能などを設定できます。

標的にはMetaMask、Trust Wallet、Phantom、OKX、TonKeeper、Coinbase、Atomic、Bybit、Binance Walletが含まれます。APIからは111件のブラウザ被害者が記録されていました。

ウォレット 被害者数
MetaMask 48
Trust Wallet 30
Phantom 20
OKX 6
TON 3
Binance 2
Bybit 2
合計 111

監視期間中に/api/v2/walletsエンドポイントを繰り返しポーリングしたところ、件数は数時間ごとに3件から5件のペースで増加していました。このキャンペーンは、私たちの観測期間中もずっと積極的に被害者を獲得し続けていたことになります。

デスクトップウォレットなりすまし

このRust製エージェントは、デスクトップ向けウォレットアプリケーションになりすまします。被害者が自分のExodus、Trezor、Ledgerのアプリだと思って開くと、偽の「Restore Wallet(ウォレットの復元)」ダイアログが表示され、シードフレーズの入力を求められます。入力されたシードはC2へ送信され、有効なもので自動転送モジュールが有効になっていれば、資金は運用者の出金アドレスへと移されます。

データ収集時点で、1,821件のデスクトップセッションが記録されており、いずれもawaitingSeed: trueの状態でした。これは、C2が感染端末を登録済みではあるものの、シードフレーズはまだ一件も送信されていないことを示しています。継続的な監視の結果、サーバー側が報告する総数は1,825件にまで増加しており、新たなセッションが積み上がり続けていることが確認できました。シードが未送信のままである理由が、配布が始まったばかりの段階であることによるものなのか、それとも被害者が偽のダイアログの途中で操作をやめたことによるものなのかは、このデータだけからは判断できません。

出金モジュールは、接続時の残高自動チェック、パスワード入力後の自動出金、シードフレーズ送信をトリガーとする出金という3つの自動化モードに対応しており、いずれも運用者ごとに個別にオン・オフを切り替えられます。


C2の内部:エージェントキーが明らかにしたもの

攻撃者の設定と資金の動き

GET /api/v2/antiledger/settingsからは、運用に関する設定全体が返ってきました。BTC(1PVq...qfZ)、LTC(LKmc...DBL)、DOGE(D5xE...FFq)、SOL(BpxG...YU)、TRON(TDij...UNp)、EVM(0xD5...F1b)の各出金アドレスに加え、TronGridのAPIキーも含まれていました。

TronGridは、TRONにおけるInfuraに相当するもので、プロフェッショナル向けのブロックチェーンRPCサービスです。これが存在することから、運用者は手動での監視に頼るのではなく、出金をトリガーする前にTRON/USDTの残高を自動でチェックする仕組みを構築していることがうかがえます。

オンチェーン分析からは、この運用が実際に資金を動かし続けていることが確認できます。分析時点で、EVMの出金アドレスからは約148ドル相当のETHが引き出され、3つの別々のコールドウォレットへ転送されていました。TRONのアドレスには約60ドル相当のUSDTとTRXが保持されていました。BTC、LTC、DOGE、SOLの各アドレスには取引履歴の記録がなく、これらのチェーンについてはまだ初期の収集段階にあるキャンペーンであることと一致しています。

出金設定への書き込みアクセス

フロントエンドのバンドルには、GETエンドポイントと同じエージェントキー認証方式を使うPUT /api/v2/antiledger/settingsが定義されています。同一の認証実装であることから示唆されるとおり、もしこのキーが書き込み経路でも受け入れられるのであれば、6つの出金アドレスを任意のアドレスに置き換えることが可能となり、今後のすべての自動転送を運用者から逸らすことができてしまいます。

私たちは書き込みリクエストを一切送信していません。この含意をあえてここで報告するのは、スコープ制限のないエージェントキーを配布することの帰結を余すところなく示しているからです。すなわち、研究者であれ、競合するグループであれ、あるいは敵対的な第三者であれ、そのサンプルバイナリさえ入手できれば、このC2の被害者を洗い出し、さらにはその収益を横取りできる可能性があるということです。


2つのインスタンス、1つのデータベース

洗い出し作業の過程で、同一ホストのポート8181にも、同じエージェントキーを受け付ける2つ目のパネルインスタンスが存在することを確認しました。シードフレーズのセッションIDを突き合わせたところ、ポート3000から取得した1,817件のうち1,814件が一致しており、ほぼ完全な重複が確認され、データベースを共有していることが裏付けられました。差分は別のデプロイメントによるものではなく、クエリを実行したタイミングのずれによるものです。

このデータベース共有の構成は、複数の運用者やアフィリエイトに対して、それぞれ別の基盤を用意することなく独立したパネルアクセスを提供するMaaSプラットフォームの構造と一致します。ポート8181は、ブラウザウォレット関連のエンドポイントに対して空の結果セットを返しました。これが契約しているモジュールの違いによるものなのか、あるいは単にそのインスタンス用のAPIの構成が異なるだけなのかは、手元のデータからは断定できません。


侵害の痕跡(IOC)

Sample:
  SHA256: d6ca3760...a490
  MD5:    8b3433...36ca534b
  Name:   needle_agent_v1.3 (internal)
Network:
  C2 Panel:  130.12.180.135:3000
  Builder:   130.12.180.135:8080
  Secondary: 130.12.180.135:8181
  Stack:     nginx/1.29.8 (Server header), React 19, Express.js
  Hosting:   ASN 202412 Omegatech LTD, Amsterdam (bulletproof hoster)
Persistence:
  Registry:  HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
rule Needle_Crypto_Stealer_v1 {
    meta:
        description  = "Needle MaaS crypto-stealer, Rust desktop agent v1.x, distributed as agent.exe"
        author       = "Beelzebub Research"
        date         = "2026-04-28"
        reference    = "https://beelzebub.ai/blog/needle-c2-crypto-stealer-analysis"
    strings:
        // Needle-specific C2 API path and config field
        $api1 = "antiledger-v2/seed-phrases" ascii
        $api2 = "open_original_wallet_on_valid_seed" ascii
        // Rust panic handler source paths, present in release builds
        $src1 = "src/views/zelcore/app.rs" ascii
        $src2 = "src/views/trezor/seed_panel.rs" ascii
        $src3 = "src/views/ledger/screens/seed_recovery.rs" ascii
        // UI string keys from Needle's fake-wallet dialog logic
        // needle_desktop_wallet_debug.txt is a debug log path embedded in the agent
        // needle.wallet. is a namespace prefix for wallet-specific UI keys
        $ui1  = "checkseed.invalid_message" ascii
        $ui2  = "needle_desktop_wallet_debug.txt" ascii
        $ui3  = "needle.wallet." ascii
    condition:
        // Valid Windows PE (MZ header + PE signature)
        uint16(0) == 0x5A4D and
        uint32(uint32(0x3c)) == 0x4550 and
        // Rust + egui binary size range
        filesize > 5MB and filesize < 50MB and
        // Both Needle-specific API strings must be present
        all of ($api*) and
        // At least one compiled-in Rust source path
        1 of ($src*) and
        // At least two Needle UI strings
        2 of ($ui*)
}

結論

このNeedleの展開は、わずか2週間足らずのうちに2つの攻撃経路を通じて1,932件の標的を登録していました。私たちが分析を完了する前に、EVMのホットウォレットはすでにコールドストレージへ資金を移し終えており、この作戦は決して仮説上のものではありませんでした。

この全容が明らかになったのは、ひとえに運用者自身のエージェントの作り方に原因があります。彼らが自らのマルウェアに埋め込んで配布していた認証情報を使うことで、私たちは彼らの被害者リストを読み取り、出金設定を抽出し、オンチェーンでの活動を追跡することができました。別のNeedleキャンペーンに関するMalwarebytesのレポートは、これが孤立した事例ではないことを裏付けています。複数の運用者が、同じプラットフォーム上で同時並行的にキャンペーンを展開しているのです。


Caronteがこれを可能にした理由

この調査全体は、たった1件のサンプルをアップロードするところから始まりました。Caronteは、埋め込まれた設定の復元、基盤の帰属特定、YARAルールの生成、脅威グラフの構築までを自律的にこなしました。これは、熟練したリバースエンジニアが取り組んでも数日はかかる作業です。人間のアナリストが担った役割は、抽出作業そのものではなく、検証とオンチェーンでの裏付け作業でした。

もし貴社のSOCが今もサンプルを手作業でリバースエンジニアリングしているのであれば、それは攻撃者の最後の波ではなく、最初の波の速度でしか動けていないということです。

Caronteの仕組みについて詳しく見る

翻訳元: https://beelzebub.ai/blog/needle-c2-crypto-stealer-analysis/

本記事は beelzebub.ai の記事を翻訳・要約したものです。