关于 js:9. Node.js 后端相关

发布于:2025-05-15 ⋅ 阅读:(10) ⋅ 点赞:(0)

一、Node 环境搭建与执行流程

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,它让 JS 不再局限于浏览器内,而是可以在服务器、终端、本地脚本中运行

核心定位:让我们可以用 JS 写本地程序、脚本、爬虫、加密逻辑、hook 工具、代理服务器等一切“非网页”程序。

1. Node.js 安装与环境搭建

1)下载 Node.js

官网地址:Node.js — Run JavaScript Everywhere

建议安装 LTS(长期支持版),比如 Node 18.x,因为一些库在新版本可能不兼容。

2)验证是否安装成功

打开终端输入:

node -v    # 显示 Node 版本
npm -v     # 显示 Node 的包管理器 npm 的版本

3)使用 nvm 管理多个版本

在实际项目中,不同工具/库可能依赖不同 Node 版本,这时用 nvm 可以快速切换版本。

安装(以 Linux/macOS 为例):

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash

然后安装/切换版本:

nvm install 18
nvm use 18
nvm install 14
nvm use 14

Windows 用户可以安装 nvm-windows

2. Node 执行流程与原理

1)执行一个 Node 脚本

node myscript.js

Node 会按照以下流程处理这段 JS:

步骤解析:

  1. 加载模块
    加载内置模块(如 fscrypto)和你写的文件或库(require()

  2. 构造运行环境
    创建一个伪装的浏览器环境,提供 globalprocessconsolesetTimeout 等函数

  3. 使用 V8 编译代码为机器码
    Node 使用 V8 引擎将 JS 代码编译为机器码并执行(所以跑得飞快)

  4. 执行主线程代码
    运行 myscript.js 中的同步代码

  5. 进入事件循环
    处理异步任务(如 setTimeout, http 请求, 文件读写 等)

2)Node 与浏览器环境的不同

功能 Node.js 浏览器
全局对象 global window
DOM 操作  无  有
网络请求 httphttps模块 fetch, XHR
加密模块 crypto(强大) 受限
沙箱执行 vmvm2  无

所以在逆向时,如果把网页 JS 拿到本地来跑,大多数时候需要用 Node.js 模拟执行,尤其适合调试加密逻辑。

3. Node 工程结构

my-script/
├── index.js         # 入口文件
├── encrypt.js       # 加密函数
├── utils.js         # 工具方法
├── data/            # 保存中间数据
├── logs/            # 日志输出
├── package.json     # 项目依赖描述文件

举例:

/*
Project: my-script
Structure:
  my-script/
  ├── index.js         # 入口文件
  ├── encrypt.js       # 加密函数
  ├── utils.js         # 工具方法
  ├── data/            # 保存中间数据
  ├── logs/            # 日志输出
  └── package.json     # 项目依赖描述文件
*/

// --- package.json ---
{
  "name": "my-script",
  "version": "1.0.0",
  "description": "示例 Node.js 项目,演示加密与日志",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {}
}

// --- encrypt.js ---
// 加密函数模块,使用 HMAC-SHA256 将文本与密钥签名
const crypto = require('crypto');

/**
 * 对文本进行 HMAC-SHA256 签名
 * @param {string} text - 待加密的文本
 * @param {string} secret - 密钥
 * @returns {string} 十六进制签名
 */
function encrypt(text, secret) {
  return crypto.createHmac('sha256', secret)
               .update(text)
               .digest('hex');
}

module.exports = { encrypt };

// --- utils.js ---
// 工具方法模块,提供文件和日志操作
const fs = require('fs');
const path = require('path');

const DATA_DIR = path.resolve(__dirname, 'data');
const LOG_DIR = path.resolve(__dirname, 'logs');

// 确保目录存在
function ensureDir(dir) {
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
}

// 写入 JSON 数据到 data 目录
function writeData(filename, obj) {
  ensureDir(DATA_DIR);
  const filePath = path.join(DATA_DIR, filename);
  fs.writeFileSync(filePath, JSON.stringify(obj, null, 2), 'utf-8');
}

// 日志输出到 logs 目录
function log(message) {
  ensureDir(LOG_DIR);
  const timestamp = new Date().toISOString();
  const logLine = `[${timestamp}] ${message}\n`;
  const logFile = path.join(LOG_DIR, 'app.log');
  fs.appendFileSync(logFile, logLine, 'utf-8');
  console.log(logLine.trim());
}

module.exports = { writeData, log };

// --- index.js ---
// 项目入口文件,演示加密和数据保存流程
const { encrypt } = require('./encrypt');
const { writeData, log } = require('./utils');

// 示例输入,可以改为从 process.argv 或 env 中读取
const inputText = 'Hello, Reverse Engineering!';
const secretKey = 'my_secret_key';

log('项目启动');
const signature = encrypt(inputText, secretKey);
log(`加密结果: ${signature}`);

// 保存结果到 data/result.json
writeData('result.json', { text: inputText, signature });
log('结果已保存到 data/result.json');

log('项目结束');

运行方式:

node index.js

4. npm / npx 使用(包管理器)

npm install axios
npm install puppeteer
npm install vm2

npx 可以临时运行工具包,不用全局安装:

npx asar extract app.asar ./unpacked

总结

名称 作用
Node.js 用于本地运行 JS 脚本(突破浏览器限制)
nvm 管理多个 Node 版本(防止依赖冲突)
npm/npx 安装运行依赖工具库
node 脚本 可执行 JS 文件,用于爬虫/hook/加密调试
事件循环 保证异步逻辑(如文件/网络)不阻塞主线程

二、fs 模块

fs 是 Node.js 的内置模块,提供对文件系统的操作功能,包含:

  • 文件读写

  • 文件夹操作

  • 文件信息查询

  • 监听文件变化

  • 创建/删除/重命名

可以用它做类似 Python 中 open()osshutil 的事。

1. 引入 fs 模块

const fs = require('fs');

Node.js 中一切 IO 操作(包括 fs)默认提供两套接口:

类型 描述 适用场景
同步(Sync) 阻塞后续代码执行,简单易用 小脚本、调试逻辑
异步(Async) 非阻塞,有回调或返回 Promise 高并发、生产级程序

2. 常见用法大全(对照表)

功能 异步方法 同步方法
读文件 fs.readFile(path, cb) fs.readFileSync(path)
写文件 fs.writeFile(path, data, cb) fs.writeFileSync(path, data)
追加写入 fs.appendFile(path, data, cb) fs.appendFileSync(path, data)
判断文件是否存在 fs.existsSync(path)  同步的
读目录 fs.readdir(path, cb) fs.readdirSync(path)
创建目录 fs.mkdir(path, cb) fs.mkdirSync(path)
删除文件 fs.unlink(path, cb) fs.unlinkSync(path)
重命名/移动 fs.rename(oldPath, newPath, cb) fs.renameSync(oldPath, newPath)
文件信息 fs.stat(path, cb) fs.statSync(path)

3. 示例详解

1)读取文件内容

异步方式:

fs.readFile('secret.txt', 'utf8', (err, data) => {
  if (err) return console.error('读取失败:', err);
  console.log('内容是:', data);
});

同步方式(调试时很好用):

const data = fs.readFileSync('secret.txt', 'utf8');
console.log('内容是:', data);

2)写入/覆盖文件

fs.writeFileSync('output.txt', '保存一些加密参数...');

3)追加写入(如日志)

fs.appendFileSync('log.txt', `[${new Date()}] 请求参数: ${param}\n`);

4)判断文件是否存在

if (fs.existsSync('result.json')) {
  console.log('已有缓存文件,可直接读取');
}

5)遍历目录中所有文件(递归)

function walk(dir) {
  const files = fs.readdirSync(dir);
  for (const file of files) {
    const fullPath = dir + '/' + file;
    const stat = fs.statSync(fullPath);
    if (stat.isDirectory()) {
      walk(fullPath);
    } else {
      console.log('发现文件:', fullPath);
    }
  }
}

walk('./src');

4. 在逆向/爬虫中的实际用途

场景 使用方式
保存爬虫抓取的数据 fs.writeFileSync('data.json', JSON.stringify(res))
保存 cookie / headers fs.writeFileSync('cookie.txt', cookieString)
记录日志 / 错误 fs.appendFileSync('log.txt', error.stack)
修改已打包 JS 文件 读取 app.asar 里的 main.js 后用正则或 AST 修改后再写回
将 hook 到的函数参数保存下来 在 hook 逻辑里 appendFileSync() 输出

5. 注意事项

  • 文件路径建议使用 path.join() 而不是硬编码 './a/b.txt',防止跨平台路径问题(Windows 用反斜杠)

  • 写文件前可判断目录是否存在:

if (!fs.existsSync('./logs')) fs.mkdirSync('./logs');
  • 异步操作都带回调;要用 Promise 化的版本建议使用 fs/promises
const fs = require('fs/promises');

await fs.writeFile('a.txt', 'hello');

总结

模块 功能
fs.readFileSync() 读取文件内容
fs.writeFileSync() 写入(覆盖)内容
fs.appendFileSync() 追加写入
fs.readdirSync() 读取目录下的所有文件
fs.statSync() 获取文件大小、类型等信息
fs.existsSync() 判断文件/文件夹是否存在

三、http 模块

Node.js 的 http 模块是用来创建 Web 服务和发送 HTTP 请求的内置模块,无需安装。

功能包含:

类别 常用功能
服务端 启动一个 HTTP 服务器,监听端口、处理请求
客户端 向目标地址发送 GET / POST 等请求

Node 的 http 模块是底层 API,不像 axios / fetch 那么简洁,但可以完全控制请求头、数据结构,适合底层调试与逆向用途。

1. http 服务端:构建本地 HTTP 服务器

这是最常见的用途,比如写一个本地服务,监听请求、打印参数、调试 JS。

示例:创建一个本地服务器

const http = require('http');

const server = http.createServer((req, res) => {
  console.log('收到请求:', req.method, req.url);

  // 接收数据
  let body = '';
  req.on('data', chunk => body += chunk);
  req.on('end', () => {
    console.log('请求体:', body);

    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ status: 'ok', msg: '已收到' }));
  });
});

server.listen(3000, () => {
  console.log('服务器运行在 http://localhost:3000');
});

用途:

  • 在调试前端 JS 加密时,你可以把它改成一个假接口,接收参数打印出来

  • 在模拟客户端请求时,观察 JS 发送的 headers、payload、cookie

  • 在本地模拟行为验证服务器(如极验的行为验证请求)

2. http 客户端:发送 HTTP 请求

http.request() 是 Node 最底层的发请求方法,适合精细控制 headers、加密数据包。

示例:发送 GET 请求

const http = require('http');

const options = {
  hostname: 'example.com',
  port: 80,
  path: '/api/data',
  method: 'GET',
  headers: {
    'User-Agent': 'NodeReverse/1.0',
  }
};

const req = http.request(options, res => {
  let data = '';

  res.on('data', chunk => data += chunk);
  res.on('end', () => {
    console.log('响应结果:', data);
  });
});

req.on('error', err => {
  console.error('请求失败:', err);
});

req.end();

示例:发送 POST 请求(传递 JSON 数据)

const postData = JSON.stringify({ username: 'test', password: '123' });

const options = {
  hostname: 'example.com',
  port: 80,
  path: '/api/login',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': Buffer.byteLength(postData),
  }
};

const req = http.request(options, res => {
  let data = '';
  res.on('data', chunk => data += chunk);
  res.on('end', () => {
    console.log('登录响应:', data);
  });
});

req.write(postData); // 写入请求体
req.end();

3. 在爬虫/逆向中的实战用途

场景 用法
 监听前端请求参数 本地搭建 server 打印 JS 发出的请求
 模拟滑动/行为验证的接口 建本地服务模拟验证码验证流程
 模拟客户端请求 自定义 headers/cookie/加密参数
 创建中间人代理 拦截原始请求/响应内容用于分析(比如 hook 极验行为参数)
 本地调试 JS 文件请求 前端访问你的 http://127.0.0.1/mock.js,返回伪造 JS

4. http vs axios 对比

特点 http axios
是否内置  Node 内置模块  需额外安装
是否支持 Promise  不支持  原生支持
用法复杂度 底层、繁琐 简洁、方便
灵活性 控制一切细节(如 TCP 层) 封装好,调试不方便

所以在 底层逆向、调试请求细节(cookie、加密数据包) 时建议用 http,而普通 API 爬虫用 axios 更方便。

5. 基于 http 构建代理服务器

可以使用 http.createServer() + http.request() 构建一个转发代理。

简单版本:

// 接收请求 -> 修改参数 -> 发出新请求 -> 返回响应
http.createServer((req, res) => {
  const newReq = http.request({
    hostname: '真实服务器地址',
    path: req.url,
    method: req.method,
    headers: req.headers,
  }, realRes => {
    realRes.pipe(res);
  });

  req.pipe(newReq);
}).listen(8000);

用途:

  • 中间人分析原始请求

  • 实时 hook 某个 cookie / 参数

  • 拦截极验验证发出的请求,替换掉 w 值再发出

总结

方法 功能
http.createServer() 创建 HTTP 服务器
http.request() 发送 HTTP 请求(底层控制)
res.writeHead() 设置返回状态码与 header
req.on('data') 获取请求体内容(用于 POST)
res.end() 响应客户端请求

四、process 模块

process 是 Node.js 的全局对象,不需要 require(),可以直接使用。

它是对当前运行中的 Node 进程的抽象,可以让你:

功能 示例用途
获取命令行参数 分析 CLI 参数,支持传参运行
获取环境变量 判断运行环境、账号密码等配置
控制进程退出 脚本出错时终止运行
捕获异常和退出信号 做异常日志记录
获取内存使用情况 检查内存泄漏/性能问题
切换工作目录 动态切换执行路径

1. 常用功能详解

1)获取命令行参数(process.argv

node app.js --url=https://test.com
console.log(process.argv);
// 输出示例: [ 'node', '/path/app.js', '--url=https://test.com' ]

可以用来做命令行脚本参数解析,比如自动化逆向:

const args = process.argv.slice(2); // 取参数部分
const urlArg = args.find(x => x.startsWith('--url='));
const url = urlArg ? urlArg.split('=')[1] : null;

2)获取环境变量(process.env

console.log(process.env.NODE_ENV); // 'development' or 'production'

在开发时通常会用 .env 文件配合,做自动配置切换:

NODE_ENV=production node app.js
if (process.env.NODE_ENV === 'production') {
  console.log('生产环境逻辑');
}

在安全逆向时,也可以用来控制不同设备、账号、token 的切换。

3)控制程序退出(process.exit()

if (!url) {
  console.error('缺少 URL 参数');
  process.exit(1); // 返回非 0 表示异常退出
}

process.exit(0) 表示正常退出
process.exit(1) 表示有错误退出

适用于:

  • 参数错误就终止脚本

  • 抓不到数据就退出

  • 自动化测试流程中

4)捕获异常(process.on('uncaughtException')

process.on('uncaughtException', err => {
  console.error('发生未捕获错误:', err);
  fs.appendFileSync('error.log', err.stack);
});

适合在逆向调试时保底捕获错误日志,防止程序直接崩溃。

5)获取当前工作目录(process.cwd()

console.log(process.cwd()); // /home/user/project

配合 fs 读取相对路径很有用:

const filePath = path.join(process.cwd(), 'data', 'config.json');

6)修改当前工作目录(process.chdir()

process.chdir('/tmp');

在脚本执行前切换路径,或者做类似 cd 的效果。

7)获取内存占用(process.memoryUsage()

console.log(process.memoryUsage());

输出:

{
  rss: 4935680,
  heapTotal: 1826816,
  heapUsed: 650472,
  external: 49879
}

适合分析 Node 服务是否存在内存泄漏(如 puppeteer 没关闭、死循环等)

2. 实战场景应用

场景 用法
 参数传递调试脚本 node decrypt.js --key=abc123
 切换环境变量运行 process.env.NODE_ENV === 'dev'
 错误日志保存 捕获异常后写入 error.log
 多账户批量登录 process.argv 动态传账号密码列表
 获取相对路径 fs.readFileSync(path.join(process.cwd(), 'xxx'))
 CLI 工具封装 包装你写的爬虫脚本变成命令行工具(支持参数)

3. 实战示例:批量解密脚本

命令:

node decrypt.js --input=enc.txt --key=abc123

代码片段:

const fs = require('fs');
const path = require('path');

const args = process.argv.slice(2);
const inputFile = args.find(x => x.startsWith('--input=')).split('=')[1];
const key = args.find(x => x.startsWith('--key=')).split('=')[1];

if (!inputFile || !key) {
  console.error('缺少参数 --input / --key');
  process.exit(1);
}

const content = fs.readFileSync(path.join(process.cwd(), inputFile), 'utf8');
// 这里进行解密...
console.log(`使用密钥 ${key} 解密成功`);

总结:process 模块核心 API

方法 / 属性 作用
process.argv 获取命令行参数数组
process.env 获取环境变量对象
process.exit(code) 终止程序(非0代表异常)
process.on('event', cb) 监听进程事件(如异常、退出)
process.cwd() 当前工作目录
process.chdir(dir) 改变当前目录
process.memoryUsage() 获取内存使用情况

五、使用 puppeteer / playwright 实现自动化爬虫

Puppeteer / Playwright 这两个工具都是用来控制浏览器自动化操作的工具

名称 背景 特点
Puppeteer Google 出品 控制 Chromium,功能成熟,适合网页截图、PDF、调试逆向
Playwright 微软出品 支持 Chromium、Firefox、WebKit,多浏览器自动化更强大,更适合模拟人类操作

简单说:

  • Puppeteer 适合单页调试、网页 JS 参数逆向

  • Playwright 适合行为模拟、滑块验证码、设备模拟

1. 安装和环境准备

使用 Node.js 环境:

安装 Puppeteer:

npm install puppeteer

安装 Playwright:

npm install playwright
npx playwright install

Playwright 会自动安装 Chromium、Firefox、WebKit 等浏览器内核。

2. 核心功能

功能 Puppeteer / Playwright 都能做
 打开网页 page.goto()
 模拟点击 page.click()
 输入内容 page.type()
 获取元素内容 page.$eval()
 执行 JS page.evaluate()
 截图、PDF page.screenshot()
 拦截请求 page.on('request', ...)
 设置 Cookie / UA / Headers page.setUserAgent()
 控制多页、多标签页 browser.newPage()

3. Puppeteer 示例(核心 API)

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: false }); // 非 headless 方便调试
  const page = await browser.newPage();

  await page.goto('https://example.com');

  // 获取元素文本
  const title = await page.$eval('h1', el => el.innerText);
  console.log('标题:', title);

  // 模拟输入和点击
  await page.type('#username', 'test');
  await page.type('#password', '123456');
  await page.click('#login');

  // 拦截请求
  page.on('request', req => {
    console.log('请求地址:', req.url());
  });

  // 等待页面加载完成
  await page.waitForTimeout(3000);

  await browser.close();
})();

4. Playwright 示例(行为模拟更强)

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  await page.goto('https://example.com');

  // 模拟滑块滑动
  const slider = await page.$('#slider');
  const box = await slider.boundingBox();
  await page.mouse.move(box.x + 10, box.y + box.height / 2);
  await page.mouse.down();
  await page.mouse.move(box.x + 200, box.y + box.height / 2, { steps: 30 });
  await page.mouse.up();

  await page.screenshot({ path: 'page.png' });

  await browser.close();
})();

Playwright 能控制真实鼠标移动,适合破解行为识别、极验、滑块等场景。

5. 实战用途

场景 用法
 动态网页加载(JS 渲染) 打开页面 + page.content() 获取完整 HTML
 参数加密流程分析 page.on('request') 抓取发送请求参数(观察 w、sign 等)
 极验滑块破解 模拟鼠标移动 + canvas 获取轨迹
 Hook JS 函数 page.evaluate 注入函数替换(如 hook encrypt())
 分析行为验证 获取 JS 执行后生成的 token / trace 参数
 绕过 WebSocket 加密 监听 WS 发送数据(适配 sekiro 等场景)
 调试前端 JS 模块 使用控制台调试加密 JS 脚本流程

6. 逆向技巧实战:hook JS 函数

可以用 page.evaluateOnNewDocument() 来劫持 JS 函数:

await page.evaluateOnNewDocument(() => {
  const originalEncrypt = window.encrypt;
  window.encrypt = function (...args) {
    console.log('调用 encrypt 参数:', args);
    return originalEncrypt.apply(this, args);
  }
});

这样可以在页面加载之前注入 Hook,非常适合调试混淆函数、密钥生成函数、行为参数构造函数

7. 使用 puppeteer 拿动态加密参数(完整流程)

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();

  // 打开页面
  await page.goto('https://xxx.com');

  // 模拟输入和点击登录
  await page.type('#username', 'test');
  await page.type('#password', '123456');
  await page.click('#login');

  // 拦截登录请求并查看加密参数
  page.on('request', req => {
    if (req.url().includes('/api/login')) {
      console.log('登录请求发出,参数:', req.postData());
    }
  });

  await page.waitForTimeout(5000);
  await browser.close();
})();

总结对比

功能 Puppeteer Playwright
支持浏览器 Chromium Chromium、Firefox、WebKit
跨平台稳定性 较强 更强
鼠标/键盘模拟 一般 精准、支持人类行为模拟
多页面控制 支持 更方便
社区支持 成熟 活跃、更新快
是否推荐 还行 更强大

六、node-hook

node-hook 是一个可以拦截 require() 加载过程的模块。可以在模块被引入前,动态修改它的源码注入自定义代码。 

非常适用于 hook 掉某个 Node.js 模块,在加载时修改源码或替换功能逻辑。

比如可以:

  • hook 掉 crypto 模块,打印所有加密参数

  • hook 掉某个自定义模块,替换为 mock 实现

  • hook 掉混淆代码模块,在加载时插入 console.log

安装

npm install node-hook

核心原理

在 Node.js 中,.js 文件通过 Module._extensions['.js'] 加载。

node-hook 就是通过劫持这个加载流程,在模块加载前修改源码,像下面这样:

require('node-hook').hook('.js', (source, filename) => {
  console.log('[HOOK]', filename);
  return modifiedSourceCode;
});

1. 核心 API 用法

1)基础 hook

const hook = require('node-hook');

hook.hook('.js', function (source, filename) {
  if (filename.includes('target-module.js')) {
    // 在模块加载前,插入调试日志
    source = "console.log('hooked!');\n" + source;
  }
  return source;
});

2)hook 指定模块

hook.hook('js', (source, filename) => {
  if (filename.includes('encrypt.js')) {
    source = source.replace('function encrypt', 'function encrypt_hooked');
  }
  return source;
});

2. 实战:hook 加密模块分析参数

假设有一个模块叫 encrypt.js

// encrypt.js
function encrypt(data) {
  return data.split('').reverse().join('');
}
module.exports = encrypt;

想看看谁调用了它,以及传了什么参数。

hook 分析:

const hook = require('node-hook');

hook.hook('.js', (source, filename) => {
  if (filename.endsWith('encrypt.js')) {
    source = `
      console.log('[HOOKED] 加密函数被调用');
      ${source.replace(
        'function encrypt',
        'function encrypt(data) { console.log("参数:", data); '
      )}
    `;
  }
  return source;
});

// 调用真实代码
const encrypt = require('./encrypt');
encrypt('abc123');

输出:

[HOOKED] 加密函数被调用
参数: abc123

3. 配合 vm2 使用:沙箱调试第三方加密代码

有些混淆代码不想直接执行,可以使用 vm2 加上 node-hook

const { NodeVM } = require('vm2');
const hook = require('node-hook');

hook.hook('.js', (source, filename) => {
  if (filename.includes('secret.js')) {
    // 劫持加密函数
    return source.replace('encrypt(', 'hooked_encrypt(');
  }
  return source;
});

const vm = new NodeVM({
  require: {
    external: true,
    context: 'sandbox'
  }
});

vm.run(`require('./secret')`, __dirname);

4. 高级玩法:用 fake 模块替代真实模块

有时候想直接用假的模块来替换真实模块,比如替换掉 axios

const hook = require('node-hook');

// 替换 axios
hook.hook('.js', (source, filename) => {
  if (filename.endsWith('axios/index.js')) {
    return `
      module.exports = {
        get(url) { console.log("拦截 GET:", url); return Promise.resolve({ data: 'fake-data' }); },
        post() {}
      };
    `;
  }
  return source;
});

适合绕过外部请求、分析数据结构、脱离真实环境调试。

5. 常见应用场景

目标 用法
 调试混淆加密模块 插入 log,观察传参与返回值
 替换模块实现 模拟外部模块、伪造返回值
 分析依赖模块调用链 打印 require 加载路径
 沙箱执行 + hook vm2 搭配,做静态分析或安全隔离
 自动打包分析 在 Electron、webpack 项目中动态插桩分析模块逻辑

总结

特性 说明
 模块加载前拦截 拦截所有 .js 文件加载流程
 修改源码注入 可注入 log、替换函数、插入 hook
 支持条件拦截 按文件路径、模块名判断是否 hook
 配合 vm2 用于 JS 沙箱安全执行
 适合逆向 快速分析混淆模块、嵌套依赖模块行为

七、vm2 用于 hook 模块

vm2 是一个基于 Node.js 原生 vm 模块的封装库,用于:

  •  在沙箱中安全运行 JS 代码

  •  控制 JS 脚本能访问什么模块和对象

  •  分析或执行第三方混淆代码而不污染主环境

  •  配合 node-hook 拦截模块源码,实现更强的 hook 调试

它解决了原生 vm 沙箱“容易逃逸”的问题,是做 JS 逆向非常重要的一环。

安装

npm install vm2

1. 基本用法:安全执行 JS 代码

const { VM } = require('vm2');

const vm = new VM({
  sandbox: {
    console,
    test: 123
  }
});

vm.run(`
  console.log("test =", test);  // 输出:test = 123
`);

可以在 sandbox 中定义允许访问的全局变量。沙箱外的所有变量默认是不可访问的。

2. NodeVM 用于 require 模块

NodeVMvm2 提供的增强型沙箱,支持:

  • require() 外部模块(可配置是否允许)

  • 模拟 Node.js 环境(带模块系统)

  • 可以 hook 模块加载过程(配合 node-hook

示例:

const { NodeVM } = require('vm2');

const vm = new NodeVM({
  console: 'inherit',
  sandbox: {},
  require: {
    external: true,       // 允许加载外部模块
    builtin: ['fs', 'crypto'],
    root: "./",           // 限定模块搜索路径
  }
});

vm.run(`
  const crypto = require('crypto');
  console.log("md5 =", crypto.createHash('md5').update("abc").digest('hex'));
`);

可以用它加载并执行整个 encrypt.js 模块,而不会污染主进程上下文

3. 实战场景:逆向混淆代码(脱离主进程执行)

假设有一段混淆代码:

// encrypt.js
function getToken(a, b) {
  return a.split('').reverse().join('') + b;
}
module.exports = getToken;

NodeVM 来安全加载并调用:

const { NodeVM } = require('vm2');
const path = require('path');

const vm = new NodeVM({
  console: 'inherit',
  sandbox: {},
  require: {
    external: true,
    root: __dirname
  }
});

const getToken = vm.run(`module.exports = require('./encrypt');`, __dirname);
console.log(getToken('abc', '123'));  // 输出:cba123

这个过程中 encrypt.js 是在沙箱中运行的,可以随时对其源码进行 hook 替换。

4. 配合 node-hook 实现模块 hook + 沙箱执行

我们可以在模块加载前插入 log:

const hook = require('node-hook');
const { NodeVM } = require('vm2');

hook.hook('.js', (source, filename) => {
  if (filename.includes('encrypt.js')) {
    return `
      console.log('[HOOK] 调用了 getToken');
      ${source}
    `;
  }
  return source;
});

const vm = new NodeVM({
  console: 'inherit',
  require: {
    external: true,
    root: './'
  }
});

vm.run(`module.exports = require('./encrypt');`, __dirname);

实现:模块还没执行前,就能插入 log 或替换函数。

5. 典型用途(逆向工程)

场景 用法
 运行混淆 JS 模块 把目标模块放进沙箱执行,防止污染主程序
 hook 模块函数逻辑 配合 node-hook 替换敏感函数(如加密函数)
 分析模块间调用关系 在 require 时打印加载路径和依赖结构
 分析 Electron 打包代码 把 Electron 的主进程 JS 文件拿来在沙箱里运行
 跑非信任代码 执行爬虫目标站点提供的 JS 参数生成器而不怕执行恶意代码

6. 实战模板(沙箱执行 + hook + 参数打印)

完整模板:

const hook = require('node-hook');
const { NodeVM } = require('vm2');
const path = require('path');

// 1. hook 加密模块,插入打印
hook.hook('.js', (source, filename) => {
  if (filename.includes('encrypt.js')) {
    return source.replace(
      'function getToken',
      `function getToken(...args) { console.log('[HOOK]', args); `
    );
  }
  return source;
});

// 2. 构建 NodeVM 沙箱
const vm = new NodeVM({
  console: 'inherit',
  sandbox: {},
  require: {
    external: true,
    root: __dirname
  }
});

// 3. 在沙箱中运行加密模块
const encrypt = vm.run(`module.exports = require('./encrypt');`, __dirname);

// 4. 调用函数观察行为
encrypt('abc123', 'XyZ');

输出结果:

[HOOK] [ 'abc123', 'XyZ' ]

总结

能力 vm2
安全执行 JS 可以
沙箱隔离环境 可以
模拟 require 可以(NodeVM)
控制模块权限 可以
hook 模块函数 可以(配合 node-hook)
执行混淆代码 可以
防止逃逸攻击 可以(比原生 vm 更安全)

八、沙箱模拟执行

沙箱模拟执行指的是在一个受限环境中运行 JavaScript 代码,确保它:

  • 不会访问本地文件系统、网络、进程等敏感资源

  • 不会污染全局环境

  • 能被动态 hook、插入打印、调试参数

它是分析第三方加密模块、运行不可信代码时的安全保障。

1. 为何要沙箱模拟执行?

场景 1:混淆 JS 函数逆向

例如遇到某站点使用 encrypt.js 生成签名参数,源代码非常复杂、混淆严重:

function x1(_0xabc123, key) {
  // 复杂逻辑...
}

我们可以:

  • 不理解它所有逻辑

  • 但可以在沙箱中跑它,观察输入输出、hook 内部函数

场景 2:避免执行恶意代码

  • 某些脚本可能访问你的磁盘、发送网络请求、执行 Shell 命令

  • 放进沙箱,阻止这些操作,只分析逻辑结构

2. 如何使用 vm2 搭建沙箱环境

1)基础沙箱:运行字符串代码

const { VM } = require('vm2');

const vm = new VM({
  sandbox: { a: 5, b: 3 }
});

const result = vm.run(`a + b`);
console.log(result);  // 输出:8

此处 a + b 在沙箱中执行,外部无法访问内部变量,反之亦然。

2)NodeVM:运行模块代码(重点)

const { NodeVM } = require('vm2');

const vm = new NodeVM({
  console: 'inherit',
  sandbox: {},
  require: {
    external: true,
    builtin: ['crypto'],
    root: "./",
  }
});

const result = vm.run(`
  module.exports = function(data) {
    const crypto = require('crypto');
    return crypto.createHash('md5').update(data).digest('hex');
  };
`, __dirname);

console.log(result('abc'));  // md5 输出

可以沙箱加载一个文件、函数,像真正模块一样调用。

3. 实战:沙箱模拟执行混淆模块

目标模块:encrypt.js(混淆代码)

// encrypt.js
function genToken(uid) {
  return uid.split('').reverse().join('') + "_abc";
}
module.exports = genToken;

我们不想污染主进程,就放进沙箱里执行:

const { NodeVM } = require('vm2');

const vm = new NodeVM({
  console: 'inherit',
  require: {
    external: true,
    root: __dirname
  }
});

// 沙箱内加载 encrypt 模块
const genToken = vm.run(`module.exports = require('./encrypt');`, __dirname);
console.log(genToken('user123'));  // 输出:321resu_abc

沙箱执行结果和真实一样,但更加安全、可控。

4. 与 node-hook 联合使用

我们可以配合 node-hook 实现动态 hook:

const hook = require('node-hook');
const { NodeVM } = require('vm2');

hook.hook('.js', (source, filename) => {
  if (filename.includes('encrypt.js')) {
    return `
      console.log('[HOOK] genToken 被调用');
      ${source.replace('function genToken', 'function genToken(...args) { console.log("参数:", args);')}
    `;
  }
  return source;
});

const vm = new NodeVM({
  console: 'inherit',
  require: {
    external: true,
    root: __dirname
  }
});

const genToken = vm.run(`module.exports = require('./encrypt');`, __dirname);
genToken('abc123');

输出:

[HOOK] genToken 被调用
参数: [ 'abc123' ]

5. 分析真实加密模块流程

适用于以下逆向任务:

项目 用法
极验、滑动验证码 把 JS 加密代码扔进沙箱执行,hook w 参数生成
Electron 项目 分析 main.js 入口模块的行为
JS加密项目 hook 所有函数、插入 log、打印参数
自动化测试 JS 模块 拿目标模块脱离环境执行验证正确性

6. 额外技巧

设置沙箱 console 为 inherit

console: 'inherit'

可以把沙箱中的 console.log 输出同步到主进程控制台。

限制 require 权限

require: {
  external: true,
  builtin: ['fs', 'crypto'],
  root: __dirname
}

防止恶意代码 require('child_process') 去执行 shell 命令。

捕获异常防止崩溃

try {
  vm.run(code);
} catch (e) {
  console.log("沙箱执行错误:", e.message);
}

总结

能力 是否支持
沙箱运行 JS 代码
安全执行混淆模块
控制 require 模块
配合 hook 修改代码
捕获 console / 参数
分析混淆函数输入输出
防止主进程被污染或攻击

九、electron 项目逆向

Electron 是一个跨平台的桌面应用框架,本质上就是:

Node.js + Chromium + HTML/JS 前端页面

可以理解为一个浏览器壳 + Node.js 脚本。很多主流软件(如微信PC、钉钉、VS Code)都是 Electron 写的。

逆向 Electron 项目目标:

目标 举例
获取打包前 JS 源码 解包后恢复源码进行分析
获取核心逻辑 找到登录加密、通信接口等
绕过客户端验证 破解授权、本地签名校验
做爬虫 / 自动化 拿到核心接口加密流程
安全研究 查找硬编码 token、配置等敏感信息

1. Electron 应用结构

大多数 Electron 应用的目录结构如下(安装目录或 asar 包内):

app/
├── main.js             // 主进程入口(Node.js)
├── preload.js          // 注入渲染进程的桥梁
├── renderer/           // 渲染进程(前端页面)
│   ├── index.html
│   └── app.js
├── node_modules/       // 第三方依赖
├── package.json        // 描述整个 App
└── ...

主进程的 JS 就是我们逆向的重点。

2. 逆向核心步骤

步骤 1:获取资源(解包)

多数 Electron 应用都打包成了 .asar 文件:

app.asar

解包工具:

npm install -g asar

asar extract app.asar output_folder

得到完整源码结构后,我们就可以进行下一步分析。

步骤 2:分析 main.js 主进程

main.js 是 Electron 启动时执行的主文件,类似于 Node.js 的入口。

看点:

  • 创建窗口的逻辑

  • 本地资源加载路径

  • preload 脚本路径

  • 是否有硬编码 token、接口地址、加密参数

const { app, BrowserWindow } = require('electron');

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: false,
      contextIsolation: true
    }
  });

  win.loadURL('https://example.com');
}

步骤 3:分析 preload.js(桥梁代码)

preload.js 是连接主进程和前端 JS 的桥梁,可以注入全局函数到页面中。

常见 hook 点:

contextBridge.exposeInMainWorld('myAPI', {
  encrypt(data) {
    return crypto.encrypt(data);
  }
});

我们可以:

  • hook 这个 API(用 node-hook 替换)

  • 或在页面层分析 window.myAPI.encrypt() 的调用参数

步骤 4:分析渲染层 JS(前端逻辑)

这部分 JS 通常会包含:

  • 页面交互逻辑

  • 登录、注册表单提交

  • 各类参数拼接、加密调用

如果看到这些代码被混淆了:

!function(a){var b=function(c){...}(window);

可以使用工具还原:

  • js-beautify 美化

  • Babel AST 解混淆

  • SourceMap 查源码(如果未移除)

3. 常用技巧与 hook 手段

1)打印主进程参数

// 替换 main.js 内容,加一行打印
console.log("[HOOK] app launched");

app.on('ready', () => {
  console.log("[HOOK] 创建窗口!");
});

2)注入渲染进程 hook(页面层)

preload.js 添加全局监听:

window.addEventListener('DOMContentLoaded', () => {
  console.log('[HOOK] 页面已加载');
});

或注入 API:

window.myAPI.encrypt = function(data) {
  console.log('[HOOK] 加密参数:', data);
  return 'fake_result';
}

4. 实战逆向:提取登录加密参数

很多软件(如 Electron 版聊天软件)在登录时会调用加密函数:

window.encrypt('用户名+密码')  =>  加密串

fetch('https://api.xxx.com/login', {
  method: 'POST',
  body: {
    data: 加密串
  }
});

可以:

  1. 解包 asar

  2. 找到 preload.js 和前端 js

  3. node-hookvm2 sandbox 执行加密模块

  4. 打印调用栈、参数、还原加密算法

5. 辅助工具推荐

工具 用途
asar 解包 Electron 应用
js-beautify 美化混淆代码
vm2 沙箱执行模块
node-hook 动态插桩打印
Chrome DevTools 调试渲染进程页面
electron-devtools-installer 安装调试插件
frida/electron-inject 动态分析运行时数据(高阶)

6. 逆向流程小结

1. 解包 asar 文件,获取源码
2. 分析 main.js 入口逻辑,识别 preload.js
3. 查看 preload 里是否 expose 函数给页面
4. 分析页面 js 中的调用链
5. 还原加密函数(用 vm2 + node-hook)
6. 打印输入输出,实现模拟调用或替换

十、打包 JS 分析

在实际项目中,前端代码一般不会以原始形式暴露,而是经过打包压缩,常见工具有:

工具 作用
Webpack 模块打包(ES6 import/export → bundle)
Rollup 更适合库打包
Vite 现代化打包工具(ESM支持)
esbuild 极快的打包压缩
UglifyJS / Terser 混淆、压缩

目的是:

  • 减小体积

  • 加速加载

  • 防止源码泄露

1、逆向难点

打包后的 JS 文件一般有如下特点:

1)文件名混淆

/assets/abc123.chunk.js
/app.min.js
/main.[hash].js

2)变量名混淆

var a = "xx";
function b(c) {
  return d(c + a);
}

3)函数结构扁平化 / IIFE 包裹

!function(){...}(); // 立即执行函数

4)模块包裹结构(如 Webpack)

(()=> {
  var __webpack_modules__ = {
    123: function(module, exports) { ... }
  };
  __webpack_require__(123);
})();

2. 打包 JS 的分析目标

目标 方法
找到核心函数 搜索关键函数 / 输入参数调用链
恢复源码结构 使用还原工具(AST / sourcemap)
模拟加密流程 提取关键逻辑并重写执行
插桩调试 hook、打印、沙箱执行
解混淆 还原变量名、结构优化

3. 常用工具链

1)美化工具

js-beautify app.min.js -o app.beauty.js

2)AST 工具分析器

  • Babel Parser

  • @babel/traverse

  • @babel/generator

  • acorn, esprima

可以用它来:

  • 还原混淆函数

  • 替换函数名

  • 还原字符串加密

示例:提取所有字符串常量

const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;

const code = `var a = "secret";`;

const ast = parser.parse(code);

traverse(ast, {
  StringLiteral(path) {
    console.log("字符串:", path.node.value);
  }
});

3)source map 还原(若未删除)

如果网站保留了 .map 文件,例如:

https://xxx.com/app.min.js.map

可以:

  • 下载 .map

  • source-map-visualization 还原函数名

4)模块结构识别(如 Webpack)

Webpack 打包后的结构通常是:

(function(modules) {
  // Webpack runtime
  return __webpack_require__(0);
})([ function(module, exports) {
  // 模块 0 的代码
}, function(...) {...} ]);

可以用 webpack-bundle-analyzer 或写脚本提取每个模块:

Object.keys(__webpack_modules__).forEach(k => {
  console.log(k, __webpack_modules__[k].toString());
});

4. 手动逆向打包 JS 的实战流程

假设我们要逆向一个加密函数 gen_w(data),文件是 main.min.js

步骤 1:格式化

js-beautify main.min.js > main.js

步骤 2:搜索函数名或接口关键字

例如搜索:

  • gen_w

  • sign

  • fetch(...)

  • XMLHttpRequest

  • .toString().slice(...)

通过参数依赖、接口调用追踪定位函数。

步骤 3:用 AST 重写或提取

比如找到了:

function gen_w(data) {
  var b = a(data);
  return b + "_xyz";
}

可以使用 @babel/parser 提取 a() 的依赖,再用 vm2 沙箱执行整段代码:

const { VM } = require('vm2');
const fs = require('fs');

const code = fs.readFileSync('./gen_w.js', 'utf8');

const vm = new VM();
const result = vm.run(code + '\ngen_w("abc")');

console.log(result);

步骤 4:hook 并插桩打印关键参数

gen_w = function(data) {
  console.log("hook: 参数 =", data);
  return "fake_w";
}

步骤 5:还原模块名

如 Webpack 中 t(123) 表示调用模块,可以还原模块 ID 与源码对应:

const modules = __webpack_modules__;
Object.entries(modules).forEach(([id, fn]) => {
  console.log("模块 ID:", id);
  console.log(fn.toString());
});

5. 如何快速定位目标函数?

  1. 抓包接口,找到关键参数(如 w、sign、token)

  2. 全局搜索该参数值出现场景(如 body.data = w

  3. 向上追溯调用链,找到定义函数(如 gen_w()

  4. 分析函数依赖链(用 AST、格式化工具)

  5. 沙箱执行还原函数

总结

操作 工具 / 方法
格式化压缩代码 js-beautify, prettier
AST 分析 @babel/parser, traverse
模块还原 自写脚本、webpack runtime 还原
逆向执行 vm2, node-hook, 控制台打印
解混淆 手动替换变量名、字符串还原
找函数 抓包 + 搜索关键字符串或接口
还原源码结构 SourceMap、bundle-analyzer

网站公告

今日签到

点亮在社区的每一天
去签到