> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Node.js 运行环境
- URL: https://blog.vercanti.com/node-js-yun-xing-huan-jing/
- Published: 2026-08-28T14:34:55.000Z
- Updated: 2026-08-28T14:57:39.000Z
- Description: 本文专注于逆向还原场景下的 Node.js 使用，包括如何在本地运行扣出的 JS 代码、利用内置模块辅助逆向分析，以及补全浏览器环境。 vm 模块允许在隔离的沙箱上下文中执行代码，是运行不受信任 JS 代码的常用方式。 vm.runInNewContext(code, sandbox, options) vm.Script 适合需要多次执行同一段代码的场景（预编译）： vm.createContext(sandbox) 将一个普通对象提升为 vm 上下文（contextify）： 逆向场景推荐使用 vm 而非 eval，原因：扣出的浏览器代码通常会访问
- Author: yellowdog
- Tags: js逆向, JS基础

> 官方文档：<https://nodejs.org/en/docs/>  
> 适用版本：Node.js 20 LTS+（2026-05-08 核实）

本文专注于逆向还原场景下的 Node.js 使用，包括如何在本地运行扣出的 JS 代码、利用内置模块辅助逆向分析，以及补全浏览器环境。

---

## 运行扣出的 JS 代码

### 基本执行

```bash
# 直接运行脚本文件
node script.js

# 执行单行代码
node -e "console.log(require('crypto').createHash('md5').update('hello').digest('hex'))"

# 交互式 REPL
node

```

### vm 模块

`vm` 模块允许在隔离的沙箱上下文中执行代码，是运行不受信任 JS 代码的常用方式。

**`vm.runInNewContext(code, sandbox, options)`**

| 参数                    | 类型      | 默认值                       | 说明                 |
| --------------------- | ------- | ------------------------- | ------------------ |
| code                  | string  | 必填                        | 要执行的 JS 代码字符串      |
| sandbox               | object  | {}                        | 沙箱对象，代码中的全局变量来自此对象 |
| options.filename      | string  | 'evalmachine.<anonymous>' | 错误堆栈中显示的文件名        |
| options.lineOffset    | number  | 0                         | 错误堆栈中的行偏移量         |
| options.timeout       | number  | 无限制                       | 执行超时（毫秒），超时抛出错误    |
| options.breakOnSigint | boolean | false                     | Ctrl+C 时是否中断执行     |
| options.contextName   | string  | 'VM Context'              | 上下文名称（调试用）         |

```javascript
const vm = require('vm');

const sandbox = {
    window: {},
    result: null
};

vm.runInNewContext(`
    function encrypt(data) {
        return data.split('').reverse().join('');
    }
    result = encrypt('hello');
`, sandbox);

console.log(sandbox.result); // 'olleh'

```

**`vm.Script`**

适合需要多次执行同一段代码的场景（预编译）：

| 参数                   | 类型     | 默认值                       | 说明            |
| -------------------- | ------ | ------------------------- | ------------- |
| code                 | string | 必填                        | 要编译的 JS 代码字符串 |
| options.filename     | string | 'evalmachine.<anonymous>' | 错误堆栈文件名       |
| options.lineOffset   | number | 0                         | 行偏移量          |
| options.columnOffset | number | 0                         | 列偏移量          |
| options.cachedData   | Buffer | 无                         | 预编译字节码缓存      |

```javascript
const vm = require('vm');

const script = new vm.Script(`
    var count = (typeof count === 'undefined') ? 0 : count;
    count++;
`);

const ctx = vm.createContext({ count: 0 });

for (let i = 0; i < 5; i++) {
    script.runInContext(ctx);
}
console.log(ctx.count); // 5

```

**`vm.createContext(sandbox)`**

将一个普通对象提升为 vm 上下文（contextify）：

| 参数      | 类型     | 默认值 | 说明                       |
| ------- | ------ | --- | ------------------------ |
| sandbox | object | {}  | 作为全局对象的基础对象，执行后属性会被写入此对象 |

```javascript
const vm = require('vm');

const ctx = vm.createContext({
    window: {},
    console: console,    // 允许沙箱内使用 console
    require: require     // 谨慎：允许沙箱访问 require 会破坏隔离性
});

const script = new vm.Script('window.x = 42;');
script.runInContext(ctx);
console.log(ctx.window.x); // 42

```

### vm 与直接 eval 的区别

| 对比项  | eval            | vm.runInNewContext |
| ---- | --------------- | ------------------ |
| 作用域  | 共享当前作用域，可访问外部变量 | 独立沙箱，无法访问外部变量      |
| 全局对象 | 共享 global       | 使用传入的 sandbox 对象   |
| 错误隔离 | 无隔离，错误会污染当前进程   | 相对隔离（但不是完全安全隔离）    |
| 适用场景 | 简单快速执行，信任代码     | 执行不确定代码，需要控制全局环境   |
| 性能   | 较高              | 略低（上下文创建有开销）       |

逆向场景推荐使用 `vm` 而非 `eval`，原因：扣出的浏览器代码通常会访问 `window`、`document` 等全局变量，通过 `sandbox` 可以精确控制这些变量的值。

---

## 模块系统

### CommonJS

```javascript
// 导出
// utils.js
function md5(str) { /* ... */ }
module.exports = { md5 };
// 或
module.exports.md5 = function(str) { /* ... */ };

// 导入
const utils = require('./utils');
utils.md5('hello');

// 导入内置模块
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

```

### ESM

Node.js 支持 ESM，但有以下限制：

- 文件扩展名必须为 `.mjs`，或 `package.json` 中设置 `"type": "module"`
- 不能直接使用 `require`（需要用 `createRequire` 代替）
- 顶层 `await` 可用

```javascript
// ESM 导入导出
import { encrypt } from './crypto.mjs';
export function sign(data) { return encrypt(data); }

// 在 ESM 中使用 require
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const fs = require('fs'); // 现在可以用 require 了

```

### 动态 require 加载文件

```javascript
// 动态加载（路径在运行时确定）
const moduleName = 'crypto';
const mod = require(moduleName);

// 加载并执行 JS 文件（常用于加载扣出的 bundle）
const bundle = require('./extracted_bundle.js');

// 强制重新加载（绕过缓存）
delete require.cache[require.resolve('./target.js')];
const fresh = require('./target.js');

```

---

## 逆向还原常用内置模块

### fs：读写文件

**`fs.readFileSync(path, options)`**

| 参数               | 类型               | 默认值  | 说明                              |      |
| ---------------- | ---------------- | ---- | ------------------------------- | ---- |
| path             | string \| Buffer | URL  | 必填                              | 文件路径 |
| options.encoding | string \| null   | null | 编码格式，如 'utf8'；为 null 时返回 Buffer |      |
| options.flag     | string           | 'r'  | 文件系统标志                          |      |

**`fs.writeFileSync(path, data, options)`**

| 参数               | 类型               | 默认值        | 说明            |             |
| ---------------- | ---------------- | ---------- | ------------- | ----------- |
| path             | string \| Buffer | URL        | 必填            | 文件路径，不存在则创建 |
| data             | string \| Buffer | Uint8Array | 必填            | 要写入的内容      |
| options.encoding | string           | 'utf8'     | 编码格式          |             |
| options.flag     | string           | 'w'        | 'w' 覆盖，'a' 追加 |             |
| options.mode     | integer          | 0o666      | 文件权限          |             |

```javascript
const fs = require('fs');

// 读取扣出的 JS 文件
const code = fs.readFileSync('./bundle.js', 'utf8');

// 读取二进制文件（如 wasm）
const wasmBuf = fs.readFileSync('./target.wasm'); // 返回 Buffer

// 将结果写入文件
fs.writeFileSync('./result.txt', JSON.stringify(results, null, 2), 'utf8');

// 追加日志
fs.writeFileSync('./log.txt', `[${new Date().toISOString()}] ${msg}\n`, { flag: 'a' });

```

### path：路径拼接

```javascript
const path = require('path');

// 拼接路径（跨平台）
const filePath = path.join(__dirname, 'files', 'bundle.js');

// 解析绝对路径
const absPath = path.resolve('./bundle.js');

// 获取目录名、文件名、扩展名
path.dirname('/home/user/file.js');  // '/home/user'
path.basename('/home/user/file.js'); // 'file.js'
path.extname('/home/user/file.js');  // '.js'

```

### crypto：内置加密模块

**`crypto.createHash(algorithm)`**

| 参数        | 类型     | 默认值 | 说明                                      |
| --------- | ------ | --- | --------------------------------------- |
| algorithm | string | 必填  | 算法名，如 'md5', 'sha1', 'sha256', 'sha512' |

返回 `Hash` 对象，调用 `.update(data).digest(encoding)` 获取结果。

**`crypto.createHmac(algorithm, key)`**

| 参数        | 类型               | 默认值       | 说明             |         |
| --------- | ---------------- | --------- | -------------- | ------- |
| algorithm | string           | 必填        | 算法名，如 'sha256' |         |
| key       | string \| Buffer | KeyObject | 必填             | HMAC 密钥 |

**`crypto.createCipheriv(algorithm, key, iv)`**

| 参数        | 类型               | 默认值  | 说明                                 |                     |
| --------- | ---------------- | ---- | ---------------------------------- | ------------------- |
| algorithm | string           | 必填   | 算法名，如 'aes-128-cbc', 'aes-256-cbc' |                     |
| key       | string \| Buffer | 必填   | 密钥，长度需与算法匹配                        |                     |
| iv        | string \| Buffer | null | 必填                                 | 初始化向量（ECB 模式传 null） |

```javascript
const crypto = require('crypto');

// MD5
const md5 = crypto.createHash('md5').update('hello').digest('hex');

// SHA256
const sha256 = crypto.createHash('sha256').update('hello', 'utf8').digest('hex');

// HMAC-SHA256
const hmac = crypto.createHmac('sha256', 'secret_key')
    .update('data')
    .digest('hex');

// AES-128-CBC 加密
const key = Buffer.from('0123456789abcdef');   // 16 字节
const iv  = Buffer.from('fedcba9876543210');   // 16 字节
const cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
let encrypted = cipher.update('plaintext', 'utf8', 'hex');
encrypted += cipher.final('hex');

// AES-128-CBC 解密
const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');

// 生成随机字节
const randomBytes = crypto.randomBytes(16).toString('hex');

```

### https / http：发请求

```javascript
const https = require('https');

// GET 请求
function get(url) {
    return new Promise((resolve, reject) => {
        https.get(url, (res) => {
            let data = '';
            res.on('data', chunk => data += chunk);
            res.on('end', () => resolve(JSON.parse(data)));
        }).on('error', reject);
    });
}

// POST 请求（带 headers）
function post(url, body, headers = {}) {
    const bodyStr = JSON.stringify(body);
    const urlObj = new URL(url);
    return new Promise((resolve, reject) => {
        const req = https.request({
            hostname: urlObj.hostname,
            path: urlObj.pathname + urlObj.search,
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(bodyStr),
                ...headers
            }
        }, (res) => {
            let data = '';
            res.on('data', chunk => data += chunk);
            res.on('end', () => resolve(data));
        });
        req.on('error', reject);
        req.write(bodyStr);
        req.end();
    });
}

```

对比 `axios`：内置 `https` 模块更底层，无需安装依赖，适合简单请求或生产环境；`axios` 更简洁，适合开发调试阶段。逆向脚本推荐 `axios` 提高效率，生产还原脚本用内置模块避免依赖。

### Buffer：二进制数据处理

**`Buffer.from(data, encoding)`**

| 参数       | 类型              | 默认值         | 说明                                                |      |
| -------- | --------------- | ----------- | ------------------------------------------------- | ---- |
| data     | string \| Array | ArrayBuffer | 必填                                                | 数据来源 |
| encoding | string          | 'utf8'      | 当 data 为字符串时的编码：'utf8', 'hex', 'base64', 'latin1' |      |

**`buf.toString(encoding, start, end)`**

| 参数       | 类型     | 默认值        | 说明                                     |
| -------- | ------ | ---------- | -------------------------------------- |
| encoding | string | 'utf8'     | 目标编码：'hex', 'base64', 'utf8', 'latin1' |
| start    | number | 0          | 开始字节索引                                 |
| end      | number | buf.length | 结束字节索引（不含）                             |

```javascript
// 字符串 -> Buffer -> hex
const buf = Buffer.from('hello', 'utf8');
console.log(buf.toString('hex'));    // '68656c6c6f'
console.log(buf.toString('base64')); // 'aGVsbG8='

// hex 字符串 -> Buffer -> 原始字节
const hexBuf = Buffer.from('68656c6c6f', 'hex');
console.log(hexBuf.toString('utf8')); // 'hello'

// base64 解码
const b64Buf = Buffer.from('aGVsbG8=', 'base64');
console.log(b64Buf.toString('utf8')); // 'hello'

// 拼接多个 Buffer
const combined = Buffer.concat([buf1, buf2]);

// Buffer 与 Uint8Array 互转
const uint8 = new Uint8Array(buf);
const backToBuf = Buffer.from(uint8.buffer, uint8.byteOffset, uint8.byteLength);

```

---

## 补全浏览器环境

浏览器 JS 代码运行在 Node.js 时，通常会访问 `window`、`document`、`navigator` 等全局对象。需要手动补全，按报错逐步添加。

### 常用 polyfill 写法

```javascript
// 最常用的基础补全
global.window = global;
global.self = global;

// location 对象
global.location = {
    href: 'https://www.example.com/',
    origin: 'https://www.example.com',
    protocol: 'https:',
    host: 'www.example.com',
    hostname: 'www.example.com',
    port: '',
    pathname: '/',
    search: '',
    hash: ''
};

// navigator 对象
global.navigator = {
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
    platform: 'Win32',
    language: 'zh-CN',
    languages: ['zh-CN', 'zh', 'en'],
    cookieEnabled: true,
    onLine: true,
    vendor: 'Google Inc.'
};

// document 基础补全
global.document = {
    cookie: '',
    referrer: 'https://www.example.com/',
    title: '',
    URL: 'https://www.example.com/',
    domain: 'www.example.com',
    createElement: function(tag) { return { style: {} }; },
    getElementById: function(id) { return null; },
    querySelector: function(sel) { return null; },
    querySelectorAll: function(sel) { return []; }
};

// screen 对象
global.screen = {
    width: 1920,
    height: 1080,
    colorDepth: 24,
    pixelDepth: 24
};

// atob / btoa（Node.js >= 16 已内置，旧版本需手动补）
if (typeof atob === 'undefined') {
    global.atob = (str) => Buffer.from(str, 'base64').toString('latin1');
    global.btoa = (str) => Buffer.from(str, 'latin1').toString('base64');
}

// performance
global.performance = {
    now: function() { return Date.now(); },
    timing: { navigationStart: Date.now() }
};

// localStorage / sessionStorage（简单 mock）
global.localStorage = (function() {
    const store = {};
    return {
        getItem: (k) => store[k] || null,
        setItem: (k, v) => { store[k] = String(v); },
        removeItem: (k) => { delete store[k]; },
        clear: () => { Object.keys(store).forEach(k => delete store[k]); }
    };
})();

```

### jsdom 快速集成

对于需要更完整 DOM 环境的场景（如代码操作 DOM 元素），直接使用 jsdom。详细用法参见 [jsdom完全指南](https://blog.vercanti.com/jsdom-wan-quan-zhi-nan-jsni-xiang-shi-zhan/)。

```javascript
const { JSDOM } = require('jsdom');

const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
    url: 'https://www.example.com/',
    referrer: 'https://www.example.com/',
    contentType: 'text/html',
    pretendToBeVisual: true
});

// 将 jsdom 的 window 挂载到 global
const { window } = dom;
global.window = window;
global.document = window.document;
global.navigator = window.navigator;
global.location = window.location;

// 现在可以运行浏览器代码了
eval(fs.readFileSync('./bundle.js', 'utf8'));

```

---

## 调试技巧

### `--inspect` / `--inspect-brk` 开启 Chrome DevTools 调试

```bash
# 启动调试服务器，等待连接后才执行代码
node --inspect-brk script.js

# 启动调试服务器，不等待，代码立即执行
node --inspect script.js

# 指定端口（默认 9229）
node --inspect-brk=0.0.0.0:9230 script.js

```

连接方式：

1. 在 Chrome 地址栏输入 `chrome://inspect`
2. 点击 "Configure..." 添加 `localhost:9229`
3. 在 "Remote Target" 下点击 "inspect" 打开 DevTools

`--inspect-brk` 会在第一行代码处暂停，适合调试启动阶段的问题。`--inspect` 不暂停，适合在代码中手动设置断点（`debugger` 语句）。

### console.log 调试 vs 断点调试

| 对比项     | console.log | 断点调试      |
| ------- | ----------- | --------- |
| 适用场景    | 快速验证、批量输出   | 深入分析执行流程  |
| 效率      | 需要重复运行      | 可实时查看所有变量 |
| 对代码的侵入性 | 需要修改代码      | 不修改代码     |
| 异步代码    | 输出顺序可能混乱    | 可暂停，清晰追踪  |

逆向建议：先用 `console.log` 快速确认入参和返回值，再用断点调试定位内部逻辑。

```javascript
// 包装目标函数，自动打印参数和返回值
function wrapFn(fn, name) {
    return function() {
        const args = Array.from(arguments);
        console.log(`[${name}] 调用参数:`, args);
        const result = fn.apply(this, arguments);
        console.log(`[${name}] 返回值:`, result);
        return result;
    };
}

target.encrypt = wrapFn(target.encrypt, 'encrypt');

```

---

---

## 最佳实践

**用 `--inspect` 调试扣出的 JS 代码**：`node --inspect-brk target.js` 启动后在 Chrome 打开 `chrome://inspect`，可以用 DevTools 调试 Node.js 代码，包括断点、变量查看、Call Stack，比 `console.log` 高效得多。

**内置 `crypto` 模块替代浏览器 `crypto.subtle`**：逆向代码中 `crypto.createHash('md5')`、`crypto.createHmac` 等是 Node.js 内置 API，无需安装额外包；浏览器的 `crypto.subtle` 对应 `webcrypto`，Node 18+ 的 `globalThis.crypto` 也可用。

**`vm.runInNewContext` 隔离全局状态**：多段目标代码需要并行执行时，用 `vm.runInNewContext(code, sandbox)` 各自隔离全局变量；比在同一个 `global` 上运行更安全，避免相互污染。

**`process.argv` 传参比环境变量更简单**：在 Python 中通过 `subprocess.run(['node', 'sign.js', json.dumps(params)])` 调用，Node.js 里 `const params = JSON.parse(process.argv[2])` 接收，比用文件或 stdin 更直接。

**ES Modules 支持用 `.mjs` 或 `"type": "module"`**：目标代码用 `import`/`export` 时，文件改为 `.mjs` 扩展名或在 `package.json` 加 `"type": "module"` 即可运行，不需要 Babel 转译。

---

## 常见陷阱

### 陷阱：`require` 在 ESM 模块中不可用

**现象：** `.mjs` 文件中用 `require('crypto')` 报 `ReferenceError: require is not defined`。  
**原因：** ES Modules 中没有 `require`，需要用 `import crypto from 'crypto'` 或 `createRequire(import.meta.url)` 兼容。  
**解决：** `import { createRequire } from 'module'; const require = createRequire(import.meta.url);` 后就可以用 `require`。

### 陷阱：`console.log` 输出的对象在某些情况下不完整

**现象：** `console.log(bigObject)` 只显示 `[Object: null prototype]` 或截断了嵌套内容。  
**原因：** Node.js 的 `console.log` 默认只显示 2 层嵌套，超过层级的显示为 `[Object]`。  
**解决：** `console.log(JSON.stringify(bigObject, null, 2))` 完整输出；或 `require('util').inspect(bigObject, { depth: null })`。

### 陷阱：Node.js 版本不兼容目标代码语法

**现象：** 运行目标 JS 报 `SyntaxError: Unexpected token '?.'`（可选链）或 `Private fields are not supported`。  
**原因：** 较老的 Node.js 版本不支持新 JS 语法；Node.js 14 不支持可选链，Node.js 12 不支持逻辑赋值运算符。  
**解决：** 升级到 Node.js 20+ LTS；或用 `npx babel target.js --presets @babel/preset-env` 将代码降级后运行。

---

## 参见

[Webpack逆向还原](https://blog.vercanti.com/webpack-ni-xiang-huan-yuan/)  
[补环境](https://blog.vercanti.com/bu-huan-jing/)  
[jsdom完全指南](https://blog.vercanti.com/jsdom-wan-quan-zhi-nan-jsni-xiang-shi-zhan/)  
[js逆向调试技巧](https://blog.vercanti.com/js-ni-xiang-diao-shi-ji-qiao/)