关于 js:9. Node.js 后端相关
一、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:
步骤解析:
-
加载模块
加载内置模块(如fs
、crypto
)和你写的文件或库(require()
) -
构造运行环境
创建一个伪装的浏览器环境,提供global
、process
、console
、setTimeout
等函数 -
使用 V8 编译代码为机器码
Node 使用 V8 引擎将 JS 代码编译为机器码并执行(所以跑得飞快) -
执行主线程代码
运行myscript.js
中的同步代码 -
进入事件循环
处理异步任务(如setTimeout
,http 请求
,文件读写
等)
2)Node 与浏览器环境的不同
功能 | Node.js | 浏览器 |
---|---|---|
全局对象 | global | window |
DOM 操作 | 无 | 有 |
网络请求 | http 、https 模块 | fetch , XHR |
加密模块 | crypto (强大) | 受限 |
沙箱执行 | vm 、vm2 | 无 |
所以在逆向时,如果把网页 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()
、os
、shutil
的事。
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 模块
NodeVM
是 vm2
提供的增强型沙箱,支持:
-
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 asarasar 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: 加密串}
});
可以:
-
解包 asar
-
找到
preload.js
和前端 js -
用
node-hook
或vm2
sandbox 执行加密模块 -
打印调用栈、参数、还原加密算法
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
-
prettier
-
de4js
(可视化)
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 runtimereturn __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. 如何快速定位目标函数?
-
抓包接口,找到关键参数(如 w、sign、token)
-
全局搜索该参数值出现场景(如
body.data = w
) -
向上追溯调用链,找到定义函数(如
gen_w()
) -
分析函数依赖链(用 AST、格式化工具)
-
沙箱执行还原函数
总结
操作 | 工具 / 方法 |
---|---|
格式化压缩代码 | js-beautify , prettier |
AST 分析 | @babel/parser , traverse |
模块还原 | 自写脚本、webpack runtime 还原 |
逆向执行 | vm2 , node-hook , 控制台打印 |
解混淆 | 手动替换变量名、字符串还原 |
找函数 | 抓包 + 搜索关键字符串或接口 |
还原源码结构 | SourceMap、bundle-analyzer |