Malware

深入銀狐 Part 3:從 DLL 側載到 ValleyRAT,完整拆解記憶體載入與反鑑識鏈

銀狐載入鏈的最終使用者態環節:合法簽章的 NtHandleCallback.exe 側載惡意 log.dll,以三條並行執行緒同時 RC4 解密並在記憶體執行 ValleyRAT 核心、部署兩支核心驅動做反鑑識、維持 Defender 排除;ValleyRAT 把 C2 設定與 plugin 藏進 HKCU\Console Registry,只刪檔或封 C2 都清不乾淨。

Dinlon · 發布

地區 China · Taiwan · Japan · Korea · India · APAC 產業 政府 · 公共基礎設施 · 醫療 · 教育與研發 · 金融 · 電子商務 · 遊戲 · 資安 平台 Windows 歸因信心 high
actor Silver Fox(銀狐) malware ValleyRAT malware Gh0st RAT technique DLL side-loading

Key findings

  • NtHandleCallback.exe 是合法簽章的應用程式,作為白加黑側載宿主;整條載入鏈在磁碟上看起來像是合法程式在執行合法 DLL。
  • 惡意 log.dll 作為載入器建立三條並行執行緒:T1 解密並在記憶體執行 ValleyRAT、T2 部署兩支反鑑識核心驅動、T3 維持 Defender 排除路徑。
  • Server.log 是 RC4 加密的 ValleyRAT 核心,以金鑰 ??Bid@locale@std 解出「上线模块.dll」後以記憶體映射執行,本體從不落地。
  • ValleyRAT 用兩個 Registry 值作為持久化容器:HKCU\Console\IpDate 可動態覆蓋 C2 設定,HKCU\Console\0 下的 32-hex 值儲存 plugin 二進位;只刪檔或封 C2 都清不乾淨。
  • 邏輯層仍沿用 Gh0st RAT 的 first-byte token 加 CManager 派發設計,但網路層已拿掉 Gh0st magic 與 zlib,改用 14-byte 自訂 header 並加 UDP/ARQ 備援,規避傳統 Gh0st 偵測規則。

銀狐 Part 3 示意圖(AI 生成)

前言

在前一篇中,我們看到銀狐透過 men.exe 完成環境鋪設:落地元件、BYOVD 殺防毒、修改 DACL 防刪除,以及安排登入觸發。本篇要解說的是舞台搭好後,真正的 ValleyRAT 如何被載入、上線、維持與保護。本次分析所用樣本:

本次分析所用樣本資訊

本文僅供防禦研究、事件應變、威脅狩獵與教育用途。文中提及之樣本與工具可能具風險,請僅在隔離、可還原且不連接生產網路之環境中測試。本文不鼓勵、協助或默許任何未經授權之行為;文中所提及之第三方平台與產品名稱僅作技術描述之用。

NtHandleCallback.exe:DLL 側載宿主

NtHandleCallback.exe 本身不是惡意程式。它是一支 PE32 應用,2020-03-11 編譯,由 Hangzhou Shunwang Technology Co.,Ltd(杭州順網科技)以 DigiCert 簽章。順網是中國一家做網咖管理軟體與遊戲雲端服務的公司,這張憑證在 2023 年後大量流入中文犯罪生態,被許多惡意程式拿來當簽章。不過這次攻擊者沒有破解簽章,只是把這支合法程式直接複製一份過來,當作側載 log.dll 的宿主。

這支 EXE 簽章合法、有 GUI 資源段(.rsrc 約 43 KB)、啟用了 Control Flow Guard,就是支商業應用,沒什麼好查的。它真正成為攻擊鏈的線索在 Import Table 裡:

NtHandleCallback.exe 的 Import Table,指向 log.dll!GenericLogImpl

注意它 IAT 中的第三方函式 log.dll!GenericLogImpl。Windows 的 DLL 搜尋順序會把應用程式目錄擺在系統目錄之前,所以只要 log.dll 跟宿主放在同一資料夾,啟動時 PE loader 就會把它映射進行程、解析匯入並呼叫 GenericLogImpl。從這裡開始,後續流程就是 log.dll 的邏輯,與這支合法 EXE 已經沒什麼關係了。

log.dll:三執行緒編排器

log.dll 是一支 PE32,共 5 個 section,ImageBase 0x10000000,大小約 176 KB。它的匯出表只有一個符號:

log.dll 匯出表,僅 GenericLogImpl 一個符號

惡意行為都集中在 GenericLogImpl。這個函式的反組譯結果容易理解:

GenericLogImpl 反組譯結果

三條執行緒並行,主呼叫端在三個都 join 之前不會返回。這種設計讓 NtHandleCallback.exe 看起來只是卡在一個日誌輸出函式裡,而背景早已完成三件事:

log.dll 建立三條並行執行緒 T1/T2/T3

以下分別拆解這三條執行緒,不過大部分篇幅會集中在 T1 的 ValleyRAT 分析。三條執行緒的進入點與職責:

執行緒進入點職責
T1StartAddress @ 0x10003EE0 → sub_10003BB0解密並執行 ValleyRAT 本體
T2sub_10003DA0部署惡意核心驅動用以反鑑識
T3sub_10003EF0維持 Defender 排除路徑

T1:Server.log 解密與 ValleyRAT 上線

這是本篇的主戲。我們會先拆解 T1 怎麼把 Server.log 解成 ValleyRAT 的 PE,接著深入解密後的 PE 本體,包括 config 的藏匿手法、C2 啟動路徑,以及與 Gh0st RAT 的關係。

RC4 解密與記憶體載入

sub_10003BB0(T1 函數)的目的是將被加密的 ValleyRAT 核心檔案 Server.log 解密,並對該記憶體位置執行:

strcpy(local_v3, "??Bid@locale@std");        // RC4 key, 16 bytes
fp   = fopen("Server.log", "rb");            // 同目錄的密文檔
size = ftell(fp);
buf  = malloc(size);
fread(buf, 1, size, fp);

rc4_decrypt(buf, size, local_v3, 16);        // FUN_10004CD0 — 標準 RC4
pe    = map_pe(buf);                          // FUN_100053D0 — 自建 PE loader
entry = find_export(pe, "NtHandleCallback");  // FUN_10005200
if (entry) entry();                           // 直接呼叫匯出

整條鏈沒有檔案落地,解密完會直接映射到記憶體、解析匯出並跳過去執行。ValleyRAT 的實體從不以檔案形式存在於磁碟。雖然真正執行時檔案從不落地,逆向時仍可直接用密碼把密文解出,丟進 CyberChef 以 ??Bid@locale@std 用 RC4 還原分析(這個密碼在同系列下用了快五個月,反倒是 C2 換得更頻繁):

以 CyberChef 用 RC4 金鑰還原 Server.log 為 PE

前 4 bytes 為 4D 5A 90 00MZ...),基本可確定是 PE 結構;丟到 PEBear 看到匯出中包含 NtHandleCallback,接下來存為 valleyrat.dll 分析。

ValleyRAT 上線模組

Server.log 以 RC4 解密後,得到一支 PE32 DLL,內部名稱寫著「上线模块.dll」,匯出函式中也包含前面 log.dll 會尋找並呼叫的 NtHandleCallback。這代表 Server.log 內部藏著 ValleyRAT 的上線模組,前面的 T1 流程只是把它從密文中解出、映射到記憶體,然後轉交執行。

解密後 PE 內部名稱為「上线模块.dll」

這裡的「上線模組」可理解成 ValleyRAT 的控制入口。它會先讀取通訊設定、連上 C2,接著依 C2 指令載入後續 plugin。因為真正啟用的能力由 C2 決定,同一套 ValleyRAT 在不同事件中可能呈現不同樣貌,有些事件只看到基礎連線,有些則進一步出現截圖、鍵盤側錄、檔案操作、遠端 shell 或其他 plugin 行為。

ValleyRAT/Winos 4.0 系列的核心仍能看到 Gh0st RAT 的影子。邏輯層保留了以 CManager 為基礎的類別架構,以及依 command token 分派功能的設計;網路層則已明顯改造,原本 Gh0st 常見的 magic 與 zlib 壓縮特徵被拿掉,改用自訂 header、TCP 通道,以及額外的 UDP/ARQ 備援設計。這讓它保留 Gh0st 的操作模型,同時避開一部分針對傳統 Gh0st 協定的偵測規則。

上线模块.dll 與核心模組的抽象流程

解密後的 PE 基本資訊:

項目內容
TypePE32 DLL(i386, 32-bit)
Size111104 (108 Kb)
MD5016d12cd1f6c68db98fcdde6075e9b71
SHA2565decc64bc4ed4d6ffd3f59be2e839fe5cc70eaa3f07191d77ac241d028e1c6f0

透過 RTTI CLASS 可以看到一些有趣的東西:

.?AVCManager@@                        ← CManager(基礎管理類)
.?AVCKernelManager@@                  ← CKernelManager(核心管理)
.?AVISocketBase@@                     ← ISocketBase(socket 抽象基底)
.?AVCTcpSocket@@                      ← CTcpSocket
.?AVCUdpSocket@@                      ← CUdpSocket
.?AV?$CArqSessionT@VCUdpSocket@@V1@@@ ← CArqSession<CUdpSocket>(ARQ over UDP)
.?AVCBuffer@@                         ← CBuffer
.?AVCAtlException@ATL@@               ← ATL 例外

這些名稱大致分離了三層:CManagerCKernelManager 是控制邏輯與命令分派核心;CTcpSocketCUdpSocketISocketBase 是網路抽象層;CArqSessionT<CUdpSocket> 則暗示它在 UDP 上額外實作可靠傳輸機制。這個 UDP/ARQ 類別在原生 Gh0st RAT 中並不存在,較符合 Winos 4.0 系列在網路層改造後的特徵。

上線模組 PE 結構與匯出,匯出含 NtHandleCallback

從偵測角度來看,單純針對傳統 Gh0st magic 或固定 TCP 行為寫規則,可能漏掉這類已改造網路層的變體。更值得觀察的特徵包括類別架構、14-byte header、自訂握手、Registry plugin 容器,以及 tracerpt.exe 注入後的外連行為。

配置參數讀取與儲存

進入執行後,ValleyRAT 會先初始化通訊設定。樣本內部硬編碼了一串 UTF-16 字串,但整串被反轉;初始化時它會把字串翻回來,再解析成以 | 分隔、以 key:value 表示的設定欄位。這種混淆只是把字元順序反轉,分析難度不高,但自 2025 年至 2026 年 4 月,銀狐使用的所有 ValleyRAT 都以這種方式儲存 C2 資訊,代表這種方式雖易分析,但透過快速的 C2 變換與多階段投放鏈,不足以影響其投放行為。本次樣本解出的設定:

|p1:ydbao8[.]cyou|o1:9000|t1:1|p2:|o2:|t2:1|p3:|o3:|t3:1|dd:1|cl:1
|fz:LINE|bb:1.0|bz:2025. 8.31|jp:1|bh:0|ll:0|dl:1|sh:1|kl:1|bd:0|

C2 config 各欄位與攻擊鏈行為的對應

最值得注意的是這組設定還能被 Registry 覆蓋。初始化時 ValleyRAT 會讀取 HKCU\Console\IpDate;若該值存在且長度符合條件,樣本就會用其中內容取代硬編碼設定。C2 因此能在第一次成功連線後下發新設定寫入該值,下一次 RAT 啟動即改用 Registry 裡的新目標。

這點對事件應變很重要:封鎖該域名只能處理樣本內建的當下 C2,如果 HKCU\Console\IpDate 已寫入更新後的基礎設施,RAT 重啟後仍可能恢復連線。C2 IOC 是時間點快照,Registry 中的動態設定則反映端點上殘留的攻擊狀態。根據經驗,攻擊者會在首次成功連線後立即更新 C2 位置以建立分析斷點。

HKCU\Console\IpDate 動態覆蓋 C2 設定

Registry plugin 容器:後段模組

除了 C2 設定外,ValleyRAT 還使用 HKCU\Console 作為 plugin 的本機儲存空間,這是本次樣本最關鍵的設計之一。我們觀察到另一個 Registry value:

HKCU\Console\0\d33f351a4aeea5e608853d1a56661059

注意路徑是 Console\0 這個子鍵,不是 Console 本身;0 這個一字元子鍵名稱本身可能也算反取證設計,編輯器一掃而過容易誤判為占位。value name 是 32 字元 hex,看起來像 MD5 或自動產生的識別值,容易讓人工檢查誤以為只是普通雜湊,但實際上它是 plugin 二進位的儲存容器。

首次部署時,C2 會推送一段 plugin 設定與 payload,ValleyRAT 收到後把「config + PE」一起寫入上述 value;之後即使 RAT 重啟,也不需重新下載即可重新載入執行。這個設計帶來的效果不止於「存在 Registry 而非檔案系統」:

  • plugin 不以檔案形式出現在磁碟上,對只依賴檔案掃描或新建執行檔監控的防線,可見度會下降。
  • 攻擊者降低了對 C2 即時可用性的依賴,只要 Registry 裡仍保存 plugin,RAT 重啟後就有機會從本機重放後段能力。
  • plugin 可能帶有受害主機綁定邏輯,樣本讀回 plugin 後會做 hostname 或 UID 相關比對,不符則向 C2 回報異常,暗示 plugin 具 per-victim 客製化能力。

事件回應時,若只刪掉 NtHandleCallback.exelog.dll 或封鎖目前 C2,仍可能留下這個 Registry plugin 容器。因此 HKCU\Console 區域是清除與蒐證時必須優先檢查的位置。

C2 連線:多次重試與備援機制

ValleyRAT 的參數預設有多個槽位,支援 TCP 與 KCP(ARQ over UDP)連線。本次樣本主要 C2 是 ydbao8[.]cyou9000/tcp。主要 C2 失敗時樣本會切換到備援組,失敗次數持續累積還會升級嘗試第三組,以對抗單一網域封鎖、sinkhole、分析環境阻斷或基礎設施短暫不可用。

C2 連線多次重試與備援機制

連線成功後,樣本送出非常短的握手資料,接著進入接收迴圈,後續封包交給 CKernelManager 處理。從這一刻起,CKernelManager 成為受害主機上的控制核心,依 C2 指令決定要忽略 heartbeat、安裝 plugin、讀回 Registry plugin,或更新本機設定。技術上這裡仍看得到 Gh0st RAT 的命令分派精神,但外層協定已被重寫,樣本使用自訂 14-byte header,取代原始 Gh0st 常見的 magic 與壓縮格式。

Plugin 部署

可以把 CKernelManager 理解成上線模組的最小派發器:它不直接提供完整 RAT 功能,而是依 C2 封包決定三件事,忽略心跳、首次安裝 plugin,或從 Registry 喚醒既有 plugin。真正的後控能力不在這層,而是在後續被注入到 tracerpt.exe 的主控模組中。主派發器(0x100054C0)依封包第一個 byte 與封包總長度這兩個簡單訊號走三條路徑:

路徑一:心跳,當第一個 byte 為 0xC9 時視為心跳,直接 return。這個 token 是 ValleyRAT 與 Gh0st 在派發層上的明顯差異:Gh0st 心跳 token 是 0x01,這裡刻意改成 0xC9,以規避「first-byte == 0x01」為基礎的 Gh0st 偵測規則。

路徑二:首次部署,當封包大小不等於 101 時走首次部署。封包前段(pkt+10xA44 = 2628 bytes)是 plugin config,後段是 payload PE。先把 payload PE 配置到記憶體並 memcpy 進去,再把同一份「config + PE」串接後以 RegSetValueExW 寫入 HKCU\Console\0\d33f351a4aeea5e608853d1a56661059,寫完立刻 _beginthreadex(sub_100052B0) 啟動注入器執行緒,後續的 needle 補位、寫入 HKLM\SOFTWARE\IpDates_infotracerpt.exe Process Hollowing 都由這條執行緒接手。

首次部署與注入流程

路徑三:喚醒重放,當封包大小剛好等於 101 時走此路徑(RAT 重啟或 C2 主動觸發甦醒)。它會開啟 HKCU\Console\0,把先前保存的 config + PE 讀回還原到記憶體,接著做主機指紋驗證:比對成功就進入注入流程把 plugin 灌回 tracerpt.exe;比對失敗(表示這份 plugin 是從別台機器搬來的)則把回應封包前綴 0x05 加上完整 config 送回 C2,讓伺服端知道這台機器拒收。

三條路徑共用同一個 plugin 暫存區與同一個注入器入口(sub_100052B0);CKernelManager 自身完全不接 keylog/shell/螢幕擷取一類功能指令,那些都是 plugin 跑起來後在 tracerpt.exe 行程內由 plugin 自己的派發器處理。

Plugin 注入與啟動:自我修補與 tracerpt.exe Hollowing

在真正執行 plugin 前,ValleyRAT 會把目前的 config 寫回 plugin。樣本會在 plugin 記憶體中搜尋一段固定標記 ihziepulgned,反轉後是 dengluplugin(「登錄 plugin」的拼音)。找到標記後,ValleyRAT 會把目前設定區塊覆寫到 plugin 中,這是一種 plugin self-patch 機制:plugin 本體不用硬編碼完整 C2 與受害主機設定,載入前由上線模組注入當前 config。

接著樣本依設定中的 kl 旗標(Server 端以「傀儡進程」作為可勾選項目)選擇執行模式。本次樣本 kl 為 1 走傀儡模式:建立暫停狀態的 tracerpt.exe,將 RAT body 寫入該程序,再修改主執行緒 context,使 payload 從 tracerpt.exe 內部開始執行,典型的 process hollowing。tracerpt.exe 是 Windows 內建的 ETW trace 報告工具、具 Microsoft 簽章,攻擊者選它作為 hollow target,是為了讓端點上出現一個有簽章、位於系統路徑、名稱合理的程序來承接 RAT 行為。

樣本中還有 watchdog 設計:定期檢查被 hollow 的 tracerpt.exe 是否仍存活,如果程序被 EDR 或人工終止,樣本會重新建立並注入新的 tracerpt.exe。單純殺掉這個 process 不能終止感染,反而會觸發它重建通訊程序。若 kl 為 0,樣本則走就地執行模式,在目前 NtHandleCallback 行程內執行 plugin;這種模式隱蔽性較低但行為更少,長期觀察下攻擊者有時會選就地模式,但數量明顯偏少。

Plugin 行為

取得並分析攻擊者投放的 plugin 後,發現一個命名為 plugin DDDD 的插件被載入。這個 plugin 即為 ValleyRAT 功能核心模組,在 Server 端通常被命名為「主控模块」,提供 27 個命令 token,包括 keylog、screenshot、shell、檔案、關機重啟、EventLog 抹除等功能。

我們在感染主機的記憶體與 Registry 各取得一份 plugin 副本:記憶體版本由 HollowsHunter 從受感染行程的 unbacked 區段挖出,Registry 版本則直接從 HKCU\Console\0 之下含 plugin 二進位的 REG_BINARY 取出。兩份共通的 530,944 bytes 中只有 74 bytes 不同,差異全部集中在 plugin 的 .data 區段,內容是 plugin 跑起來後寫入的 victim runtime 狀態(疑似工作目錄或 session token)。因此可理解為同一支 PE,一個是部署當下的乾淨樣本,另一個是已執行過、攜帶受害主機指紋的活體樣本。

打開這份 plugin 的 RTTI 字串可看到 CLoginManagerCManagerCTcpSocketISocketBase,但 CKernelManager 缺席。推測 plugin 不是上線模組的延伸,而是一支繼承自 CManager、與上線模組平行的另一個 manager 類別。進一步看它的工作迴圈會發現它有自己的 socket 建構(TCP + UDP/ARQ 雙通道)、自己的 200-cycle p1/p2/p3 C2 failover、自己的 Registry 持久化(這次的 key 是 HKCU\Console\Ipcial,與上線模組的 IpDate 並列),甚至連對 C2 的握手與 recv loop 都是自己重新實作的。從架構看,這支 plugin 比較像「主控模塊」,是一支獨立、功能完整的次階段 RAT,而不是傳統上被後續載入的能力 plugin。

CLoginManager 的 vtable 第一個 slot 拉出來,看到的是一張漂亮的 switch table 派發器:

movzx eax, byte ptr [esi]         ; 取封包第一個 byte 作為 token
sub   eax, 0x14                   ; 偏移到 0
cmp   eax, 0xb6                   ; 範圍檢查
ja    default_handler
movzx eax, byte [eax+0x100262a8]  ; 經 index 表轉為 case slot
jmp   dword [eax*4+0x10026238]    ; 透過 jump 表分派

plugin 命令分派 switch table 反組譯

token 接受範圍是 [0x14, 0xCA],jump 表共 28 個 slot(27 個有效命令 + 1 個 default),Gh0st 的命令分派也是這種寫法。對照前一節 CKernelManager 只有 3 條 path 的派發邏輯,plugin 的指令派發器徹底回到 Gh0st 風格的全功能設計。有意思的是:ValleyRAT 在上線模組那層已把 Gh0st 原始的 5-byte magic 與 zlib 拿掉,換上 14-byte 自訂 header 加通訊雙通道(推測為了加備援連線、順便繞過 WAF 針對 Gh0st 撰寫的舊規則);但在真正執行命令這層,派發機制與類別架構仍完整保留,且加入了 BCrypt AES 等現代化模組,所以這條 plugin 算是 Gh0st 架構的高度改裝。

實務上這 27 個命令可歸併成 8 個功能類別,每類底下可能對應 1~3 個 token 變體:

27 個命令歸併為 8 大功能類別

也就是說,這支 plugin 涵蓋了一支現代 RAT 該有的所有基本能力,從偵察、操控、持久化到事後清理都有。攻擊者拿到一台機器後,任何事情都不需再下發新 plugin,本身就夠用。其中我認為較特別的五個命令:

五個較特別的命令

0x22 ClearEventLogs 是事件應變上特別麻煩的一條:它把三個 wide string ApplicationSecuritySystem 依序傳給 OpenEventLogW 然後立刻 ClearEventLogW,一次性把端點上最常被檢視的三個事件來源全部清空。IR 團隊事後看到的會是一個乾淨的 EventLog,沒有登入紀錄、沒有服務啟動紀錄、沒有任何系統錯誤訊息。要對抗這條命令,可用 Sysmon 把關鍵事件即時轉到中央 SIEM,或啟用 Windows Event Forwarding,即使本機 log 被清空,IR 仍能從遠端拷貝看到完整時間軸。

T2:反鑑識核心驅動部署與 PID 保護

這條執行緒的目的是啟用惡意核心驅動,並將 T1 執行的 ValleyRAT 等工具列入保護對象,防止鑑識工具分析。它依序完成:

  • 等待 NtHandleCallback.exe 出現 PID
  • 部署 Cndom6.sys,建立 service,並開啟 \\.\Cndom6 驅動位置(為 IOCTL 註冊)
  • 向 Cndom6 下 IOCTL 註冊 loader 群組的受保護 PID
  • 等待 tracerpt.exe 出現 PID
  • 部署 XiaoH.sys,建立 service
  • 向 Cndom6 下 IOCTL 註冊 tracerpt 群組的受保護 PID
  • 檢查 NVIDIA.exe 是否在跑,若沒有就 WinExec 啟動
  • 向 Cndom6 下 IOCTL 註冊 NVIDIA 群組的受保護 PID

三個被保護的群組對應攻擊鏈上的三個關鍵角色:NtHandleCallback.exe 是本篇的白加黑宿主,tracerpt.exe 是 ValleyRAT 本體注入的傀儡行程,NVIDIA.exe 則是 Part 2 中透過 BYOVD 殺防毒的元件。

從 log.dll 內部解出的兩支驅動

追蹤寫檔函式的呼叫點,可定位到嵌在 log.dll 內部的兩段二進位:

驅動RVAFile Offset大小位元
Cndom6.sys0x230900x2129023,616 bytes (0x5C40)x86-64 (PE32+)
XiaoH.sys0x28CD00x26ED010,808 bytes (0x2A38)x86-64 (PE32+)

兩段開頭都是標準 PE+ 檔案。log.dll 本身是 32-bit,但攜帶的是兩支 64-bit 驅動;這對 user-mode 載入不是問題,服務只要走 CreateServiceStartService,載入就會由系統完成,隨後再把驅動檔案刪除。分析上只需對其 dump 即可取得驅動檔。這些驅動如何透過核心機制完成反鑑識,將於下一章詳述。

Cndom6 IOCTL 介面

log.dll T2 在本次部署實際只用了四個 IOCTL(0x2221800x22218C0x221E600x222190),但完整反向 Cndom6 後可看到它對外暴露六個 IOCTL,外加一條總旗標:

Cndom6 對外的 IOCTL 介面

從呼叫順序可反推 Cndom6 的對外設計邏輯:攻擊者把 PID 保護需求拆成多個獨立 IOCTL,每個對應不同的核心 hook 或不同的 PID 清單。另一個值得注意的反取證設計是,Cndom6 在載入後會透過解除自身映像分頁區段的引用,把自己的 .sys 檔案從磁碟刪掉,使 log.dll T2 部署這支驅動後,在端點上找不到對應的 Cndom6.sys 檔案。

XiaoH IOCTL 介面

XiaoH 與 Cndom6 同樣提供 IOCTL 介面:Cndom6 負責隱藏與保護 PID(讓 process 看不見也殺不掉),XiaoH 負責隱藏網路連線(讓 netstat/EDR 看不到 C2 連線)。兩支拼起來形成完整的 Ring 0 反鑑識鏈。XiaoH 對外暴露四個 IOCTL:

XiaoH 對外的 IOCTL 介面

其中 hide-list 與 whitelist 是互補的兩個容器:

XiaoH 的 hide-list 與 whitelist 容器

「替換成假連線」是 XiaoH 比一般 rootkit 更特別的地方:它把目標連線改寫成結構合法的假連線,把目標 IP 換成 1.2.3.4、port 換成 0x3930(14640)的占位值,讓查詢時變成數量正確但內容沒有 C2 的連線清單。另一個細節是 XiaoH 的 0x221E60 IOCTL code 與 Cndom6 的 0x221E60(保護 tracerpt 群組)相同但對應不同 device namespace,因此 T2 呼叫端必須打開對應的 \\.\XiaoH\\.\Cndom6,IOCTL 才會送到正確的驅動。

T3:Defender 排除路徑維持

T3 的程式碼很短,核心就是一個 PowerShell 迴圈:

while (!is_terminate_signal()) {
    run_powershell(
        "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
        "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command "
        "\"if ((Get-MpPreference).ExclusionPath -notcontains "
        "'C:\\\\Users\\\\Public\\\\Pictures') "
        "{ Add-MpPreference -ExclusionPath 'C:\\\\Users\\\\Public\\\\Pictures' }\""
    );
    Sleep(500);
    if (is_terminate_signal()) break;
    Sleep(1000);
}

目的很簡單:即使有人手動把 Defender 排除清單裡的 C:\Users\Public\Pictures 拿掉,這條迴圈會在 500 到 1000 毫秒內補回去。指令裡的 if ... -notcontains 是冪等防呆,重複執行不會產生多餘的系統事件。

不過有個有趣的狀況:如果 Defender 服務已被 NVIDIA.exe 搭配 NSecKrnl 直接殺掉,Get-MpPreference 會失敗,這條迴圈就變成背景空轉,佔用一條執行緒但實際什麼也沒做。其實 T3 通常沒實際用途,只在 Defender 還活著時才有意義,回想 Part 1 的 Inno Setup 安裝腳本已把整個 C 槽加入排除清單,那為何還要再做一次?推測是這工具在其他投放鏈也被利用,或只是作為雙重保險。

技術統整

NtHandleCallback.exelog.dll、ValleyRAT 上線模組與後段 plugin 行為解析為多個技術,逐項與 ATT&CK 的對照見文末的 MITRE ATT&CK mapping 表。其中 plugin command handler 代表樣本具備的遠控能力,不一定表示所有命令都已在本次事件中被實際觸發。

NtHandleCallback.exe/log.dll/ValleyRAT 的技術統整對照表

結語

回頭看這條鏈,NtHandleCallback.exe 本身沒有太多戲份,更像一個被挑中的合法外殼。真正的執行者是 log.dll!GenericLogImpl:一個很短的函數,同時拉起三條線,T1 解密 Server.log 並喚醒 ValleyRAT、上線後取得 plugin;T2 部署核心驅動保護關鍵 process;T3 反覆補回 Defender exclusion。這也是這條鏈容易被低估的地方:入口看起來普通,後面卻已經把載入、持久化與反鑑識分開處理。

這次最特別的是 HKCU\Console 的相關操作:IpDate 保存 C2 config,Console\0 底下那串 32 字元 hex value 則保存 plugin 本體。即使刪掉 NtHandleCallback.exelog.dll,再封掉硬編碼 C2,只要 Registry 裡的 config 與 plugin 還在,這台機器就不能算乾淨。

解出後段 plugin 後,ValleyRAT 與 Gh0st 的關係也更清楚:外層上線模組已拿掉 Gh0st magic 與 zlib、改成自訂 14-byte header 並加 UDP/ARQ 備援;但真正執行命令的 plugin 裡,仍能看到 first-byte token、CManager、switch table 這些 Gh0st-style 設計。它保留了 Gh0st 的控制習慣,然後把投放、通訊與存活方式重新包裝了一次。

Part 3 先停在 user-mode 這條線。下一篇會把視角往下拉到 T2 部署的 Cndom6.sysXiaoH.sys,看惡意驅動如何在 kernel 裡做 PID 保護、連線隱藏與反鑑識。

樣本與參考

MITRE ATT&CK mapping

TacticTechniqueProcedure
Defense evasionT1574.002NtHandleCallback.exe 為合法簽章宿主,透過 Import Table 側載同目錄的 log.dll!GenericLogImpl
Defense evasionT1553.002以合法簽章的第三方商業程式(杭州順網 DigiCert 簽章)作為側載宿主,降低對初始程序的警覺
Defense evasionT1036.005log.dll、Server.log、tracerpt.exe 等名稱與合法元件靠攏,工作目錄置於 C:\Users\Public\Pictures\WindowsData
Defense evasionT1027上線模組以 RC4 密文存於 Server.log,C2 config 以 UTF-16 反轉字串保存,plugin 以 REG_BINARY 藏於 HKCU\Console\0
Defense evasionT1027.009log.dll 內嵌 Cndom6.sys 與 XiaoH.sys 兩支 64-bit 驅動
Defense evasionT1140T1 以 RC4 金鑰 ??Bid@locale@std 解密 Server.log,還原出「上线模块.dll」
Defense evasionT1620解密後的上線模組不落地,由 log.dll 於記憶體配置、映射 PE、解析匯出並轉交執行
Command and controlT1105連上 C2 後接收後段 plugin 並寫入 Registry;plugin 的 DownloadExec 類命令可再拉取任意 binary 落地執行
Defense evasionT1112以 HKCU\Console\IpDate 覆寫 C2 config,以 HKCU\Console\0\d33f351a4aeea5e608853d1a56661059 保存 plugin 二進位
Defense evasionT1055.012kl 旗標啟用傀儡模式時,建立暫停的 tracerpt.exe,寫入 RAT body 後改主執行緒 context 使其自 tracerpt.exe 內執行
Command and controlT1095上線模組與 plugin 以自訂 14-byte header 的封包格式進行 C2 通訊,含 TCP 與 UDP/ARQ 備援
Command and controlT1571硬編碼主 C2 使用 ydbao8[.]cyou 的 9000/tcp 非標準埠
PersistenceT1543.003T2 部署 Cndom6.sys 與 XiaoH.sys,建立並啟動 driver service 取得 Ring 0 能力
Defense evasionT1014Cndom6.sys 保護與隱藏 process/handle,XiaoH.sys 隱藏網路連線,構成核心層反鑑識鏈
Defense evasionT1562.001T3 反覆以 PowerShell 檢查並維持 Defender 對 C:\Users\Public\Pictures 的排除
ExecutionT1059.001以 powershell.exe 隱藏視窗搭配 Get-MpPreference/Add-MpPreference 操作 Defender 設定
DiscoveryT1007後段 plugin 具服務列舉能力,蒐集受害主機服務狀態
CollectionT1056.001plugin 具鍵盤側錄 command handler,可啟動對應執行緒側錄輸入
CollectionT1113plugin 具螢幕擷取能力,可由 C2 指令觸發
ExecutionT1059.003plugin 具後台命令執行(shell)能力,可由 C2 觸發
Defense evasionT1070.001plugin 的 0x22 ClearEventLogs 依序清空 Application、Security、System 事件記錄
ImpactT1529plugin 具強制重啟、關機與登出能力
Defense evasionT1564將惡意狀態分散到加密檔案、Registry value、被 hollow 的合法系統程序與核心 driver

Indicators of compromise

TypeIndicatorFirst seenLast verifiedConfidenceStatus
Domainydbao8[.]cyouValleyRAT 主 C2(ydbao8[.]cyou 的 9000/tcp)2025-092026-05-14HighACTIVE
SHA2565decc64bc4ed4d6ffd3f59be2e839fe5cc70eaa3f07191d77ac241d028e1c6f0上线模块.dll — Server.log 以 RC4 解密後的 ValleyRAT 上線模組(記憶體載入、不落地)2025-092026-05-14High
MD5016d12cd1f6c68db98fcdde6075e9b71上线模块.dll — 同上,ValleyRAT 上線模組(MD5)2025-092026-05-14High
RegistryHKCU\Console\IpDateC2 設定動態覆蓋點;C2 首次連線後可下發新設定寫入此值,RAT 重啟即改用新目標2025-092026-05-14High
RegistryHKCU\Console\0\d33f351a4aeea5e608853d1a56661059plugin 二進位(config + PE)的 REG_BINARY 儲存容器,清除與蒐證須優先檢查2025-092026-05-14High
RegistryHKCU\Console\Ipcial後段 plugin(主控模塊)自身的持久化鍵2025-092026-05-14High
RegistryHKLM\SOFTWARE\IpDates_info注入器執行緒寫入的標記值2025-092026-05-14Moderate
String??Bid@locale@stdServer.log 的 RC4 解密金鑰(同系列沿用近五個月)2025-092026-05-14High
Stringihziepulgnedplugin self-patch 標記(反轉為 dengluplugin);找到後覆寫當前 config2025-092026-05-14High
PathC:\Users\Public\PicturesT3 反覆補回的 Windows Defender 排除路徑2025-092026-05-14High

OIS-2026-007 · TLP:CLEAR under FIRST TLP 2.0 · 引用本研究請附研究編號與固定網址。