要点
- urlscan.ioで直接配信される
.vbsファイルを探索したところ、世界全体で結果はわずか3件でした。そのうちオンライン状態が十分に長く続いて回収できたのは1件のみで、未文書化のx86-64ペイロードに至る完全な攻撃チェーンを組み込んだ3.3MBのスクリプトでした - 5段階のチェーンは、難読化されたVBScriptから始まり、動的生成されたPowerShell、AES-256-ECB、独自のBase85アルファベット、リフレクションでロードされる.NETインジェクター、そして
AppLaunch.exeへのプロセスホロウイングへと移行します。永続化のためにドロッパーは、偽のWindows Security XHTML文書内に自らのソースを反転させた形で隠します - ネスト構造のPE内にあるPDBパスは、
Overlord HVNC DLL Loadedというバナーによって独立して裏付けられており、このマルウェアファミリーをEvolution RATと命名しています。ビルダーがパッチを当てる.cfgマーカーはホストに依存しないファイル指紋を提供し、固定された平文のハローフレームはビルドに依存しないネットワークシグネチャを提供します - このペイロードは、HVNC、リモート入力、ファイル・プロセス制御、キーロギング、インジェクション、接続終了、リバースプロキシを含む、対話的アクセスのための59個のコマンドを公開しています。収集時点では配布元とC2は同一のコロンビアIPアドレスを共有していました
- この決定的な機能はセッションID窃取です。Evolution RATは稼働中のブラウザプロファイルを複製し、そのシングルトンロックを解除して、隠しデスクトップ上でクローンを起動し、Chromeがフィンガープリンティングスクリプトに公開するのとまったく同じ形式で35のホスト属性をフォーマットします。リバースプロキシは、盗まれたCookieとデバイスIDに被害者のネットワーク発信元情報を追加します
- Caronteは最終ペイロードをマッピングし、ネスト構造のPDBメタデータを抽出、そして
.cfg内のC2情報を回収しました。スクリプトチェーン自体は手動で展開作業を行っており、締めくくりのセクションではそれぞれの貢献を明確に区別しています
主要な発見の概要
| カテゴリ | 詳細 |
|---|---|
| 研究上の貢献 | Evolution RATに関する初の公開技術文書。ネスト構造のPE内にあるPDBパスから命名し、Overlord HVNC DLL Loadedバナーによって独立して裏付けられている |
| 配信チェーン | 3.3MBのVBS → 動的生成PowerShell → AES-256-ECBローダー → 独自Base85 → リフレクション.NETインジェクター → AppLaunch.exeにホロウイングされたx86-64ペイロード |
| 永続化 | 偽のXHTMLWindows Security文書に反転VBScriptを埋め込み、XOR難読化されたStartupローダーが復元・実行。第二の経路ではStartupにWindowsDefender.exeをインストール |
| セッション乗っ取り | 稼働中のブラウザプロファイルのクローン化、ロックファイルの削除、MinHookによるパスリダイレクト、Chrome App-Bound Encryptionへの対応、ブラウザ互換の35のフィンガープリントフィールド |
| コマンド&コントロール | 暗号化されていないフレーム形式のTCPプロトコル上で59個のコマンド。収集時点でserversniperxx.duckdns.org:4577は配布元ホストとIPアドレスを共有していた |
| 持続的な検知 | ビルダーのマーカー##CFG_START##/##CFG_END####(.cfgセクション内)、および固定されたクライアントハローAD DE 02 00 10 02 00 00 |
| 公開データベースでの検知状況 | 収集時点:.NETインジェクターは公開ソース4件中0件がヒット、x86-64ペイロードは4件中1件がヒット |
攻撃チェーンの概要
このチェーンは非常に手が込んでいますが、その目的は最終段階に達して初めて見えてきます。各段階はそれぞれ異なる検知対象を取り除いていき、ペイロードが実行された時点で、あらゆる主要機能が再利用可能なブラウザセッション、すなわち認証状態、起動可能なプロファイル、一致するデバイスフィンガープリント、そして被害者自身のネットワーク発信元情報の構築に寄与します。
発見の経緯:世界でわずか3件
あるクエリが返した結果はわずか3つのURLでした。翌朝までにそのうち2つは応答しなくなっていました。残る1つを開くと、5段階の実行チェーン、隠しデスクトップ、59個のコマンド、そして内部名称がこれまで一切公開報告に登場したことのないマルウェアファミリーが姿を現しました。
このサンプルは、報告や提出物から得られたものではありません。ある一つの観察に基づいて構築されたハンティングによって発見されました。ランディングページやリダイレクトチェーンを経由せず、スクリプトペイロードを直接配信するURLは非常に稀であり、世界規模で列挙できるほどの数しか存在しない、という観察です。
urlscan.ioのクエリは単純なものです:
task.url:".vbs" AND date:>now-24h
この期間、プラットフォーム全体で返ってきた結果はわずか3件でした:
http://asegurar2026.duckdns.org/sostener2.vbs
http://172.245.214.91/fridaexploit1.vbs
http://64.89.160.17/System1.vbs
この件数であれば、通常悩まされるトリアージ問題は逆転します。管理すべきシグナル対ノイズの比率がほぼ存在しない、つまりノイズがほとんどないからです。各結果は、通常フィルターを書くのに費やす時間で個別に取得・検証できます。
3件のうち2件は1日以内に消えていました。172.245.214.91と64.89.160.17はいずれも、どちらも特徴を把握する前に翌朝までに応答を停止しており、本レポートではこの2つについて一切記述していません。これがこの種のハンティングの実情です。クエリ自体は低コストですが、観測できる期間は短く、発見されたもののほとんどは検証される前に消えてしまいます。稼働し続けていたのはasegurar2026.duckdns.orgのみで、本レポートが扱うのはこの1件だけです。
このホストは同一のペイロードを少なくとも2つの名前、sostener2.vbsとenvifa.vbsで配信しており、両ファイルはバイト単位で同一です(SHA256 c8f229c7843f5fec053c04fb78b2ed045c029671412d74a07ce40608eeef1797)。ベアIPアドレスでも応答があります。http://181.237.42.61/envifa.vbsは同一のハッシュを持つ200 OKを返しており、これはDuckDNSの名前があくまで利便性のためのものであり必須ではないこと、つまり動的DNSエントリのみをテイクダウンしてもペイロードは除去されないことを意味しています。
注目すべき詳細はサーバーバナーです:
Server: Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.0.30
Windows上のApache、PHP 8.0という構成で、コロンビアのアドレスのドキュメントルートから.vbsを配信しています。この組み合わせは一般的な商用ホスティングとしては非典型的であり、インターネットに公開されたWAMP形式のマシンが配布拠点として転用されたことと整合しますが、サーバーの所有者については入手可能な証拠からは特定できませんでした。
命名は一貫してスペイン語です。asegurar(確保する、保証する)、sostener(保持する、維持する)。コロンビアのアドレスと合わせて考えると、ラテンアメリカでの運用背景がうかがえますが、これは被害者の地理的分布や攻撃者の意図する標的を確定づけるものではありません。
ドロッパー:1つのBase64ストリームを343分割
sostener2.vbsは3,346,929バイトあり、そのバイトがどこに費やされているかを正確に把握しておく価値があります。直感的な答えは間違っているからです。
このファイルの大部分はパディングではありません。大部分がペイロードなのです。3,308,332文字の単一のbase64ストリーム、ファイル全体の98.9%が、インラインで運ばれるAES暗号化された次段階です。それ以外のすべて、ロジック、永続化ルーチン、おとり関数は、残りの38KBに収まっています。何かをダウンロードするドロッパーは存在しません。x86-64バイナリに至るまでの全チェーンが、このたった1つのテキストファイルに搭載されているのです。
そのストリームの上に、3つの難読化手法が重ねられています:
- 文字列に対するXOR 0x99。意味を持つリテラルはすべて、パイプ区切りの10進バイト値の配列として格納され、単一バイトでXORされ、実行時に1つのヘルパー関数によって再構築されます。このファイルに対する静的な文字列抽出では、使えるものは何も得られません。APIの名前、パス、URLのいずれも可読な形式では存在しないのです。
- 343個のリテラルを、順序を入れ替えて再結合。base64ストリームは1つのリテラルではありません。343個の
Dim代入文に分割され、343個の別々のステートメントによってv0x86fadに連結されますが、その順序は宣言順とは343箇所中一致箇所ゼロです。ファイルを上から下へ読み、見た目の順序でリテラルを結合しても、何にもデコードされない文字列しか得られません。ペイロードの復元には、単にbase64をgrepするだけでなく、連結の順序を解決する必要があります。 - おとりコード。34個の関数・サブルーチン定義があり、そのほとんどは値を計算しても破棄するだけのもので、170行の
If 0 Then Randomize Timerという形式の文と混在しています。この条件は常に偽であり、この文が実行されることはありません。
おとりコードはしばしば単なる容量稼ぎのパディングとして語られがちですが、ここではそうではありません。この170行の合計は約4KB、ファイル全体のわずか0.12%にすぎません。スキャナーのバイト予算を消耗させるものではないのです。これが打ち負かすのは人間の目とナイーブなパーサーです。制御フローを追おうとする読み手は、目に見えるステートメントの9割を捨て去らなければならず、関数定義をインデックス化するツールは、どこにも通じない34個の名前を得るだけです。
このファイルがスキャナーに課すコストがあるとすれば、それはパディングではなくペイロードに由来するものです。おとり行をすべて取り除いても、3.34MBのスクリプトが残ります。このサイズこそが、このマルウェアが運ぶものそのものなのです。
永続化:マルウェアを逆順で格納した偽XHTMLページ
永続化の仕組みはこのドロッパーの中で最も特徴的な部分であり、特定の製品ではなくある種の検知手法全般を打ち負かすものであるため、正確に説明する価値があります。
このスクリプトはまず、自分自身が既にStartupフォルダから実行されているかどうかを確認し、そうでない場合にのみ永続化をインストールします。実行されていない場合、WScript.ScriptFullNameで自身のソースを読み込み、StrReverseで1文字ずつ逆順にし、その反転テキストをXHTMLとして提示される文書内に埋め込みます:
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>Windows Security</title></head>
<body>
<div id="ms-2EDe7CMv96" data-key="8MxUjqEtBpLuwtsg">
<!--2EDe7CMv96-->
[reversed VBScript source]
<!--/2EDe7CMv96-->
</div></body></html>
このファイルは以下の2箇所に書き込まれます:
%APPDATA%\Microsoft\manifest_ckCGj9.xhtml
C:\Users\Public\manifest_ckCGj9.xhtml
別のローダーがCfgShieldckCGj9.vbsとしてStartupフォルダに投下され、人間の読み手を意識して書かれた3行のコメントで始まります:
' Windows SmartScreen Filter Config
' Version 4.8.494 - Microsoft Corporation
' (c) 2026 Microsoft. All rights reserved.
このローダーは自身の文字列にXOR 0xCDを使用しており、これはドロッパーの0x99とは異なる鍵であるため、一方用に書かれたデコーダーはもう一方を読み解くことができません。
デコードされると、このルーチンは%APPDATA%を展開して\Microsoft\manifest_ckCGj9.xhtmlを探し、見つからなければC:\Users\Public\manifest_ckCGj9.xhtmlにフォールバックします。リテラルdata-key="8MxUjqEtBpLuwtsg"によってファイルを検証し、2つの2EDe7CMv96コメントマーカーの間のテキストを抽出し、それを反転させて元のソースを復元し、先頭のOption Explicitを取り除いた上で、ExecuteGlobalで実行します。
さらにMicrosoft.Update.Session、Win32_OperatingSystem、root\SecurityCenter2という3つのデコード済み文字列がこのローダー内に宣言されていますが、一切使用されていません。これらはドロッパーの死んだ関数と同種のおとりで、急いで確認する分析者がこれを環境チェックだと読んでしまうような位置に配置されています。
これが検知に及ぼす影響は直接的です。再起動後も生き残るアーティファクトは、スクリプトとは認識されません。.xhtml拡張子を持ち、<title>にはWindowsのセキュリティコンポーネントを名乗る文言があり、本文には逆順に書かれたVBScriptが含まれています。
すべての文字列、API名、キーワードが逆順になっているため、元のドロッパー用のいかなるシグネチャもこの本文にはマッチしません。認識可能なスクリプト構文を持つ永続的なファイルは、小さなStartupローダーだけであり、そちらの文字列自体もXORエンコードされています。
同じ6文字のトークンckCGj9がXHTMLファイル名とローダーファイル名の両方に含まれており、トリアージ時にホスト上でこの2つのアーティファクトを結びつける手がかりとなります。
第2~5段階:AES、独自Base85、そしてホロウイング
このドロッパーは自ら何かを復号することはしません。第2のスクリプトを生成し、その処理をPowerShellに委ねます。
具体的には、VBScriptがメモリ上で短いPowerShellプログラムを構築し、それをADODB.Stream経由でバイトオーダーマークなしのUTF-8として%TEMP%\<random hex>.ps1に書き込み、実行します:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy Bypass -File "<%TEMP%\...ps1>"
生成される第2段階は小さなものです。その唯一の役割は、343個のリテラルからなるblobをbase64デコードし、ECBモード・PKCS7パディングのAES-256で復号し、平文をディスクに書き込むことです。
すべての型名・メソッド名は文字列断片から組み立てられ('System'+'.Con'+'vert'、('From'+'Base64S'+'trin'+'g')、('Create'+'Decryp'+'tor'))、リフレクションによって呼び出されるため、スキャナーがマッチできる完全な.NET識別子は残されません。唯一隠されていないリテラルは鍵です:
Be1OluQXuNlV3c/vviqtY1xEW70mAk13+uxHyLqeTZ8=
これは32バイトで、05ed4e96e417b8d955ddcfefbe2aad635c445bbd26024d77faec47c8ba9e4d9fとなります。
2,481,247バイトの平文自体もPowerShellスクリプトです。%TEMP%\<random hex><random hex>\ClJdZcnG.ps1に書き込まれ、2度目のpowershell.exe起動ではなくコール演算子を用いて呼び出されます。したがって第3段階は、これを復号したプロセス内部で実行され、独立したコマンドラインとして現れることは一切ありません。
3秒後、生成されたスクリプトはそのファイルとディレクトリを削除し、VBScriptがその後を追って第2段階を削除します。このチェーンは中間アーティファクトを消去し、XHTMLの永続化と最終ペイロードが書き込むもの以外は何も残しません。
この再構成は正確です。343個の連結を解決し、その結果をbase64デコードして、その鍵で復号すると、SHA256 a49c01a5cc1c06be5f2e6cbd0f63554c7755e45f3931c08541da643014c8c9e5にバイト単位で一致する2,481,247バイトが得られます。
第3段階はバイナリをBase85エンコードされたblobとして運びますが、標準のBase85ではありません。アルファベットは独自のものです:
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~
数字が先頭、続いて大文字、小文字、そして両方の引用符文字とバックスラッシュを除いた記号群という順序で、これがこのblobをエスケープなしでPowerShellのヒアストリング内に収められる理由です。グループサイズ、基数、短いグループのパディングは通常のものですが、文字と値の対応マッピングだけが独自です。これはAscii85にもRFC 1924のZ85セットにも一致せず、それだけで十分機能します。標準的なデコーダーではゴミしか出力されず、既知のエンコード方式を順に試すアンパッカーでも一致は見つかりません。オペレーター側のコストは、スクリプト自体に搭載された約40行のデコードロジック程度ですが、分析者側のコストは、このblobが既存のツールでは一切反応しないという点です。
デコードするとx86-64ペイロードが得られます。第2の、より小さなPEが同じスクリプト内に単純なbase64文字列として同居しています:
| ファイル | サイズ | 役割 |
|---|---|---|
| .NETインジェクター | 9,728バイト | スクリプト内のbase64、Reflection.Assembly::Load経由でメモリ内にロード。型はyMOxml.nRCN06Y、メソッドはC3FrcKQp |
| x86-64ペイロード | 1,972,736バイト | 独自Base85のblob、Evolution RAT本体 |
このインジェクターのエントリーメソッドは正確に2つの引数、すなわちペイロードのバイト列とターゲットプロセスのパスのみを受け取ります。設定もC2もオプションも一切ありません。実行するのはプロセスホロウイングという1つの操作のみで、その対象はAppLaunch.exeであり、ポインタ幅に応じてパスを選択します:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\AppLaunch.exe (64-bit host)
C:\Windows\Microsoft.NET\Framework\v4.0.30319\AppLaunch.exe (32-bit host)
AppLaunch.exeは.NET Frameworkに同梱された署名済みのMicrosoftバイナリです。事実上あらゆるWindowsインストール環境に存在する正規のホストプロセスであり、これが動作していること自体は表面上異常ではありません。
このインジェクターについてはもう一段落費やす価値があります。そのインポート面が意図的に有益な情報を持たないよう作られているためです。宣言されているP/InvokeはGetModuleHandleA、GetProcAddress、VirtualAlloc、CreateProcessA、CloseHandleのみで、いずれもkernel32由来です。メタデータにはインジェクションのプリミティブを示すものは何もありません。実際のAPIセットは0x2FでXORされた文字列として格納され、実行時にntdll.dllから解決されます。このntdll.dllという名前自体も同じ方式でエンコードされています:
a[zABN_yFJX`I|JL[F@A -> NtUnmapViewOfSection
a[nCC@LN[JyF][ZNCbJB@]V -> NtAllocateVirtualMemory
a[x]F[JyF][ZNCbJB@]V -> NtWriteVirtualMemory
a[hJ[l@A[JW[{G]JNK -> NtGetContextThread
a[|J[l@A[JW[{G]JNK -> NtSetContextThread
a[}J\ZBJ{G]JNK -> NtResumeThread
41 5B 4B 43 43 01 4B 43 43 -> ntdll.dll
それぞれがMarshal.GetDelegateForFunctionPointerによって呼び出し可能な状態に変換されます。これはWin32の一連の手順ではなく、ネイティブAPIによるホロウイングです。VirtualAllocEx、WriteProcessMemory、SetThreadContextのいずれも使われません。Win32の名前をインポートテーブル上であれ、その層に設置されたフック群であれキーとする検知ロジックには、このアセンブリはプロセスを生成する以外何もしていないように見えます。
ファミリー名の特定:ネスト構造のPE内にあったPDBパス
このx86-64ペイロード自体にはPDBパスがありません。そのデバッグディレクトリには、Microsoftのリンカが出力するプロファイルガイド最適化のセクションレイアウトを示すPOGOレコードが1つあるだけで、他には何もありません。外側のバイナリのデバッグ情報を読んでも、このファミリーは匿名のままです。
ファミリー名は、もう一段深いところにあります。ペイロードのリソースセクション内、オフセット1810200の位置に、もう1つの完全なPEファイルが存在しており、そちらはデバッグパスを取り除かずにビルドされていました:
C:\Users\USER\Desktop\Evolution RAT\Overlord-Server\dist-clients\HVNCInjection.pdb
GUID 59965EFB-F602-42DC-94D0-FCF15841EFC1 age=1
このパスからは3つのことが読み取れます。プロジェクト名はEvolution RATです。サーバー側コンポーネントはOverlord-Serverと呼ばれ、クライアントバイナリはdist-clientsディレクトリに生成されており、これは共通のサーバープロジェクトからオペレーターごとのクライアントをコンパイルするビルダーの構成を示しています。そしてこのネスト構造のモジュールはHVNCInjectionという名前で、関数を1つも逆コンパイルせずともこのペイロードの主要機能を物語っています。
このパスはC:\Users\USER\Desktopから始まっており、このプロジェクトがUSERという名前のアカウントのデスクトップディレクトリからビルドされたことを示しています。デバッグパスはネスト構造のモジュール内には残存していましたが外側のバイナリには残っておらず、これがトップレベルのPEしか読まないツールにこのファミリー名が見えない理由です。
このネスト構造のモジュールは64ビットDLLで、そのオフセットからのセクションテーブルは156,160バイトの位置で終わっており、そのRSDSデバッグレコードは外側ペイロードのファイルオフセット1944736に位置しています。このモジュール自身の文字列がPDBとは独立にこの名前を再度裏付けています。このモジュールはOverlord HVNC DLL Loadedというバナーを%TEMP%\crashlogovd.logに書き込むのです。ビルドの異なる部分によって生成された2つのアーティファクトが、Overlordという名前で一致しています。
Evolution RAT、Overlord-Server、SniperLogs、HVNCInjectionを検索しても、公開された文書は一切見つかりません。ベンダーレポートも、サンドボックスの分析記事も、マルウェアリポジトリのエントリも、フォーラムへの投稿も存在しません。クエリで浮かび上がるのは、たまたまHVNCを実装している無関係のファミリーだけです。この技術セット自体は既に確立されたものです。しかしこの特定の実装は、公開記録のどこにも記述されていません。
C2は平文で送受信されており、それは設計上の判断です
このペイロードは設定情報を、.cfgという専用のPEセクションに格納しています。仮想サイズは163バイト、RVAは0x1A0000、ファイルオフセットは0x193600で、暗号化されておらず、固定レイアウトです。このセクション全体は、バイト単位で以下のようになっています:
offset len contents
0 13 "##CFG_START##"
13 3 zero padding, aligning the next field to 16
16 128 host, null-padded: "serversniperxx.duckdns.org"
144 2 port, uint16 LE: E1 11 = 4577 (0x11E1)
146 1 zero padding
147 13 "##CFG_END####"
160 3 zero padding
この平文格納は見落としではありません。マーカーがその理由を説明しています。##CFG_START##と##CFG_END####は、ビルダーがコンパイル済みバイナリ内でオペレーターのホストとポートをパッチで書き込むために検索する目印です。このセクションが平文かつ固定幅でなければならないのは、コンパイラへのアクセス権を持たないビルドツールが、コンパイル後にここへ書き込むためです。128バイトのnullパディングされたホストフィールドも同じ制約から来ています。ファイルのサイズやレイアウトを変更できないその場での上書きであり、各フィールド周辺のアラインメント用パディングは、パッチ適用ツールがパースせずに定数オフセットへシークできるようにするためのものです。
これによりこのマーカーは、ファミリー全体を特定できる指紋となります。オペレーターがどのC2をパッチで書き込んでも、このツールキットが生成するあらゆるビルドを通じて不変であるため、後述のYARAルールはserversniperxx.duckdns.orgではなくこのマーカーにアンカーしています。ホストとポートは変わりますが、セクション構造こそがこの製品を特徴づけているのです。
serversniperxx.duckdns.orgは181.237.42.61に解決され、これはsostener2.vbsとenvifa.vbsを配信しているのと同じアドレスです。配布はWindows上のApacheインスタンスを通じてポート80で行われ、コマンド&コントロールは同一マシン上のポート4577で行われています。ポート4577はTCP接続を受け入れますがHTTPには応答しません。これは本レポートの後段で再構成されるフレーム形式のバイナリプロトコルと一致します。
このツールキット全体を通じて内部の命名は一貫しています。C2のホスト名はserversniperxx、被害者ごとの識別子形式はSniperX_%d、キーロガーのディレクトリはC:\ProgramData\SniperLogsです。オペレーターのインフラから被害者のディスク上のディレクトリ名まで、同じルートが貫かれています。
本セクションの内容はすべて、ペイロードバイナリから静的に確認されたものです。
中核モジュールはHVNCです。modules\hvnc_injection.dllはReflectiveLoaderエクスポートを通じてリフレクションでロードされ、ロード可能なイメージとしてディスクに触れることは一切ありません。HVNC(hidden virtual network computing)は、被害者からは見えず攻撃者が対話的に操作できる、Windowsの第2のデスクトップを作成します。そのデスクトップ上で起動されたアプリケーションは、被害者自身のセッション内で、被害者自身のトークンとネットワーク上の位置情報を持って動作する一方、見えているデスクトップには何も表示されません。
GhostStandby。SetWindowDisplayAffinity(0x11)で作成される透明ウィンドウで、これは画面キャプチャからウィンドウを除外するフラグの組み合わせです。オペレーターがこのウィンドウの背後に置いたものは、スクリーンショット、画面録画、リモートサポートセッションのいずれにも映りません。これは正規のDRMやパスワードマネージャーがキャプチャからコンテンツを守るために使うのと同じAPIを、オペレーターの活動を映さないために転用したものです。winlogon.exeのなりすまし。SeDebugPrivilegeを取得した後、winlogon.exeに対してDuplicateTokenExとImpersonateLoggedOnUserを実行し、SYSTEMレベルのセキュリティコンテキストを得ます。- 解析対策。
IsDebuggerPresent、SetUnhandledExceptionFilter、そしてNtSuspendProcess/NtResumeProcessのペア。 - ブラウザスレッドの一時停止。SQLiteの認証情報データベースを読み込む前にブラウザのスレッドを一時停止させ、読み込み後に再開させることで、一貫した状態でデータベースを読み取り、かつブラウザ自身のファイルハンドルと競合しないようにしています。
- Defenderの除外設定。コマンドプレフィックス
powershell -WindowStyle Hidden -Command "Add-MpPreference -ExclusionPath 'と、対応する-ExclusionProcessバリアントの両方が文字列定数として存在しており、サービスを無効化しようとするのではなく、自身のパスとプロセス名をDefenderの除外リストに追加しています。 - 第2の永続化経路。このペイロードは、3段階前に確立されたVBScriptの永続化とは独立して、自身を
Start Menu\Programs\StartupにWindowsDefender.exeとしてコピーします。また自身をsvchost.exeとしてもコピーします。 - キャプチャ。
BitBltとGDI+によるJPEGエンコードでの画面キャプチャ、Media Foundation(MF.dll、MFPlat.DLL、MFReadWrite.dll)によるウェブカメラ・マイクのキャプチャ。 - キーロギング。
WH_KEYBOARD_LLとWH_MOUSE_LLフックがC:\ProgramData\SniperLogs\keylog_%04d%02d%02d_%02d%02d%02d.txtに書き込み、C2チャネル経由の小規模なログ管理コマンドセット(CONTENT|、DELETE、DELETED、EMPTY)を備えています。 - 同期プリミティブ。ミューテックス
hvnc:ready、hvnc:launch_ok、hvnc:launch_fail、hvnc_watchdog:stuck、Local\hvnc_rdi_、SingletonSocket。stuck状態を持つウォッチドッグの存在は、このHVNCセッションが1回限りの発火ではなく、長期間維持され監視されることを前提としていることを示しています。
ネットワーク制御
このペイロードのネットワーク機能のうち3つは、単なる情報窃取ツールに必要な範囲を超えています:
- 任意のTCP接続の終了を
SetTcpEntry経由で行い、コマンド0xE3として公開されています。コマンドペイロードは12バイトで、ローカルIP、ローカルポート、リモートIP、リモートポートから成ります。オペレーターはホスト上の任意の単一接続を切断できます。 - 接続の列挙で、コマンド
0xE0、JSON形式で接続ごとに1オブジェクトとして報告されます:{"pid":%u,"process":"%s","local_ip":"%s","local_port":%u,"remote_ip":"%s","remote_port":%u,"state":"%s"}。 - システムプロキシの乗っ取りで、
127.0.0.1:1をSoftware\Microsoft\Windows\CurrentVersion\Internet Settingsに書き込みます。ループバックのポート1はプロキシではなく、そこには何も待ち受けていません。これを設定すると、システムプロキシを尊重するあらゆるアプリケーションについて、被害者のインターネットアクセスが完全に失われます。
これら3つを併せて読むと、封じ込め用のツールキットを形成していることがわかります。接続の列挙によって、どのプロセスが何と通信しているかが特定できます。SetTcpEntryは特定のセッションを終了させます。デッドプロキシの設定はホストを丸ごと切断します。既に情報を窃取し終えた情報窃取ツールにとってこれらは何の役にも立ちません。しかし、自分自身のチャネルを開いたまま特定の接続だけを切断する必要がある、対話的なライブセッションを操作するオペレーターにとっては役立ちます。
4つ目のネットワーク機能である、コマンド0xB0のリバースプロキシについては、後述のC2コマンドセットの節で扱います。
認証情報アクセス:20種のブラウザと、Chrome最新の防御機構
このペイロードは約20種のブラウザを標的とし、事実上Chromiumエコシステム全体とFirefoxをカバーしています。Chrome、Edge、Brave、Opera、OperaGX、Vivaldi、Chromium、Yandex、CentBrowser、Iridium、CocCoc、Torch、Comodo Dragon、Epic Privacy、Slimjet、URBrowser、7Star、Amigo、Sputnik、Firefoxです。
使用されるクエリは標準的なものです:
SELECT origin_url, username_value, password_value FROM logins;
SELECT host_key, name, encrypted_value, path, is_secure, is_httponly, expires_utc FROM cookies;
Geckoプロファイルではmoz_placesから、Chromiumプロファイルではurlsから履歴を読み取ります。
2つの詳細がこのマルウェアをありふれた実装から区別しています。
1つ目は、このペイロードがapp_bound_encrypted_keyを扱う点です。Chrome 127はApp-Bound Encryptionを導入しており、これはCookieとパスワードの暗号化鍵を特定のアプリケーションに紐付けるもので、Local Stateファイルをコピーして別プロセスからCryptUnprotectDataを呼び出しても鍵は得られなくなります。このペイロードはこのapp-bound鍵の経路を明示的に扱っており、つまりChrome 127以前の方式ではなく現行のChromeに対して機能するということです。
2つ目は、キー名"Google Chromekey1"を用いてNCryptDecryptを呼び出す点です。これはChromeのapp-bound鍵フローにおけるCNGキー識別子です。これは汎用的なDPAPIラッパーではなく、以前存在した方式ではなく現行の防御機構に対して書かれたコードです。
転換点:パスワードを読み取るのではなく、セッションを複製する
ここまでの内容であれば、Evolution RATは異例に高機能な情報窃取ツールとしてまだ解釈できます。しかし1つの関数がその解釈を変えます。CloneProfileです。その目的は別の秘密情報を抽出することではなく、被害者の認証済みブラウザを別の場所で使用可能にすることです。
その流れは以下の通りです:
- ブラウザのユーザーデータディレクトリを特定し、そのプロファイルを攻撃者が管理するパス配下の
hvnc_%08Xにコピーします。このコピーは2パスで実行され、まず重要なファイルを、続いて残りを処理し、タイムアウトは60秒で、進捗をC2チャネル経由で報告します。liteモードでは、セッション状態を持たない大容量ディレクトリ、cache、code cache、service worker、blob_storage、拡張機能ストア、テレメトリデータベースをスキップします。 - コピーからロックファイル
SingletonLock、SingletonCookie、parent.lock、.parentlockを削除します。 - 隠しHVNCデスクトップ内で、クローンされたプロファイルを対象にブラウザを起動します。
ステップ1には、この実装が真剣に対処している複雑な問題があります。稼働中のブラウザは最も重要なファイルを開いたままにしており、単純なコピーではそれらに失敗します。このコードはその場合を明示的に処理しています。SeDebugPrivilegeを有効化し、NtQuerySystemInformationでシステムのハンドルを列挙してどのプロセスがロック対象のパスを保持しているかを特定し、そのロック元プロセスからハンドルを複製することでファイルを読み取ります。ステータス文字列はその意図を明確に示しています(Hijack: %d locking PIDs for %s、Hijack: %llu handles, searching...、Hijack OK: %s (%lld bytes))。プロファイルを奪うために被害者がブラウザを閉じる必要はありません。
ステップ2は、このコピーを使用可能にする仕組みです。この4つのファイルは、Chromiumおよびgeckoがプロファイルディレクトリごとに単一のブラウザインスタンスのみを強制するための仕組みであり、これらが存在すると、同じプロファイルに対する2度目の起動は拒否されるか、稼働中のインスタンスへ処理が引き継がれます。コピーからこれらを削除すると、被害者自身のブラウザが元のプロファイルで稼働し続けたまま、クローンを独立して起動できるようになります。
起動するのは認証情報を要求するブラウザではありません。既にログイン済みの、そのプロファイルにセッションCookieが存在していたあらゆるサイトにログイン済みのブラウザです。セッションCookieは、その構造上、認証が既に完了したこと(二要素目を含む)を証明するものです。埋めるべきパスワードプロンプトも、傍受すべきOTPも、被害者が承認すべきプッシュ通知も存在しません。なぜならアプリケーション側から見れば、新たな認証は一切発生していないからです。第2の要素は突破されたのではありません。被害者によって満たされ、そのCookieがその証跡なのです。
ブラウザをクローンに固定し続けるために、このペイロードはファイルAPI面全体にMinHookフックをインストールします:
NtCreateFile NtQueryAttributesFile
NtOpenFile NtQueryFullAttributesFile
NtDeleteFile NtQueryDirectoryFile
NtSetInformationFile NtQueryDirectoryFileEx
CreateProcessW
ブラウザプロセスがファイルを開く、検査する、列挙する、削除するあらゆる経路が傍受されており、さらにCreateProcessWもフックされているため、ブラウザが生成する子プロセスもこのリダイレクトを引き継ぎます。ブラウザは自分が通常のプロファイルを使っていると信じています。しかし実際に使っているのはコピーの方なのです。
フィンガープリントセット:35のフィールドと、その用途
クライアントは接続時にこれらのフィールドを送信しません。要求に応じて収集される仕組みで、コマンド0x35がブラウザ情報窃取を含む完全な収集パスをトリガーし、結果はオペコード0x64のJSON文書として返されます。35のフィールドは以下の通りです:
os_name os_version os_platform machine_name
user_name domain processor_count system_dir
screen_width screen_height screen_depth timezone
timezone_offset timezone_display language language_display
languages cpu_name cpu_id cpu_cores
cpu_threads gpu_name gpu_driver gpu_ram
ram_gb device_memory mac_address disk_serial
fonts fonts_count browsers chrome_version
user_agent webgl_vendor webgl_renderer
この種のマルウェアではホストプロファイルの収集は標準的なものです。被害者を国、権限レベル、そして再訪する価値があるマシンかどうかで分類するのに十分なフィールドです。このクライアントは接続時点で既に1つ、528バイトの固定フィールド構造体を送信しており、これは後述のプロトコルの節で扱います。これは2つ目のはるかに大規模な収集であり、その過剰分は特定のサブセットに集中しています:
webgl_vendor・webgl_renderer・fonts・fonts_count・device_memory・languages・user_agent
これはシステムインベントリではありません。1フィールドごとに見ても、これはまさにブラウザフィンガープリンティングの入力セットです。不正検知プラットフォームが、有効な認証情報を提示しているセッションが本当にそれを主張しているデバイスから来ているかどうかを判定するために使う手法です。WebGLのベンダー文字列とレンダラー文字列は、ブラウザが報告する通りにGPUとドライバーを特定します。インストールされているフォントのリストとその件数は、フィンガープリンティングスクリプトが利用できる中で最もエントロピーの高いシグナルの一つです。device_memoryとlanguagesは、OSがその名前で公開するものではなく、JavaScriptから読み取れるnavigatorプロパティに対応しています。
値は単に収集されるだけでなく、フォーマットされている
決定的な詳細は、どのフィールドが収集されるかではありません。それらの値が出力される際にコードが何を行っているか、です。
これらの値のいずれも、ネイティブプロセスがブラウザから読み取れるものではないため、このペイロードはWMI、レジストリ、GDIといったシステム由来の情報源から構築します。そしてそれぞれの値を、ブラウザが生成したのとまったく同じ文字列にフォーマットします。フォーマット文字列はバイナリ内に、それが対応するフィールド名のすぐ近くに存在します:
user_agent Mozilla/5.0 (Windows NT %lu.%lu; %s) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/%s Safari/537.36
webgl_vendor "Google Inc. (" + <adapter vendor> + ")"
webgl_renderer "ANGLE (" + <adapter name> + ", Direct3D11 vs_5_0 ps_5_0, D3D11)"
device_memory %.0f (integer gigabytes, as navigator.deviceMemory reports)
ram_gb %.1f (the same quantity, one decimal, for the operator)
languages <UI language> + ",en-US"
GPU文字列はWin32_VideoControllerから取得され、ChromeのANGLE表記でラップされます。ChromeのバージョンはSoftware\Google\Chrome\BLBeaconから読み取られ、ユーザーエージェントのテンプレートに組み込まれます。レジストリの値が存在しない場合のフォールバックとして131.0.6778.86がハードコードされています。プラットフォームトークンにはWin64; x64が置換されます。物理メモリは1回取得され、2回出力されます。ram_gbの下では%.1fの精度で、device_memoryの下では整数に丸められて出力されますが、これは2種類の異なる消費者、すなわちトリアージを行う人間と、navigator.deviceMemoryが返す値と一致することを期待するフィンガープリント比較のためです。
この最後のペアが、意図を明確に物語っています。RAM容量を2つの精度で2つの異なる名前の下に2回収集することは、単なるシステムインベントリが行うことではありません。一方は自分自身のため、もう一方はウェブサイトが観測した値と一致させるためのものである場合にのみ、そうする理由が生まれます。
率直に言えば、これはたまたまフィンガープリンティングと重なるマシンインベントリではありません。これはフィンガープリント偽造キットです。ホストの実際のハードウェアから、そのホストのブラウザが報告していたであろう値を、報告側が用いる構文で組み立て、どこか別の場所から再生する準備を整えているのです。
この3つの機能を組み合わせると、設計上の意図が明らかになります:
CloneProfileは、オペレーターに被害者の認証済みセッションを与えます。- Cookie窃取は、オペレーターにそれらのセッションが依拠しているトークンを与えます。
- 35フィールドのフィンガープリントセットは、オペレーターに、不正検知エンジンがそれらのセッションに紐付いていると期待するデバイスIDを与えます。
結果として生まれるのは、リスクエンジンがクライアント側から評価する各軸において一致する、再生されたセッションです。正しいCookie、正しいユーザーエージェント、正しい言語・タイムゾーンの組み合わせ、正しい申告メモリ量、正しいGPUベンダーとレンダラー、正しいフォント一覧。通常アカウント乗っ取りの兆候となるはずのシグナル、すなわち未認識のデバイス、アカウントの履歴と一致しないロケール、既知のプロファイル上での新しいブラウザフィンガープリントは、まさにこの収集が満たそうとしているシグナルそのものなのです。
これが防御側にとって重要な分析上の区別点です。狙われているのは認証情報そのものではありません。セッションIDです。この感染後にパスワードをローテーションしても、既にクローンされたセッションには何の変化ももたらしません。パスワードのリセットよりも、MFAの再登録の方がはるかに重要です。
ここで実際に新しい点
セッション窃取自体は新しい発見ではなく、本レポートもそれを主張しているわけではありません。Cookie窃取は何年も前からありふれた手法であり、Adversary-in-the-Middleフィッシングキットはそれよりさらに長い期間にわたり大規模にライブセッションをMFAの脇を通してリレーしてきましたし、プロファイルのクローン化も複数のありふれた情報窃取ツールに見られます。Chrome 127で導入されたApp-Bound Encryptionは、まさにこの「Local Stateをコピーする」手法を打ち破るために出荷されたものであり、それはこの種の圧力が既に十分理解されていたからこそ存在しています。
このサンプルには、その一線を越える3つの点があります。
1つ目は、この収集がフォーマットを意識していることです。ハードウェアインベントリを取得する情報窃取ツールは数多くありますが、このマルウェアは受け手側が期待する形状を把握した上でそれを生成しています。これは、これらの値がブラウザが報告したものとの比較対象として送られることを前提にしていない限り意味を成しません。
2つ目は、この3つの機能が1箇所に集約され、組み合わされていることです。Cookie、起動可能なプロファイルクローン、ブラウザ構文によるデバイスフィンガープリント、ネットワーク発信元のためのリバースプロキシは、通常、情報窃取ツール、ローダー、プロキシウェアファミリー、そして購入者が後付けする何かに分散しているものです。ここでは、それらすべてが1つのバイナリに、1人のオペレーターによって、1つのチャネル上で駆動されています。
3つ目は、このクローンが、ロック元プロセスからのハンドル複製によって、被害者のブラウザを停止させることなく取得される点です。ブラウザを閉じる必要があるコピーは、あくまで機会に依存した機能です。稼働中のブラウザに対して機能するコピーは、運用として使える機能です。
いずれも新規の技術を必要とするものではありません。必要とされたのは、狙う対象がパスワードではなくセッションであると決断し、その仕様に沿ってすべてのコンポーネントを構築することだけです。
C2プロトコル:8バイトのヘッダーと、暗号化なしの通信
これまでの機能セットは、オペレーターが何を再現できるかを説明するものでした。このプロトコルは、その運用のあらゆる部分がどのように制御されているかを説明すると同時に、その過程で2つの持続的な検知機会を明らかにします。
以下に示すプロトコルは、ペイロードバイナリからGhidraで再構成したものです。C2との間で通信を行ったことは一切なく、サンプルを実行したこともなく、通信のキャプチャも一切関与していません。ここに記載する内容はすべて、フレームを構築するコードとそれを検証するコードから得られたものです。
フレーミング
すべてのメッセージは固定8バイトのヘッダーに続いて生のペイロードが続く形式です:
| オフセット | サイズ | フィールド | 備考 |
|---|---|---|---|
| 0 | 2 | マジック | 0xDEAD、リトルエンディアン、通信上ではAD DE |
| 2 | 2 | オペコード | リトルエンディアン |
| 4 | 4 | 長さ | リトルエンディアン、0x1000000(16MiB)を上限とする |
ちょうどlengthバイト分のペイロードが続きます。終端マーカーもチェックサムも暗号化層もありません。送信側はFUN_140039620、受信側はFUN_140017050で同じ構造を検証します。
このマジックは命令ストリーム内でも読み取れます。0x140039634ではバイト列B8 AD DE 00 00がmov eax, 0xDEADに逆アセンブルされ、これはフレームが送出される前にヘッダーに読み込まれる定数です。
通信は暗号化されておらず、ChaCha20はソケットに一切関与しない
両方向とも最初から最後まで追跡しました。送信側では、FUN_140039620がヘッダーを構築し、送信全体を担うループであるFUN_140039380にバッファを渡します。受信側では、FUN_140017050がヘッダーを検証し、受信全体を担うループであるFUN_140038590を通じてボディを読み込みます。いずれの経路にも暗号プリミティブは一切登場せず、バッファはそのままの状態でソケットに到達し、離れていきます。
これは正確に述べておく価値があります。というのも、このバイナリには実際に最新の暗号技術が含まれているからです。FUN_140010710はRFC 8439で規定されたChaCha20です。その唯一の呼び出し元はFUN_140019040で、これはChaCha20-Poly1305 AEADラッパーであり、そのさらに唯一の呼び出し元であるFUN_1400197a0は、ChromeのApp-Bound Encryptionの鍵を復号するためのものです。.dataにハードコードされている3つの鍵:
B31C6E241AC846728DA9C1FAC4936651
E98F37D7F4E1FA433D19304DC2258042
CCF8A1CEC56605B8517552BA1A2D061C
はこの経路に属します。これらは通信用の鍵ではなく、認証情報窃取用の資材です。
もう1つのアーティファクトは、当初誤った方向を示唆していました。ChaCha20のシグマ定数expand 32-byte kは.rdata内の0x14014E248に存在し、定数スキャナーが期待する通りの場所にあり、その存在はストリーム暗号がプロトコルをラップしていることを示唆するように見えます。
しかしこれには相互参照がゼロです。.text内のRIP相対変位はそのアドレスを一切解決しておらず、ファイル内にそこへの絶対ポインタも存在しません。これはChaCha実装が残した死んだデータであり、稼働中の暗号は別のコピーからその状態をChromeキー経路上で初期化しています。したがって暗号定数だけを見て評価すると、誤った通信評価に至ってしまうことになります。
これが一般的な誤りのパターンです。暗号定数やインポートだけから評価を組み立てると、バイナリを逆に読んでしまい、チャネルは保護されていると誤って結論づけてしまいます。これを解決するのは参照グラフだけです。コマンド&コントロールは平文で行われており、その流れを観測できる位置に置かれたセンサーであれば、そこに含まれるすべてのフレームを解析できます。
ハンドシェイク
getaddrinfo、続いてsocket、続いてconnect。- 何かを送信する前にソケットオプションが設定されます:
TCP_NODELAY、SO_SNDBUF0x80000、SO_RCVBUF0x40000、30000msのタイムアウト、そして400000msのアイドルと5000msのプローブ間隔を持つSIO_KEEPALIVE_VALS。 - クライアントは最初のフレーム
AD DE 02 00 10 02 00 00を送信します。マジック、オペコード0x02、長さ0x210です。それに続く528バイトは、FUN_14000e760によって構築される固定フィールド構造体です:
| オフセット | サイズ | 内容 |
|---|---|---|
0x000 |
0x40 |
HWID:ComputerName+ボリュームシリアル番号 |
0x040 |
0x40 |
ComputerName |
0x080 |
0x40 |
UserName |
0x0C0 |
0x40 |
RtlGetVersionから得たOSバージョン |
0x100 |
0x10 |
アーキテクチャ |
0x110 |
0x40 |
未特定 |
0x150 |
0x80 |
フォアグラウンドウィンドウのタイトル |
0x1D0 |
0x40 |
予約 |
- クライアントはその後、処理を進める前にオペコード
0x03のフレームを待ちます。 - アクティブなウィンドウタイトルを運ぶビーコンをオペコード
0x90で開始し、即座に1回、その後は変更があるたびに、250msの周期で送信します。 - メインループに入り、ヘッダーを受信・検証し、本文を受信して処理を振り分けます。
前節で述べた35フィールドのJSONは、この手順の一部ではありません。接続時のプロファイルは528バイトの固定フィールド構造体であり、フィンガープリント文書はオペレーターがコマンド0x35で要求した時にのみ生成されます。
59個のコマンド
ディスパッチャーはFUN_140019c20です。opcode > 0xF9となる値は拒否され、0x14001B694にあるジャンプテーブルにインデックスされます。59個のオペコードにハンドラーが存在します。支配的な慣例として、応答オペコードはコマンドオペコードに1を加えたものです。0x00には0x01で応答し、0x20は画面ストリームを開始してフレームは0x21として返ってきます。
そのうち最も重要なものは以下の通りです:
| オペコード | 機能 |
|---|---|
0x00 |
Ping、0x01で応答 |
0x20 |
画面ストリーム開始、フレームはオペコード0x21で送信 |
0x23 |
リモートマウス:動作、x、y |
0x24 |
リモートキーボード:押下/離す、仮想キー |
0x33 |
ファイルのステージングと自己更新 |
0x35 |
完全収集:ブラウザ情報窃取、35フィールドのJSONをオペコード0x64で返却 |
0x38 |
プロセス一覧 |
0x3A |
PID指定でのプロセス強制終了 |
0x42 |
リモートシェルのstdinへの書き込み |
0x44 |
ファイルマネージャーとドライブ一覧 |
0x4A |
LockWorkStation、画面解像度変更、ExitProcess |
0x4C |
ファイルまたはディレクトリの再帰的な削除 |
0x4D |
ShellExecute経由の実行 |
0x80 |
マイクキャプチャ、44.1kHz、4バッファ |
0xA8 |
ストリーム品質とフレームレート、10~100にクランプ |
0xA9 |
クリップボードへの書き込み |
0xB0 |
任意のホストへのリバースプロキシ接続 |
0xC7/0xC8 |
BlockInputのオン/オフ |
0xCB |
HKCU Run配下の永続化、設定または削除 |
0xD0 |
PEインジェクションとホロウイング |
0xE0 |
TCP接続テーブル |
0xE2 |
名前指定でのプロセス強制終了 |
0xE3 |
SetTcpEntryによるTCP接続終了、12バイトのペイロード |
0xF7 |
キーログファイルの取得 |
このコマンドセットは、情報窃取ツールのコマンドリストではなく、完全な対話型リモートアクセス面です。画面、マウス、キーボード、入力ブロックはセッションをカバーし、ファイルマネージャー、プロセス制御、ShellExecute、PEインジェクションはホストをカバーし、レジストリの永続化と自己更新は足場を確保します。
0xB0:出口ノードとしての被害者
1つのオペコードには個別の注意を払う価値があります。0xB0はオペレーターが指定したホストへの接続を開き、それを経由してリレーを行います。これはリバースプロキシであり、この表の他のすべての機能とは異なる種類の機能です。ホストからデータを読み取るわけでもホストを制御するわけでもありません。ホストをオペレーターのためのネットワーク出口地点にするだけです。
これはまた、本レポートがこれまで説明してきたパターンを完結させます。クローンされたプロファイルは認証済みセッションを提供します。35フィールドの収集は、そのセッションが提示すると想定されるデバイスIDを提供します。0xB0は、リスクエンジンがクライアント側から評価できる第3の軸、すなわちネットワーク発信元情報を提供します。これを経由して送信されたトラフィックはオペレーターのアドレスではなく被害者自身のアドレスから発信されるためです。Cookie、デバイス、ネットワーク。1つのバイナリの中に、それぞれ異なるチェック項目をカバーする3つの機能があるのです。
このプロトコルが教えてくれないこと
取り繕わずに述べておくべき3つのギャップがあります:
0x03の確認応答のペイロードはソケットから読み込まれますが、そのバッファの消費者は特定できませんでした。この時点でサーバーが何を送り返してくるかは確定していません。- 9つのオペコードにはハンドラーは特定できているものの、機能は未解決です:
0x30、0x31、0x48、0x49、0xA0、0xA1、0xC0、0xC4、0xF5。 - helloの構造体のオフセット
0x110にある0x40バイトのフィールドは特定できていません。
ネットワークシグネチャ
クライアントが送信する最初の8バイトは固定されています:マジック、オペコード0x02、長さ0x210です。ビルダーが.cfgセクションにパッチで書き込むホストやポートが変わっても、これらは変化しません。したがってこのhelloは、##CFG_START##マーカーがファイルレベルで不変のアーティファクトであるのと同じように、ビルド不変のネットワークアーティファクトです。ここから直接2つのルールが導かれます:
alert tcp $HOME_NET any -> $EXTERNAL_NET any (
msg:"Evolution RAT C2 hello (magic 0xDEAD, opcode 0x02, 528B sysinfo)";
flow:established,to_server;
content:"|AD DE 02 00 10 02 00 00|"; depth:8; offset:0;
threshold:type limit,track by_src,count 1,seconds 300;
classtype:trojan-activity; sid:1000001; rev:1;)
alert tcp $HOME_NET any -> $EXTERNAL_NET any (
msg:"Evolution RAT active-window beacon (opcode 0x90)";
flow:established,to_server;
content:"|AD DE 90 00|"; depth:4; offset:0;
classtype:trojan-activity; sid:1000002; rev:1;)
1つ目のルールは、初回のチェックイン時に送信元1つあたり5分に1回だけ発火します。2つ目のルールは、既に確立された感染をとらえます。ウィンドウタイトルのビーコンはセッションが続く限り送信され続け、その先頭4バイトも同様に固定されているためです。
副次的なシグナルとして、キープアライブの設定は特徴的です。400秒のアイドルと5秒のプローブ間隔という組み合わせは、数分間沈黙した後に密なプローブのバーストを発する、長期間持続するTCPセッションを生み出します。非標準ポートへの持続的な接続において、この時間パターンは、内容とは独立して単独でも監視する価値があります。
防御上の優先事項
Evolution RATは4つの異なる層で持続的なシグナルを露呈しています。防御側は、このツールキットを検知したり、その狙いを封じ込めたりするために、今回のキャンペーンで使われている現在のDuckDNS名を必要としません。
- ファイルと永続化の検知。大きく反転したテキスト領域を含むXHTMLファイルと、それに付随する小さなStartup VBSローダーを探してください。ペアになった
ckCGj9というファイル名、2EDe7CMv96というコメントマーカー、data-key="8MxUjqEtBpLuwtsg"はこのキャンペーン固有のものですが、##CFG_START##と##CFG_END####は設定に関わらずこのペイロードビルダーを特定します。 - 実行のテレメトリ。
wscript.exeが-ExecutionPolicy Bypass付きでPowerShellを起動する動き、ランダムな名前の%TEMP%ディレクトリ配下の一時的な.ps1ファイル、そして予期しないAppLaunch.exeの子プロセスやホロウイングされたイメージを相関付けてください。ユーザーのStartupフォルダ配下に後から現れるWindowsDefender.exeは、2つ目の高信頼度のシグナルです。 - ネットワークの検査。この平文プロトコルにより、内容による検知が実用的になります。固定されたクライアントhello
AD DE 02 00 10 02 00 00、アクティブウィンドウビーコンのAD DE 90 00、そして400秒/5秒のキープアライブパターンは、C2のホスト名やポートが変わっても有効なままです。 - ブラウザとIDへの対応。プロファイルのクローン化を踏まえると、修復措置はマルウェア除去とパスワードのローテーションにとどまってはいけません。サーバー側でアクティブなアプリケーションセッションを終了させ、影響を受けたデバイスとネットワークからのアカウント活動を確認し、適切な場合はMFAを再登録し、ブラウザCookieとデバイスフィンガープリントデータが漏洩したものとして扱ってください。
- 封じ込めの順序。オペレーターはTCP接続を列挙・終了させ、システムプロキシを変更し、入力をブロックし、HVNCを通じてアクセスを維持することができます。侵害されたデスクトップ内でのみ実行される対応に頼るのではなく、ネットワーク制御プレーン側でエンドポイントを隔離してください。
収集時点での公開データベースにおける検知状況
2つのPE段階について、OTX、MalwareBazaar、ThreatFox、URLhausと照合しました:
| サンプル | ヒットのあった公開ソース数 |
|---|---|
| .NETインジェクター(9,728バイト) | 4件中0件 |
| x86-64ペイロード(1,972,736バイト) | 4件中1件(OTXのみ) |
Caronteはこのペイロードをinfostealer with hVNCと分類し、レピュテーションデータに一切依存せず、挙動のみから高という判定を下しました。
収集時点で、181.237.42.61はポート80でドロッパー群に応答しており、4577での接続も受け付けていました。いずれの観測も2026-08-13時点のものであり、本レポートのいかなる記述もこのホストの現在の状態に関する主張として読むべきではありません。同じurlscanの観測期間内にあった2つの姉妹URLは1日以内にオフラインになっており、こちらのホストも動的DNS上にあります。
侵害指標(IOC)
Stage hashes (SHA256):
Stage 1 VBScript dropper 3,346,929 B
c8f229c7843f5fec053c04fb78b2ed045c029671412d74a07ce40608eeef1797
served as sostener2.vbs and envifa.vbs, byte-identical
Stage 3 PowerShell loader 2,481,247 B
a49c01a5cc1c06be5f2e6cbd0f63554c7755e45f3931c08541da643014c8c9e5
= AES-256-ECB(base64_decode(343-literal blob)), PKCS7 stripped
Stage 4 .NET injector 9,728 B
5b5f3f49769493f6ed3de978d41bc4bb8dd286467becb4d25036f037462cb013
Stage 5 x86-64 payload (Evolution RAT) 1,972,736 B
7a3c619827557de9a3687daa137f772f40e1bba1a4ca32bac1b06557f42ce522
Nested HVNCInjection module carved from stage 5 offset 1810200 to EOF
6f6b10e5cce18005231da32a4344e764d7c1e4f22bcc1d78f264561966a1b46d
(162,536 B as carved; the module's section table ends at 156,160 B,
so this hash depends on the carve boundary, not on the module alone)
Stage 2 is generated at runtime by stage 1 and has no fixed hash.
Dropper:
Names: sostener2.vbs, envifa.vbs (byte-identical)
SHA256: c8f229c7843f5fec053c04fb78b2ed045c029671412d74a07ce40608eeef1797
Size: 3,346,929 bytes (98.9% of it is the base64 payload literal)
Type: Obfuscated VBScript. XOR 0x99 strings; payload split across 343
string literals concatenated in non-declaration order; 34 decoy
functions; 170 constant-false padding lines (~4 KB total)
Dropped / staged files:
%TEMP%\<hex>.ps1 stage 2, generated AES decryptor
(UTF-8 no BOM, deleted by stage 1)
%TEMP%\<hex><hex>\ClJdZcnG.ps1 stage 3, self-deleted after 3 s
(fixed basename "ClJdZcnG")
Launch: powershell.exe -NoProfile -ExecutionPolicy Bypass -File "<stage 2>"
stage 3 is then invoked in-process with the call operator, so it
never appears as a separate command line
Distribution:
Host: asegurar2026.duckdns.org
IP: 181.237.42.61 (Colombia)
URLs: http://asegurar2026.duckdns.org/sostener2.vbs
http://asegurar2026.duckdns.org/envifa.vbs
http://181.237.42.61/envifa.vbs
Server: Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.0.30
(Apache on Windows; consistent with a compromised host)
C2:
Host: serversniperxx.duckdns.org
Port: 4577 (0x11E1)
IP: 181.237.42.61 <-- same machine as distribution
Protocol: framed binary over raw TCP, cleartext (does not speak HTTP)
Stored in PE section .cfg, RVA 0x1A0000, file offset 0x193600,
virtual size 163 bytes, cleartext:
+0 13 "##CFG_START##"
+13 3 zero padding
+16 128 host, null-padded
+144 2 port, uint16 LE (E1 11 = 4577)
+146 1 zero padding
+147 13 "##CFG_END####"
+160 3 zero padding
C2 protocol:
Frame: [magic uint16 LE 0xDEAD][opcode uint16 LE][length uint32 LE][payload]
no closing marker, no checksum, no encryption; length cap 0x1000000
Functions: FUN_140039620 (TX framing) -> FUN_140039380 (send_all)
FUN_140017050 (RX validation) -> FUN_140038590 (recv_all)
magic at 0x140039634: B8 AD DE 00 00 (mov eax, 0xDEAD)
Hello: AD DE 02 00 10 02 00 00 + 528-byte fixed-field struct (FUN_14000e760)
0x000 0x40 HWID (ComputerName + volume serial)
0x040 0x40 ComputerName
0x080 0x40 UserName
0x0C0 0x40 OS version (RtlGetVersion)
0x100 0x10 architecture
0x110 0x40 unidentified
0x150 0x80 foreground window title
0x1D0 0x40 reserved
Server ACK: opcode 0x03 (client waits for it before proceeding)
Beacon: opcode 0x90, active window title, immediately then on change (250 ms)
Socket: TCP_NODELAY, SO_SNDBUF 0x80000, SO_RCVBUF 0x40000, timeout 30000 ms
SIO_KEEPALIVE_VALS idle 400000 ms, interval 5000 ms
Dispatcher: FUN_140019c20, guard opcode > 0xF9, jump table 0x14001B694, 59 opcodes
response opcode = command opcode + 1 (dominant convention)
Notable opcodes:
0x00 ping (reply 0x01) 0x20 screen stream (frames 0x21)
0x23 remote mouse 0x24 remote keyboard
0x33 file staging/self-update 0x35 full collection (JSON via 0x64)
0x38 process list 0x3A kill process by PID
0x42 shell stdin write 0x44 file manager / drive listing
0x4A LockWorkStation, resolution change, ExitProcess
0x4C delete file/dir 0x4D ShellExecute
0x80 microphone (44.1 kHz) 0xA8 stream quality/FPS (clamp 10..100)
0xA9 clipboard write 0xB0 reverse proxy to arbitrary host
0xC7 / 0xC8 BlockInput on/off 0xCB HKCU Run persistence (set/delete)
0xD0 PE injection/hollowing 0xE0 TCP connection table
0xE2 kill process by name 0xE3 SetTcpEntry kill (12-byte payload)
0xF7 keylog file retrieval
Handler identified, capability unresolved:
0x30 0x31 0x48 0x49 0xA0 0xA1 0xC0 0xC4 0xF5
Malware identity:
Family: Evolution RAT
PDB: C:\Users\USER\Desktop\Evolution RAT\Overlord-Server\dist-clients\HVNCInjection.pdb
PDB GUID: 59965EFB-F602-42DC-94D0-FCF15841EFC1 age=1
Note: PDB is in a PE nested at offset 1810200 of the payload (inside the
.rsrc range, raw 0x193800..0x1E0200). RSDS record at offset 1944736.
The outer payload's debug directory has a single POGO record.
Second confirmation: "Overlord HVNC DLL Loaded" written to
%TEMP%\crashlogovd.log by the nested module
Client ID format: SniperX_%d
Crypto:
Stage 2: AES-256-ECB / PKCS7 (decrypts stage 3)
Key: Be1OluQXuNlV3c/vviqtY1xEW70mAk13+uxHyLqeTZ8= (base64)
= 05ed4e96e417b8d955ddcfefbe2aad635c445bbd26024d77faec47c8ba9e4d9f
Stage 3: Base85 with non-standard alphabet (carries stage 5);
stage 4 travels in the same script as plain base64
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~
Stage 4: API name strings XOR 0x2F, resolved from ntdll.dll at runtime
Payload: ChaCha20-Poly1305 (RFC 8439) at FUN_140010710 / FUN_140019040,
reached only from FUN_1400197a0 (Chrome App-Bound Encryption key
decryption). Not used for C2 traffic.
Decoy: "expand 32-byte k" at 0x14014E248 in .rdata has ZERO xrefs.
Dead data. Do not infer an encrypted C2 channel from it.
Hardcoded keys in .data:
B31C6E241AC846728DA9C1FAC4936651
E98F37D7F4E1FA433D19304DC2258042
CCF8A1CEC56605B8517552BA1A2D061C
Persistence:
%APPDATA%\Microsoft\manifest_ckCGj9.xhtml
C:\Users\Public\manifest_ckCGj9.xhtml
(fake XHTML: <title>Windows Security</title>,
container <div id="ms-2EDe7CMv96" data-key="8MxUjqEtBpLuwtsg">,
markers <!--2EDe7CMv96--> ... <!--/2EDe7CMv96-->,
body = StrReverse of the original VBScript)
Startup\CfgShieldckCGj9.vbs
(header: "Windows SmartScreen Filter Config" /
"Version 4.8.494 - Microsoft Corporation" /
"(c) 2026 Microsoft. All rights reserved."
XOR 0xCD strings, ExecuteGlobal;
unused decoy strings: Microsoft.Update.Session,
Win32_OperatingSystem, root\SecurityCenter2)
Installed only when the running script is not already in Startup
Start Menu\Programs\Startup\WindowsDefender.exe (payload self-copy)
Payload also copies itself as svchost.exe
Injection:
.NET injector type/method: yMOxml.nRCN06Y :: C3FrcKQp
Loaded via: Reflection.Assembly::Load
Entry args: (payload bytes, target process path)
Hollowing target: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\AppLaunch.exe
(Framework\ instead of Framework64\ on 32-bit)
Declared P/Invokes (kernel32, all the metadata reveals):
GetModuleHandleA, GetProcAddress, VirtualAlloc,
CreateProcessA, CloseHandle
Real API set, strings XOR 0x2F, resolved from ntdll.dll at runtime:
NtUnmapViewOfSection, NtAllocateVirtualMemory, NtWriteVirtualMemory,
NtGetContextThread, NtSetContextThread, NtResumeThread
(bound via Marshal.GetDelegateForFunctionPointer)
NOTE: the Win32 names VirtualAllocEx / WriteProcessMemory /
SetThreadContext / ResumeThread do NOT appear in this injector.
Filesystem (payload):
C:\ProgramData\SniperLogs\
C:\ProgramData\SniperLogs\keylog_%04d%02d%02d_%02d%02d%02d.txt
modules\hvnc_injection.dll (reflectively loaded, ReflectiveLoader export)
Mutexes / synchronization:
hvnc:ready hvnc:launch_ok hvnc:launch_fail
hvnc_watchdog:stuck Local\hvnc_rdi_ SingletonSocket
Registry:
Software\Microsoft\Windows\CurrentVersion\Internet Settings
ProxyServer set to 127.0.0.1:1 (dead proxy; severs the host's internet access)
Command execution:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "<%TEMP%\<hex>.ps1>"
powershell -WindowStyle Hidden -Command "Add-MpPreference -ExclusionPath '
powershell -WindowStyle Hidden -Command "Add-MpPreference -ExclusionProcess '
cmd.exe /Q (remote shell host)
taskkill /IM <browser> /F (before profile clone)
Hooked APIs (MinHook, browser profile redirection):
NtCreateFile NtOpenFile NtDeleteFile NtSetInformationFile
NtQueryAttributesFile NtQueryFullAttributesFile
NtQueryDirectoryFile NtQueryDirectoryFileEx
CreateProcessW
Browser targets (~20):
Chrome, Edge, Brave, Opera, OperaGX, Vivaldi, Chromium, Yandex,
CentBrowser, Iridium, CocCoc, Torch, Comodo Dragon, Epic Privacy,
Slimjet, URBrowser, 7Star, Amigo, Sputnik, Firefox
Profile-clone artifacts:
Clone directory name: hvnc_%08X
Deleted from the copy: SingletonLock SingletonCookie
parent.lock .parentlock
Locked files are read by duplicating handles out of the locking process
(SeDebugPrivilege + NtQuerySystemInformation handle enumeration).
Log strings: "Hijack: %d locking PIDs for %s",
"Hijack: %llu handles, searching...",
"Hijack OK: %s (%lld bytes)",
"CloneProfile: TIMEOUT after 60s"
lite mode skips: cache, code cache, service worker, blob_storage,
jumplisterrors, extensions, extension state/rules/scripts,
local/sync extension settings, managed extension storage,
segmentation_platform, commerce_local_db,
optimization_guide_prediction_model_downloads
Chrome App-Bound Encryption:
app_bound_encrypted_key handled
NCryptDecrypt with key name "Google Chromekey1"
Fingerprint fields (35), returned with opcode 0x64 on command 0x35:
os_name os_version os_platform machine_name user_name domain
processor_count system_dir screen_width screen_height screen_depth
timezone timezone_offset timezone_display language language_display
languages cpu_name cpu_id cpu_cores cpu_threads gpu_name gpu_driver
gpu_ram ram_gb device_memory mac_address disk_serial fonts fonts_count
browsers chrome_version user_agent webgl_vendor webgl_renderer
Browser-facing values are synthesized into Chrome's own formats:
user_agent Mozilla/5.0 (Windows NT %lu.%lu; %s) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/%s Safari/537.36
platform token "Win64; x64"
chrome_version from HKCU Software\Google\Chrome\BLBeacon "version",
fallback literal 131.0.6778.86
webgl_vendor "Google Inc. (" + <Win32_VideoController vendor> + ")"
webgl_renderer "ANGLE (" + <adapter> + ", Direct3D11 vs_5_0 ps_5_0, D3D11)"
device_memory %.0f from Win32_OperatingSystem TotalVisibleMemorySize
ram_gb %.1f same source, operator-facing precision
languages <UI language> + ",en-US"
rule Evolution_RAT_VBS_Dropper_XHTML_Persistence {
meta:
description = "VBScript dropper for Evolution RAT: stores its own reversed source inside a fake XHTML file and reloads it from a Startup loader"
author = "Beelzebub Research"
date = "2026-08-13"
reference = "https://beelzebub.ai/blog/evolution-rat-hvnc-browser-session-hijacking"
strings:
$marker = "2EDe7CMv96" ascii
$manifest = "manifest_ckCGj9" ascii
$loader = "CfgShieldckCGj9" ascii
$datakey = "8MxUjqEtBpLuwtsg" ascii
$title = "<title>Windows Security</title>" ascii
$hdr = "Windows SmartScreen Filter Config" ascii
$f1 = "StrReverse" ascii nocase
$f2 = "ExecuteGlobal" ascii nocase
$pad = "If 0 Then Randomize Timer" ascii nocase
condition:
(any of ($marker, $manifest, $loader, $datakey))
and 2 of ($title, $hdr, $f1, $f2, $pad)
}
rule Evolution_RAT_PowerShell_Stage {
meta:
description = "Evolution RAT stage 3/4: AES-256-ECB decrypted PowerShell loader carrying a Base85 blob, reflective .NET injector load and AppLaunch.exe hollowing"
author = "Beelzebub Research"
date = "2026-08-13"
reference = "https://beelzebub.ai/blog/evolution-rat-hvnc-browser-session-hijacking"
strings:
$key = "Be1OluQXuNlV3c/vviqtY1xEW70mAk13+uxHyLqeTZ8=" ascii wide
$b85 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~" ascii wide
$dec = "Decode-Base85" ascii wide
$tgt = "AppLaunch.exe" ascii wide nocase
$refl = "Reflection.Assembly" ascii wide
$type = "yMOxml.nRCN06Y" ascii wide
$meth = "C3FrcKQp" ascii wide
$mode = "ECB" ascii wide
$pad = "PKCS7" ascii wide
$ps = "-ExecutionPolicy Bypass" ascii wide nocase
condition:
// $b85 is the strongest anchor: the alphabet is a toolkit constant and
// survives rebuilds, while $key and the type/method names may not.
$b85
or $key
or (3 of ($dec, $tgt, $refl, $type, $meth, $mode, $pad, $ps)
and any of ($type, $meth))
}
rule Evolution_RAT_DotNet_Injector_XOR_NT_API {
meta:
description = "Evolution RAT stage 4: 9,728-byte .NET injector that resolves native NT hollowing APIs from XOR 0x2F encoded strings, so the Win32 names never appear"
author = "Beelzebub Research"
date = "2026-08-13"
reference = "https://beelzebub.ai/blog/evolution-rat-hvnc-browser-session-hijacking"
strings:
// API names as stored: plaintext XOR 0x2F
$x_unmap = "a[zABN_yFJX`I|JL[F@A" ascii // NtUnmapViewOfSection
$x_alloc = "a[nCC@LN[JyF][ZNCbJB@]V" ascii // NtAllocateVirtualMemory
$x_write = "a[x]F[JyF][ZNCbJB@]V" ascii // NtWriteVirtualMemory
$x_getctx = "a[hJ[l@A[JW[{G]JNK" ascii // NtGetContextThread
$x_setctx = "a[|J[l@A[JW[{G]JNK" ascii // NtSetContextThread
$x_resume = "a[}J\\ZBJ{G]JNK" ascii // NtResumeThread
$x_ntdll = { 41 5B 4B 43 43 01 4B 43 43 } // ntdll.dll
$type = "yMOxml" ascii
$meth = "C3FrcKQp" ascii
condition:
uint16(0) == 0x5A4D
and (
3 of ($x_*)
or ($x_ntdll and any of ($type, $meth))
)
}
rule Evolution_RAT_Payload_Config_Section {
meta:
description = "Evolution RAT x86-64 payload: builder config markers in the .cfg PE section plus HVNC, GhostStandby and SniperLogs artifacts. The ##CFG_START## markers are build-invariant and match any operator's host/port."
author = "Beelzebub Research"
date = "2026-08-13"
reference = "https://beelzebub.ai/blog/evolution-rat-hvnc-browser-session-hijacking"
strings:
$cfg_start = "##CFG_START##" ascii
$cfg_end = "##CFG_END####" ascii
$h1 = "hvnc:ready" ascii
$h2 = "hvnc:launch_ok" ascii
$h3 = "hvnc:launch_fail" ascii
$h4 = "hvnc_watchdog:stuck" ascii
$h5 = "hvnc_injection.dll" ascii
$g1 = "GhostStandby" wide
$s1 = "SniperLogs" ascii
$s2 = "SniperX_" ascii
$s3 = "CloneProfile" ascii
$s4 = "Google Chromekey1" wide
$ov = "Overlord HVNC DLL Loaded" ascii
$wgl = "ANGLE (" ascii
$ua = "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36" ascii
condition:
uint16(0) == 0x5A4D
and (
($cfg_start and $cfg_end)
or $ov
or 3 of ($h1, $h2, $h3, $h4, $h5, $g1, $s1, $s2, $s3, $s4, $wgl, $ua)
)
}
結論
この5段階のチェーンはカモフラージュであり、この話の到達点ではありません。反転ソースによる永続化、XOR文字列、AES、独自Base85アルファベット、リフレクションによるロード、プロセスホロウイング、それぞれが異なる検知面を取り除きながら、ペイロードがブラウザに到達できるようにしています。そこで起きていることこそが、Evolution RATをありふれた情報窃取ツール群から分ける要因です。
この攻撃は4つの部品から構成されています。Cookieは認証済み状態を提供します。CloneProfileは被害者のブラウザを停止させることなくその状態を起動可能にします。35フィールドの収集は、ChromeのANGLE (...)レンダラー構文に至るまで、不正検知システムが期待するデバイスIDを再現します。オペコード0xB0は、被害者のネットワークを通じてトラフィックを送出可能にします。これらが組み合わさることで、盗まれたセッションは同一のデバイスと接続から戻ってきたユーザーのように見えるのです。Chromeのために GPU名をフォーマットする理由が、単なるホストの棚卸しであるはずがありません。
最も強力な検知手法もまた、1つのキャンペーンのインフラを超えて存在します。serversniperxx.duckdns.orgとポート4577は1つのビルドを特定するものであり、ビルダーの##CFG_START##/##CFG_END####マーカーはこのツールキット自体を特定します。固定されたAD DE 02 00 10 02 00 00のhelloも、ネットワーク上で同じ役割を果たします。ホストは変わっても、製品としての制約は残り続けるのです。
Caronteが役立った点、役立たなかった点
この種のレポートは、あたかもツールがすべての作業をこなしたかのように語られがちです。今回はそうではなく、その内訳を正確に述べる価値があります。うまくいかなかった部分の方が、うまくいった部分よりも示唆に富んでいるからです。
Caronteが行わなかったこと:このチェーンを展開しませんでした。このVBScriptはまずCaronteに提出されました。その分析はサブ分析をゼロ件しか返しませんでした。343個のリテラルの連結を解決せず、生成されたPowerShellからAES鍵を回収せず、第3段階を復号せず、独自のBase85をデコードすることもなく、したがって最終的な2つのPEファイルには一切到達しませんでした。本レポートにおいて、ドロッパーからx86-64バイナリに至るまでのすべてのステップは、先の各節で説明したスクリプトを用いて手作業で行われました。Caronteが分析したペイロードは、その作業が完了した後に、別のサンプルとして手動で提出されたものです。
これは現実に存在するギャップであり、その形は明確です。このチェーンは5段階のフォーマット変換であり、それぞれが異なるエンコーディングであり、そのデコーダーは上位段階に埋め込まれています。これを展開するには、サンプルが何をするかを推論するのではなく、サンプルが記述する変換を実際に実行する必要があります。この2つは異なる作業であり、後者のために構築されたエンジンは前者を自動的にはこなせません。
Caronteが行ったこと:ペイロードをマッピングしました。x86-64バイナリを与えると、GhostStandbyとSetWindowDisplayAffinity(0x11)による画面キャプチャ除外、winlogon.exeトークンなりすまし経路、キー名Google Chromekey1を伴うNCryptDecrypt呼び出し、Defender除外コマンド、SQLite読み込みに先立つブラウザスレッド一時停止、hvnc:*ミューテックス群一式、SniperLogs、そしてそれを実装する具体的な関数アドレスを伴うプロセスホロウイングの手順を特定しました。この最後の点は率直に述べる価値があります。手作業のパスは挙動のリストを生成しますが、Caronteはアドレスに紐付いた挙動のリストを生成しました。これは異なる成果物であり、より有用なものです。infostealer with hVNCという高判定の分類は、レピュテーション照会ではなく、この挙動的な証拠から導かれたものです。
また、このファミリーを特定づける2つの識別子も、コードについての推論ではなく決定論的な抽出によって導き出しました。
C2アドレスは、逆コンパイルが辿るいかなる命令からも参照されていません。これはコンパイル後にビルダーが書き込んだ、.cfgという非標準のPEセクション内に存在しています。すべてのセクションを列挙し、サイズが異常なものをダンプすれば、1ステップでこれが見つかります。上記の要約はserversniperxx.duckdns.orgを特定し、末尾の0x11e1を、##CFG_START##と##CFG_END####マーカーの間にある設定blobから直接ポート4577として読み取っています。
PDBパスもまた、このペイロードのデバッグディレクトリには存在しません。そこにはPOGOレコードが1つあるだけです。それはリソースセクションの生のレンジ内、オフセット1810200に埋め込まれた別のPE内にあります。逆コンパイルされたコードについて推論してもそこには到達できません。ファイルを走査し、有効なPEシグネチャを伴うMZヘッダーをすべて切り出し、それぞれのデバッグディレクトリを解析することで到達できます。この切り出しにより、構造化された指標としてpdb_pathと59965EFB-F602-42DC-94D0-FCF15841EFC1というビルドGUIDが得られました。
この切り出されたモジュールを構造的な文脈、すなわちオフセット、サイズ、セクションごとのエントロピー、3つのDLLからなるインポートリストとして与えると、この実行はそのモジュールを抽出し、それ自体を独立した分析としてキューに登録しました:
このモジュール単体のスコアは0で、ネットワークや永続化のロジックは一切保持していません。これは純粋にローダーであり、ファイルAPIのフック群にすぎず、実際の機能はこれが注入する第2段階の中に存在しています。このモジュール自身の要約は、%TEMP%\crashlogovd.logに書き込むOverlord HVNC DLL Loadedバナーを名指ししており、これがPDBパスに続く、このファミリー名の2つ目の独立した裏付けとなっています。
したがって、正直な総括は二分されたものになります。バイナリに対しては、このエンジンが分析を主導し、手作業のパスよりも多くの構造を生み出しました。マルチステージのスクリプトチェーンに対しては、最初のエンコーディングで止まり、残りは人間が処理しました。ネスト構造のPE切り出しと異常セクションの列挙は、いまや抽出パスとして提供されており、これは2つのギャップのうち小さい方を埋めるものです。スクリプトチェーンの再帰的なアンパックはより大きなギャップであり、それは依然として未解決のままです。
翻訳元: https://beelzebub.ai/blog/evolution-rat-hvnc-browser-session-hijacking/