# Deobfuscating Javascript Malware

> 通过逆向编码层、eval 调用链、字符串操作和控制流混淆， 对 Web 攻击、钓鱼页面和投放器脚本中使用的恶意 JavaScript 代码进行去混淆， 以还原原始恶意逻辑。适用于 JavaScript 恶意软件分析、脚本去混淆、 Web 嗅探器分析或混淆投放器调查等请求。

- Skill: `killvxk/deobfuscating-javascript-malware` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add killvxk/deobfuscating-javascript-malware`
- Raw SKILL.md: https://api.skillmd.com/api/skills/killvxk/deobfuscating-javascript-malware/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: Apache-2.0
- Author: killvxk (https://skillmd.com/u/killvxk)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/killvxk/deobfuscating-javascript-malware

---


# 对 JavaScript 恶意软件进行去混淆

## 适用场景

- 调查带有混淆 JavaScript 的钓鱼页面，该脚本执行凭据窃取或重定向操作
- 分析注入到电商网站的 Web 嗅探器（Magecart 风格）
- 对下载并执行第二阶段恶意软件的 JavaScript 投放器进行去混淆
- 检查包含带嵌入式混淆脚本的 HTML 文件的恶意电子邮件附件
- 分析使用大量 JavaScript 混淆来隐藏漏洞投递的浏览器漏洞利用工具包

**不适用于**仅被压缩的生产代码混淆 JavaScript；对此类代码使用标准美化工具即可。

## 前置条件

- Node.js 18+，用于在受控环境中执行和调试 JavaScript
- Python 3.8+，安装 `jsbeautifier` 库用于代码格式化
- 浏览器开发者工具（Chrome DevTools），用于在隔离浏览器中进行受控执行
- CyberChef（https://gchq.github.io/CyberChef/），用于编码/解码操作
- de4js 或 JStillery，用于自动化 JavaScript 去混淆
- 隔离的分析虚拟机（无法访问生产系统或敏感数据）

## 工作流程

### 步骤 1：安全提取并检查混淆脚本

在不执行的情况下隔离恶意 JavaScript：

```bash
# 从 HTML 文件中提取 JavaScript
python3 << 'PYEOF'
from html.parser import HTMLParser

class ScriptExtractor(HTMLParser):
    def __init__(self):
        super().__init__()
        self.in_script = False
        self.scripts = []
        self.current = ""

    def handle_starttag(self, tag, attrs):
        if tag == "script":
            self.in_script = True
            self.current = ""

    def handle_endtag(self, tag):
        if tag == "script":
            self.in_script = False
            if self.current.strip():
                self.scripts.append(self.current)

    def handle_data(self, data):
        if self.in_script:
            self.current += data

with open("malicious_page.html") as f:
    parser = ScriptExtractor()
    parser.feed(f.read())

for i, script in enumerate(parser.scripts):
    with open(f"script_{i}.js", "w") as f:
        f.write(script)
    print(f"已提取 script_{i}.js（{len(script)} 字节）")
PYEOF

# 美化提取的 JavaScript
npx js-beautify script_0.js -o script_0_pretty.js
```

### 步骤 2：识别混淆技术

对所使用的混淆方法进行分类：

```
常见 JavaScript 混淆技术：
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
字符串编码：
  - 十六进制编码：         "\x68\x65\x6c\x6c\x6f" -> "hello"
  - Unicode 转义：         "\u0068\u0065\u006c\u006c\u006f" -> "hello"
  - Base64：               atob("aGVsbG8=") -> "hello"
  - charCodeAt/fromCharCode：String.fromCharCode(104,101,108,108,111)
  - 数组查找：             var _0x1234 = ["hello","world"]; _0x1234[0]

Eval 调用链：
  - eval(atob("..."))
  - eval(unescape("..."))
  - new Function("return " + decoded)()
  - document.write("<script>" + decoded + "</script>")
  - setTimeout(decoded, 0)

控制流：
  - 带打乱顺序的 switch-case 分发器
  - 不透明谓词（始终为真/假的条件）
  - 死代码插入
  - 变量名混淆（_0x4a3b, _0xab12）

反分析：
  - 调试器陷阱：setInterval(function(){debugger;}, 100)
  - Console 检测：覆盖 console.log
  - 时序检查：performance.now() 差值
  - DevTools 检测：window.outerWidth - window.innerWidth > 100
```

### 步骤 3：移除反分析保护

消除反调试和反分析陷阱：

```javascript
// 分析前移除调试器陷阱
// 在混淆脚本中替换：

// 之前：
setInterval(function() { debugger; }, 100);

// 之后（已消除）：
setInterval(function() { /* debugger 已移除 */ }, 100);

// 消除 DevTools 检测
// 之前：
if (window.outerWidth - window.innerWidth > 160) { window.location = "about:blank"; }

// 之后：
if (false) { window.location = "about:blank"; }

// 消除时序检查
// 覆盖 performance.now 以返回一致的值
const originalNow = performance.now;
performance.now = function() { return 0; };
```

### 步骤 4：解码字符串混淆层

逐层解码编码字符串：

```python
# 用于解码常见 JS 混淆模式的 Python 脚本
import re
import base64
import urllib.parse

def decode_hex_strings(code):
    """将 \\xNN 序列替换为 ASCII 字符"""
    def hex_replace(match):
        hex_str = match.group(0)
        try:
            return bytes.fromhex(hex_str.replace("\\x", "")).decode("ascii")
        except:
            return hex_str
    return re.sub(r'(?:\\x[0-9a-fA-F]{2})+', hex_replace, code)

def decode_unicode_escapes(code):
    """将 \\uNNNN 序列替换为字符"""
    def unicode_replace(match):
        return chr(int(match.group(1), 16))
    return re.sub(r'\\u([0-9a-fA-F]{4})', unicode_replace, code)

def decode_charcode_arrays(code):
    """解析 String.fromCharCode 调用"""
    def charcode_replace(match):
        codes = [int(c.strip()) for c in match.group(1).split(",")]
        return '"' + "".join(chr(c) for c in codes) + '"'
    return re.sub(r'String\.fromCharCode\(([0-9,\s]+)\)', charcode_replace, code)

def decode_base64_strings(code):
    """解析带静态字符串的 atob() 调用"""
    def atob_replace(match):
        try:
            decoded = base64.b64decode(match.group(1)).decode("utf-8")
            return f'"{decoded}"'
        except:
            return match.group(0)
    return re.sub(r'atob\(["']([A-Za-z0-9+/=]+)["']\)', atob_replace, code)

# 应用所有解码器
with open("script_0.js") as f:
    code = f.read()

code = decode_hex_strings(code)
code = decode_unicode_escapes(code)
code = decode_charcode_arrays(code)
code = decode_base64_strings(code)

with open("script_0_decoded.js", "w") as f:
    f.write(code)
print("已解码字符串写入 script_0_decoded.js")
```

### 步骤 5：安全解析 Eval 调用链

在不执行的情况下展开 eval/Function 构造器调用链：

```javascript
// Node.js 脚本，用于安全解析 eval 调用链
// 在隔离环境中运行：node --experimental-vm-modules deobfuscate.js

const vm = require('vm');

// 创建带日志记录的沙箱上下文
const sandbox = {
    eval: function(code) {
        console.log("=== EVAL 已拦截 ===");
        console.log(code.substring(0, 500));
        console.log("===================");
        return code; // 返回代码而不执行
    },
    document: {
        write: function(html) {
            console.log("=== DOCUMENT.WRITE 已拦截 ===");
            console.log(html.substring(0, 500));
        },
        getElementById: function() { return { innerHTML: "" }; }
    },
    window: { location: { href: "" } },
    atob: function(s) { return Buffer.from(s, 'base64').toString(); },
    unescape: unescape,
    setTimeout: function(fn) { if (typeof fn === 'string') console.log("TIMEOUT CODE:", fn); },
    console: console,
    String: String,
    Array: Array,
    parseInt: parseInt,
    RegExp: RegExp,
};

const context = vm.createContext(sandbox);

// 在沙箱中加载并执行混淆脚本
const fs = require('fs');
const code = fs.readFileSync('script_0.js', 'utf8');

try {
    vm.runInContext(code, context, { timeout: 5000 });
} catch(e) {
    console.log("执行错误（预期）：", e.message);
}
```

### 步骤 6：分析去混淆后的载荷

检查还原出的恶意逻辑：

```
去混淆后的恶意软件类别与 IoC 提取：
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
凭据窃取器：
  - 表单 action URL（数据泄露端点）
  - XMLHttpRequest/fetch 目标地址
  - 目标输入字段名称（username, password, cc_number）

Web 嗅探器（Magecart）：
  - 支付表单覆盖注入
  - 卡数据泄露 URL
  - 键盘记录器事件监听器（onkeypress, oninput）

重定向脚本：
  - location.href 赋值中的目标 URL
  - 基于 user-agent 或 referrer 的条件重定向
  - 伪装逻辑（向爬虫展示良性内容）

漏洞利用工具包落地页：
  - 浏览器/插件版本检查
  - 漏洞利用载荷 URL
  - 编码为数组或编码字符串的 shellcode
```

## 核心概念

| 术语 | 定义 |
|------|------|
| **Eval 调用链（Eval Chain）** | 嵌套的 eval()、Function() 或 document.write() 调用层，每层解码一层混淆后传递给下一层 |
| **字符串数组旋转（String Array Rotation）** | 将所有字符串存储在打乱顺序的数组中并通过计算索引访问的混淆技术，用于隐藏字符串字面量 |
| **死代码插入（Dead Code Insertion）** | 添加永远不会执行的非功能性代码块，以增加分析复杂性并迷惑模式匹配 |
| **不透明谓词（Opaque Predicate）** | 结果预先确定但难以静态判断的条件表达式；用于掩盖控制流 |
| **反调试（Anti-Debugging）** | 用于检测并阻止浏览器 DevTools 或调试器使用的 JavaScript 技术，包括 debugger 语句和时序检查 |
| **Web 嗅探器（Web Skimmer）** | 注入到电商网站以从结账表单中窃取支付卡数据的恶意 JavaScript（Magecart 攻击） |

## 工具与系统

- **CyberChef**：GCHQ 的基于 Web 的编码/解码转换工具，用于解开多层混淆
- **de4js**：支持常见混淆工具（obfuscator.io、JScrambler）的在线 JavaScript 去混淆器
- **Node.js VM 模块**：用于安全执行混淆代码的沙箱 JavaScript 执行环境，具有 API 拦截功能
- **Chrome DevTools**：浏览器开发者工具，支持通过断点和控制台访问逐步调试 JavaScript 执行
- **JSDetox**：提供执行模拟和去混淆功能的 JavaScript 恶意软件分析工具

## 常见场景

### 场景：对 Magecart Web 嗅探器进行去混淆

**场景背景**：被攻陷的电商网站在其结账页面注入了混淆的 JavaScript。需要对脚本进行去混淆，以识别数据泄露端点并确定哪些客户数据被窃取。

**方法**：
1. 从页面源代码中提取注入的脚本（通常附加到合法 JS 文件末尾或从外部域加载）
2. 美化代码并识别混淆技术（通常是字符串数组 + 旋转 + 十六进制编码）
3. 使用 Python 解码器脚本解码字符串编码层（十六进制 -> Unicode -> Base64）
4. 通过评估数组定义和旋转函数来解析字符串数组
5. 识别表单定位逻辑（使用 querySelector 查找支付表单字段）
6. 从 XMLHttpRequest 或 fetch 调用中提取泄露 URL
7. 记录被窃取的数据字段和泄露端点，用于事件响应

**常见陷阱**：
- 在已连接网络的系统上执行混淆脚本（脚本可能在分析期间回连）
- 在使用浏览器 DevTools 前未移除反调试陷阱（无限调试器循环）
- 遗漏从外部 URL 动态加载的额外混淆层
- 忽视 Base64 编码的内联图像或数据 URI，它们可能包含额外的脚本

## 输出格式

```
JavaScript 恶意软件去混淆报告
=========================================
来源：           checkout.js（注入到 example-shop.com）
混淆方式：       obfuscator.io（字符串数组 + 旋转 + 十六进制编码）
已移除层数：     3

已识别的混淆技术
[1] 包含 247 个条目的字符串数组，旋转量 0x1a3
[2] 十六进制编码的字符串引用（\x68\x65\x6c\x6c\x6f）
[3] Base64 包装的 eval 调用链（2 层）
[4] 反调试：setInterval debugger 陷阱

去混淆后的功能
类型：           Magecart 支付卡嗅探器
目标表单：       input[name*="card"], input[name*="cc_"]
捕获数据：       卡号、有效期、CVV、持卡人姓名
泄露方式：       通过 XMLHttpRequest 进行 POST
泄露 URL：       hxxps://analytics-cdn[.]com/collect
泄露格式：       JSON { "cn": card_number, "exp": expiry, "cv": cvv }
触发方式：       结账页面的表单提交事件

提取的 IoC
域名：           analytics-cdn[.]com
IP：             185.220.101[.]42
URL：            hxxps://analytics-cdn[.]com/collect
                 hxxps://analytics-cdn[.]com/gate.js
```

