TorNetとPureHVNCを実行する新種のローダーの調査

2025年5月頃、これまで観測されていない新種のローダーを含む複数のファイルが圧縮されたZIPファイルがVirusTotalにアップロードされました。このローダーは2種類のマルウェア (TorNetとPureHVNC) を実行する点や特徴的なアルゴリズム (MurmurHash2) によるAPI Hashingが実装されている点など、従来のローダーではあまり見られない複数の特徴を有していました。この記事ではローダーをマルウェア解析することで得られた情報について共有します。
ZIPファイル
ZIPファイルには正規の実行ファイル、ローダー、処理をフォワーディング (プロキシ) するための正規のDLLファイル、そして複数のサイズが大きいファイル (正規ソフトウェアのインストーラー) を含んだフォルダが含まれていました。サイズが大きいファイルについては、ZIPファイルを肥大化させてセキュリティ製品のスキャンを回避することを狙って含めたものだと思われます。

正規の実行ファイルを除くZIPファイル内の全てのファイル・フォルダには、隠しファイル・システムファイル属性が付与されています。Windowsのデフォルト設定では隠しファイルやシステムファイルはエクスプローラー上で表示されないため、一般ユーザーの視点では実行ファイルのみが展開先のフォルダに表示されます。

この実行ファイルはデジタル署名が付与された正規のファイルですが、DLL Sideloading1によって攻撃者が用意した悪性のDLLファイル version.dll
をロードします。前述の実行ファイルにロードされる本来の (正規の) version.dll
は、npHPReader64.dll
にリネームされています。そのため、攻撃者が用意した悪性のversion.dll
(以下ローダーとする) がロードされ、 npHPReader64.dll
に処理をフォワーディングしつつ、さらに複数のマルウェア (TorNetローダーとPureHVNCローダー) を他プロセスにインジェクションして実行します。
永続化
ローダーが実行されると、自動実行設定をレジストリに登録してマルウェアの永続化2を実現します。まず、%LOCALAPPDATA%
フォルダ下にnpHPReader64
フォルダを作成します。

npHPReader64
フォルダを作成その後、作成したフォルダにローダーの実行に必要なファイル (正規の実行ファイル、ローダー、正規のDLLファイル) をすべてコピーします。

最後に、レジストリ上に永続化のための設定を登録します。
- キー:
HKCU\Software\Microsoft\Windows\CurrentVersion\Run
- 値:
npHPReader64
- データ: コピーした正規の実行ファイルのパス

この際、ローダーはAPI関数の呼び出しの前後で、無意味な関数呼び出し (戻り値をEAXレジスタに格納しない関数の呼び出し) を多用しています。API関数の戻り値をデコンパイラのような解析ツールで追跡できないようにする解析妨害だと思われますが、ジャンクコードといった難読化を解除する要領で該当箇所をパッチすることで対応することができます。
MurmurHash2を用いたAPI Hashing
この先の処理で、ローダーは度々API Hashingによって動的に解決したWindows API関数を呼び出します。API Hashingとは、API関数名やDLLファイル名を任意のアルゴリズムでハッシュ化した状態で保持し、ハッシュ値をもとに動的にAPI関数のアドレスを取得して呼び出す攻撃手法です。

このローダーでは、シード値0xB801FCDAのMurmurHash2ベースのアルゴリズムを用いてAPI関数名やDLLファイル名をハッシュ化していました。著名な情報窃取型マルウェアであるLummaStealerにもMurmurHash2を用いたAPI Hashingが実装されていますが、今回のようなローダーに実装されているケースはこれまで観測されていませんでした。

実装の随所に見られる定数0x5BD1E995はMurmurHash2のマジックナンバーです。この定数とシード値、そしてハッシュ化の対象となる文字列をもとにハッシュ値が算出されます。例えば、LoadLibraryA
関数のハッシュ値は0x439C7E33です。
ローダー内のハッシュ値をもとに呼び出されているAPI関数を特定するスクリプトはAppendix Bに記載しています。

ペイロードの復号・展開
ローダーが実行するペイロードはLZMAで圧縮されて、AES-128-ECBで暗号化された状態でローダー内に存在します。永続化の設定が完了したら、ローダーはペイロードを実行するために、ペイロードの復号および展開処理を実行します。

記事冒頭で示した通り、このローダーは2つのペイロードを実行しますが、それぞれのペイロードが異なるAES鍵で暗号化されています。各ペイロードを復号するAES鍵は以下の通りです。
- ペイロード1 (TorNetローダー):
37C1FF3236DD4989153CCAC2CA712192
- ペイロード2 (PureHVNCローダー):
6CB15D6A5C9AB4C2B2885FF35836892A

ペイロードのSHA256ハッシュ値は記事末尾のAppendix Aに記載しています。
コードインジェクション
ペイロードが復号・展開できしだい、ローダーはCreateProcessA
関数を用いて、C:\Windows\Microsoft.NET\Framework\v4.0.30319\jsc.exe
のプロセスをサスペンド状態で作成します。

CreateProcessA
関数でサスペンド状態のプロセスを作成その後、VirtualAllocEx
関数やWriteProcessMemory
関数を用いて作成したプロセスの仮想アドレス空間内にペイロードを書き込みます。

最後に SetThreadContext
関数やResumeThread
関数を用いて作成したプロセスのコンテキスト内でペイロードを実行します。

.NET Frameworkで作成された簡易ローダー
jsc.exe
にインジェクションされたペイロードはどちらも.NET Frameworkで作成された簡易的なローダーでした。次段のペイロード (TorNetあるいはPureHVNC) はAES-256-CBCで暗号化してGZip圧縮された状態でバイト型の配列内に格納されています。

復号・展開したペイロードをAssembly.load
関数を使ってロードして、Delegate.CreateDelegate
関数で取得したペイロードのエントリーポイントを実行します。

TorNet
TorNetはTOR (The Onion Router) ネットワーク経由で悪意のあるサーバーに通信を行うマルウェア (ダウンローダー) です。
TorNetが実行されると、まずハードコードされた設定情報をBase64デコード、デシリアライズします。デシリアライズにはProtocol Buffersを利用します。この設定情報には通信先サーバーのアドレス、ポート番号などが含まれています。

その後、ミューテックスの作成や解析環境・サンドボックス環境でないかの確認3を経て、TORを一時フォルダにダウンロードしてから実行します。

TORが実行されると、localhost
のポート9050上でTORネットワーク経由で通信するためのSOCKSプロキシが利用可能になります。TorNetはこのSOCKSプロキシ (TORネットワーク) を経由し悪意のあるサーバーと通信して、DES3-ECBとGZipで暗号化および圧縮された任意の.NETアセンブリを受信してロードします。

PureHVNC
PureHVNCは攻撃者に感染端末の遠隔操作アクセスを提供する商用の遠隔操作マルウェア (RAT)4 です。
PureHVNCが実行されると、まずハードコードされた設定情報をBase64デコード、GZip展開、デシリアライズします。デシリアライズにはTorNetと同様にProtocol Buffersを利用します。この設定情報にはC2サーバーのアドレス、ポート番号、証明書などが含まれています。

次に、ハードコードされた設定値に応じて、このマルウェアを永続化するための処理を行います。これはPowerShellを利用してタスクスケジューラに登録することで行われますが、今回は1段目のローダーの時点で永続化設定を実施していたためか、この機能は無効化されていました。

同様に設定値に応じて、SetThreadExecutionState
関数を引数として0x80000003 (ES_CONTINUOUS | ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED
) を渡して呼び出します。これには、感染端末がスリープ状態に移行するのを防ぐ狙いがあると考えられます。この機能も今回の検体では無効化されていました。

SetThreadExecutionState
関数の呼び出しすべての初期化処理が終了しだい、PureHVNCはC2サーバーに初期通信5を行います。この通信にはAPI関数やWMIを用いて収集した感染端末に関する様々な情報が含まれています。

その後、C2サーバーからGZip圧縮されたペイロード (PureHVNCモジュール) を受信して実行するループ処理に入ります。この際、まず4バイト分のデータを受信して、それをペイロードのサイズとしてバッファを確保した後にペイロードを受信するような処理を実行しますが、これはTorNetがペイロードを受信する際の処理と全く同じものです。これ以外にも設定情報のデシリアライズ処理などに複数の共通点が見られることから、TorNetとPureHVNCは同じ開発者によって開発されたマルウェアである可能性があります。

なお、以前FortiGuard Labsが解析したPureHVNCのバージョン4.1.0では、受信したペイロードをレジストリ上に保存するような処理が存在しましたが、今回解析したPureHVNCのバージョン4.1.9ではその機能が削除されていました。これはフォレンジック調査等によってペイロードが保全されて、解析されることを防ぐために加えられた変更だと考えられます。
参考: Python多段階ローダーによって配信されるPureHVNC – FortiGuard Labs
おわりに
ローダーの機能そのものに目新しい点はありませんでしたが、API HashingのアルゴリズムにMurmurHash2を用いている点や、TorNetとPureHVNCという2種類のマルウェアを同時にロードする点など、興味深い実装が確認できました。今後の攻撃でも利用される可能性があるため、引き続き警戒が必要だといえるでしょう。
Appendix A: IoCs ↩︎
ファイル
SHA256 | 説明 |
---|---|
943c1d64cda373beab24e3b1fdb715e14ce79b0f04674368e26db781cc68cea6 | ZIPファイル |
5a1b8fe009bfc405bd863f645f5f1112c1cf386b663da1722893ffe45c00ce24 | ローダー (version.dll ) |
2886be48b8af62edd856a2605039a3341f0bb385474992308b775d1abc240f7e | TorNetローダー |
da6b59c1f7ed3e1986f9285a7ed4aff91c00cacd428938b67650a03af68ce7a4 | PureHVNCローダー |
be682003b89b79d761ffebebb307a74a8ed6ca7324ffd4da185943bf2ced4dba | TorNet |
5ef6d6fb0cd5ea08764e50c6b61cf2cfa441b0c1b12f52d74c0a92c28de13aa4 | PureHVNC |
通信先
アドレス | 説明 |
---|---|
139.99.87[.]31:7702 | TorNet通信先サーバー |
139.99.85[.]213:{56001, 56002, 56003} | PureHVNC C2サーバー |
Appendix B: API Hashing解析用スクリプト↩︎
UnicornおよびIDAPython APIを利用して、ローダー内のハッシュ値から呼び出されているAPI関数を特定するスクリプトを以下に示します。類似検体の解析などにお役立てください。
from unicorn import *
from unicorn.x86_const import *
import idaapi
import idautils
import ida_funcs
import pefile
import struct
API_RESOLVER_FN = 0x10067C3A
HASHCODE_START = 0x1007030D
HASHCODE_END = 0x1007051E
ENUM_NAME = 'APIHASH'
def calculate_hash(string, seed=0xB801FCDA):
string = string.lower()
code = ida_bytes.get_bytes(HASHCODE_START, HASHCODE_END - HASHCODE_START)
uc = Uc(UC_ARCH_X86, UC_MODE_32)
stack_base = 0x00100000
stack_size = 0x00100000
EBP = stack_base + (stack_size // 2)
uc.mem_map(stack_base, stack_size)
uc.mem_write(stack_base, b"\x00" * stack_size)
uc.reg_write(UC_X86_REG_EDI, len(string))
uc.reg_write(UC_X86_REG_ESI, len(string))
len_bytes = struct.pack('<I', len(string))
uc.mem_write(EBP + 0xC, len_bytes)
seed_bytes = struct.pack('<I', seed)
uc.mem_write(EBP + 0x10, seed_bytes)
uc.mem_write(EBP - 0x48, string)
uc.reg_write(UC_X86_REG_EBP, EBP)
code_base = 0x00200000
code_size = 0x00100000
uc.mem_map(code_base, code_size, UC_PROT_ALL)
uc.mem_write(code_base, b"\x00" * code_size)
uc.mem_write(code_base, code)
code_end = code_base + len(code)
uc.emu_start(code_base, code_end, timeout=0, count=0)
return uc.reg_read(UC_X86_REG_EAX)
def main():
# Calculate hash value of API functions
api_dict = {}
for dll in ['kernel32.dll', 'ntdll.dll']:
try:
pe = pefile.PE('C:\\Windows\\System32\\' + dll)
api_list = [e.name for e in pe.DIRECTORY_ENTRY_EXPORT.symbols]
api_list = [api for api in api_list if api != None]
except (AttributeError, pefile.PEFormatError):
continue
for api in api_list:
api_dict[calculate_hash(api)] = api
# Create enum type for API hash
enum = idc.get_enum(ENUM_NAME)
if enum == idc.BADADDR:
enum = idc.add_enum(idaapi.BADNODE, ENUM_NAME, idaapi.hex_flag())
# Collect used API hash values
for xref in idautils.XrefsTo(API_RESOLVER_FN):
ea = xref.frm
push_cnt = 0
while ea != idc.BADADDR:
if idc.print_insn_mnem(ea) == 'push':
push_cnt += 1
if push_cnt == 3:
hash_value = idc.get_operand_value(ea, 0) & 0xFFFFFFFF
break
ea = idc.prev_head(ea)
# Print API hashing resolution result
if hash_value not in api_dict:
print(f'[-] Failed: {hex(hash_value)} used at {hex(xref.frm)}')
else:
print(f'[+] Resolved: {hex(hash_value)} ---> {api_dict[hash_value]} used at {hex(xref.frm)}')
# Add enum member and apply it
enum_value = idc.get_enum_member(enum, hash_value, 0, 0)
if enum_value == -1:
idc.add_enum_member(enum, ENUM_NAME + "_" + api_dict[hash_value].decode(), hash_value)
idc.op_enum(ea, 0, enum, 0)
if __name__ == '__main__':
main()
Code language: Python (python)
- WindowsにおけるDLLファイルの検索 (読み込み)順序を悪用することで、アプリケーションに正規のDLLファイルだと誤認させて悪性のDLLファイルを読み込ませる攻撃手法。 ↩︎
- 感染端末が再起動しても攻撃者のアクセスが維持されるように、端末が起動した際にマルウェアを自動的に実行するための設定。 ↩︎
- プロセスにロードされているモジュール、複数のWMIクエリの結果、画面のサイズ、ユーザー名などをもとに判断する。 ↩︎
- 本体はTorNetと同様にダウンローダーとしての機能しか有していないが、これまで観測されている攻撃キャンペーンの情報から、モジュール型のRATである可能性が高い。 ↩︎
- 初期通信に限らず、すべてのC2通信はSSLで暗号化されている。 ↩︎