逆向工程在Node.js生态里是一个很实际的话题。很多场景都会遇到:对接一个没有文档的内部接口、分析某个前端加密逻辑、还原客户端与服务端之间的通信协议,或者研究一段被混淆过的JavaScript代码。Node.js天然就是JavaScript运行时,配合它内置的net、http、crypto、Buffer等模块,几乎可以覆盖逆向分析从数据采集到逻辑复现的全流程。这篇文章从准备工作、抓包分析、代码还原到脚本复现,把整体思路串一遍。

一、逆向前的准备:环境与分析工具
开始逆向之前,先明确目标:你要还原的是网络协议、加密算法,还是业务逻辑。目标不同,工具链也不同。协议层面需要抓包工具,比如Wireshark、Charles、Fiddler,或者直接用mitmproxy做中间人代理。代码层面则需要浏览器开发者工具、Node.js调试器,以及一些反混淆工具。
Node.js本身在这些环节里能做的事比想象中多。比如目标是一个移动App或桌面客户端,可以写一个Node.js的TCP代理,把流量转发并落盘,这样比通用抓包工具更灵活:
const net = require('net');
// 本地端口 -> 远程服务器的透明代理,同时把数据落盘
const LOCAL_PORT = 8888;
const REMOTE_HOST = '203.0.113.10';
const REMOTE_PORT = 7777;
net.createServer(client => {
const upstream = net.connect(REMOTE_PORT, REMOTE_HOST);
client.on('data', d => {
require('fs').appendFileSync('up.log', d);
upstream.write(d);
});
upstream.on('data', d => {
require('fs').appendFileSync('down.log', d);
client.write(d);
});
client.on('close', () => upstream.destroy());
upstream.on('close', () => client.destroy());
}).listen(LOCAL_PORT, () => console.log('proxy on', LOCAL_PORT));这段代码的核心价值在于:所有原始字节都会被完整记录,你可以用Buffer逐字节分析包头、长度字段、校验位。对于私有二进制协议,这是最直接的分析手段。相比之下,HTTP层面的抓包用现成工具更省事,但一旦遇到TCP长连接或WebSocket,自己写代理往往效率更高。
另外一个容易被忽略的准备工作是确定法律边界。逆向他人软件可能涉及著作权和许可协议问题,分析自己公司的系统、做安全研究、学习协议设计一般没问题,但把逆向成果用于破解或绕过授权就是另一回事了。动手之前先想清楚这一点。
二、定位与还原加密逻辑
抓到数据之后,下一步是搞清楚内容为什么看不懂。常见的情况有三种:Base64或Hex编码、对称加密(AES、DES)、非对称加密或签名(RSA、HMAC)。编码不是加密,Base64解码后往往就是明文,先排除这种简单情况:
const b = Buffer.from('5L2g5aW977yM546w5ZG9', 'base64');
console.log(b.toString('utf8')); // 输出解码后的明文如果解码后仍是乱码,大概率是加密。这时候要去找加密的实现代码。如果目标是网页,打开开发者工具,在Network面板找到可疑请求,用Initiator回溯调用栈,通常能定位到加密函数。关键字搜索也很有效:CryptoJS、AES、encrypt、secret、iv这些词出现的地方基本就是核心逻辑所在。
如果代码被混淆过(变量名变成十六进制、控制流被打平),不要硬读。有两个策略:一是动态调试,在加密函数入口打断点,直接观察输入输出;二是把混淆代码抠出来放到Node.js里单独跑。抠代码时注意补环境,浏览器环境下的代码直接搬进Node.js会报window is not defined之类的错误,可以手动补一个假的全局对象:
// 最简单的环境补齐
global.window = global;
global.navigator = { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' };
global.document = { cookie: '', createElement: () => ({ getContext: () => null }) };
require('./obfuscated-code.js');还原出算法之后,用Node.js的crypto模块验证。比如确认目标是AES-CBC加密,就用相同密钥和IV尝试解密抓包数据,能还原出可读的JSON就说明分析正确:
const crypto = require('crypto');
function aesDecrypt(cipherBase64, key, iv) {
const decipher = crypto.createDecipheriv('aes-128-cbc',
Buffer.from(key, 'utf8'), Buffer.from(iv, 'utf8'));
let out = decipher.update(Buffer.from(cipherBase64, 'base64'));
out = Buffer.concat([out, decipher.final()]);
return out.toString('utf8');
}
console.log(aesDecrypt(data, '1234567890abcdef', '0102030405060708'));验证通过后,建议把还原的逻辑封装成独立模块,加上单元测试,固定几组抓包样本作为测试用例。这样目标方一旦改了算法,跑一遍测试就能立刻发现。
三、用Node.js完整复现目标接口
算法和协议都搞清楚之后,最后一步是复现。用Node.js写一个客户端脚本,按协议顺序构造请求、处理响应。以一个带签名的HTTP接口为例,典型流程是:拼参数、按字典序排序、拼接密钥、计算摘要、带上签名发请求:
const crypto = require('crypto');
function sign(params, secret) {
const sorted = Object.keys(params).sort()
.map(k => `${k}=${params[k]}`).join('&');
return crypto.createHmac('sha256', secret)
.update(sorted).digest('hex');
}
async function callApi(params, secret) {
const body = { ...params, sign: sign(params, secret), ts: Date.now() };
const res = await fetch('https://api.ipipp.com/v1/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return res.json();
}工程化方面有几个建议。第一,把协议细节和业务代码分层,协议编解码放在独立模块里,业务层只调用接口。第二,处理好时序问题,有些协议有握手、心跳、序号递增的要求,复现时必须严格模拟,否则服务端会断开连接。第三,做好日志,把每次收发的原始数据都记录下来,出现行为不一致时方便对比排查。
还要提防反调试手段。有些客户端会检测代理、校验证书、或者在代码里插入无限断点。遇到这种情况,可以通过Frida等框架Hook关键函数,拿到加密前后的明文对照,这属于更深入的动态分析范畴,但思路是一致的:定位关键函数、观察输入输出、推导逻辑。
四、总结
Node.js做逆向工程的优势可以归纳为三点:和前端同语言,抠出来的代码可以直接跑;网络与字节处理能力强,二进制协议分析顺手;异步模型天然适合模拟长连接通信。整个流程的套路也比较固定:抓数据、定位算法、还原验证、脚本复现。真正难的不是工具使用,而是面对混淆代码和未知协议时的耐心和推理能力。把每一步的证据都记录下来,逐步缩小范围,大多数看似复杂的加密逻辑最终都能拆解成几行标准库调用。最后再强调一次,逆向技术本身是中性的,用在接口对接、安全研究和学习上,它就是提升效率的利器。
Node.js逆向工程抓包分析协议还原修改时间:2026-09-14 06:04:52