1. Web JS逆向工程全体系解析
作为一名长期从事Web安全研究的从业者,我经常需要分析前端JavaScript代码中的加密逻辑。Web JS逆向工程(Web JavaScript Reverse Engineering)本质上是对前端网页JS代码的反向分析过程。现代网站的接口加密、参数签名和反爬机制大多在前端JS中实现,我们需要通过系统化的调试和分析方法,还原这些加密参数的生成规则。
1.1 技术原理与价值
前端加密的核心特点是:所有加密逻辑最终都要在浏览器中执行。这意味着无论代码如何混淆加密,只要它能在浏览器运行,就必然能被分析和还原。这个特性为我们进行逆向分析提供了可能性。
在实际工作中,JS逆向主要应用于以下几个场景:
- 爬虫开发:破解网站的sign、token等加密参数,实现自动化数据采集
- 安全测试:分析前端加密逻辑,寻找可能的越权或未授权访问漏洞
- 接口对接:当第三方网站没有开放API时,通过逆向实现合法调用
- 反爬对抗:绕过网站的环境检测、反调试等保护机制
重要提示:所有逆向分析行为必须遵守法律法规,仅在获得明确授权的情况下进行。未经授权的逆向分析可能涉及法律风险。
1.2 技术体系组成
完整的Web JS逆向技术体系包含以下几个核心组成部分:
- 浏览器调试工具:Chrome DevTools是核心武器,特别是Network、Sources和Console面板
- 加密参数定位:XHR断点、Hook注入等关键技术
- 代码混淆分析:处理常见的压缩和混淆代码
- 加密算法识别:Base64、MD5、AES、RSA等常见算法
- 环境模拟:补全Node.js中缺失的浏览器环境
- 反调试对抗:绕过网站的反调试保护机制
2. 浏览器调试工具深度解析
2.1 Network面板实战技巧
Network面板是逆向分析的第一步,也是最重要的入口点。它记录了网页所有的网络请求,包括XHR接口、JS文件、图片等资源。
2.1.1 关键配置项
在实际使用中,有两个配置项必须开启:
- Preserve log:保留请求日志,防止页面刷新后丢失关键信息
- Disable cache:禁用缓存,确保每次都能获取最新代码
javascript复制// 示例:通过命令行开启这些配置(实际在DevTools界面操作更直观)
chrome.devtools.network.onRequestFinished.addListener(request => {
console.log(request.request.url);
});
2.1.2 请求分析要点
分析接口请求时需要特别关注以下几个部分:
- Headers:检查Authorization、Cookie等认证信息
- Payload:查看POST请求的加密参数
- Initiator:追踪请求的发起源,定位加密函数入口
- Response:分析返回数据的结构和加密情况
2.2 Sources面板高级用法
Sources面板是我们调试JS代码的主战场,掌握它的高级功能可以极大提升逆向效率。
2.2.1 断点类型与应用场景
| 断点类型 | 触发条件 | 适用场景 |
|---|---|---|
| 行断点 | 执行到指定行 | 精确调试已知代码位置 |
| 条件断点 | 表达式为true时 | 只在特定条件下中断 |
| XHR断点 | 请求URL包含关键词 | 定位接口加密入口 |
| 事件断点 | 特定事件触发时 | 分析交互逻辑 |
2.2.2 调试控制快捷键
- F8:继续执行
- F10:单步跳过
- F11:单步进入
- Shift+F11:单步跳出
专业技巧:使用Blackbox Script功能屏蔽第三方库代码,让调试更聚焦于业务逻辑。
2.3 Console面板的创造性用法
Console不仅是输出日志的地方,更是我们与网页JS环境交互的桥梁。
2.3.1 常用调试命令
javascript复制// 调用网站加密函数进行测试
const testSign = window.generateSign("test", Date.now());
console.dir(testSign);
// 查看函数定义
console.dir(window.encryptData);
// 监控对象属性变化
const target = {};
console.log("初始对象:", target);
new Proxy(target, {
set(obj, prop, value) {
console.log(`属性${prop}被修改为:`, value);
return Reflect.set(...arguments);
}
});
3. 加密参数定位核心技术
3.1 XHR断点定位法
XHR断点是定位加密函数最有效的方法之一,它不需要预先知道函数名或关键词。
3.1.1 操作流程
- 在Sources面板找到"XHR/fetch Breakpoints"
- 添加接口URL关键词(如"/api/login")
- 触发接口请求,断点会自动命中
- 通过Call Stack回溯调用链,找到业务代码
3.1.2 实战案例
假设我们要分析一个登录接口的加密过程:
- 设置XHR断点关键词"login"
- 点击登录按钮触发断点
- 在Call Stack中从下往上查找,跳过jQuery、axios等框架代码
- 找到业务层代码,通常包含参数处理逻辑
javascript复制// 典型的加密参数生成流程
function buildRequest(payload) {
const timestamp = Date.now();
const nonce = generateNonce();
const sign = generateSign(payload, timestamp, nonce); // 在这里打断点
return {
...payload,
timestamp,
nonce,
sign
};
}
3.2 Hook注入技术
Hook技术可以拦截和修改原有的函数行为,是逆向分析的利器。
3.2.1 基础Hook模板
javascript复制// 通用Hook函数模板
function hookFunction(target, name, callback) {
const original = target[name];
target[name] = function(...args) {
const result = original.apply(this, args);
callback(args, result, this);
return result;
};
return () => { target[name] = original; }; // 返回恢复函数
}
// 示例:Hook JSON.parse
hookFunction(window, 'JSON.parse', (args, result) => {
console.log('JSON.parse被调用,输入:', args[0], '输出:', result);
});
3.2.2 常用Hook点
- 加密相关:CryptoJS、window.crypto.subtle
- 网络请求:XMLHttpRequest.prototype.send、window.fetch
- 编码解码:window.btoa、window.atob
- 存储相关:localStorage.getItem、sessionStorage.setItem
4. 代码混淆与反混淆实战
4.1 常见混淆技术分析
现代前端代码混淆主要采用以下几种技术:
- 标识符混淆:将有意义变量名改为短名称或无意义字符串
- 控制流扁平化:将顺序执行的代码改为switch-case结构
- 字符串加密:将字符串常量加密存储,使用时解密
- 死代码注入:插入永远不会执行的冗余代码
- 反调试陷阱:添加debugger语句或检测调试环境
4.2 AST反混淆技术
AST(抽象语法树)反混淆是目前最有效的反混淆方法。
4.2.1 反混淆流程
- 使用@babel/parser将代码解析为AST
- 遍历AST节点,识别混淆模式
- 应用相应的反混淆转换
- 使用@babel/generator将AST重新生成代码
javascript复制// 示例:使用Babel处理字符串数组混淆
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generator = require('@babel/generator').default;
const code = `var _0x1a2b=['MD5','toString','salt'];...`;
const ast = parser.parse(code);
traverse(ast, {
VariableDeclarator(path) {
if (t.isArrayExpression(path.node.init)) {
// 处理字符串数组
}
}
});
5. 常见加密算法识别与处理
5.1 算法特征识别表
| 算法类型 | 特征 | 常见应用场景 |
|---|---|---|
| Base64 | 结尾常有=,字符集A-Za-z0-9+/ | 参数编码、图片传输 |
| MD5 | 32位十六进制字符串 | 接口签名、密码存储 |
| AES | 块大小128bit,常用CBC模式 | 接口数据加密 |
| RSA | 公钥以BEGIN PUBLIC KEY开头 | 登录密码加密 |
5.2 算法处理示例
5.2.1 MD5签名还原
javascript复制// 典型的MD5签名生成逻辑
function generateSign(params, secret) {
const keys = Object.keys(params).sort();
const str = keys.map(k => `${k}=${params[k]}`).join('&') + secret;
return CryptoJS.MD5(str).toString();
}
// Python复现
import hashlib
def generate_sign(params, secret):
keys = sorted(params.keys())
s = '&'.join(f'{k}={params[k]}' for k in keys) + secret
return hashlib.md5(s.encode()).hexdigest()
5.2.2 AES解密处理
javascript复制// 前端AES解密示例
function decryptData(encrypted, key, iv) {
const keyBytes = CryptoJS.enc.Utf8.parse(key);
const ivBytes = CryptoJS.enc.Utf8.parse(iv);
const decrypted = CryptoJS.AES.decrypt(
encrypted,
keyBytes,
{ iv: ivBytes, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
);
return decrypted.toString(CryptoJS.enc.Utf8);
}
6. 浏览器环境模拟技术
6.1 环境差异对比表
| 环境特征 | 浏览器环境 | Node.js环境 |
|---|---|---|
| 全局对象 | window | global |
| DOM API | 完整支持 | 不支持 |
| 网络请求 | XMLHttpRequest | http/https模块 |
| 用户代理 | 真实浏览器UA | Node.js版本信息 |
6.2 环境补全方案
6.2.1 轻量级补全方案
javascript复制// 基础环境补全
global.window = global;
global.navigator = {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
};
global.document = {
createElement: () => ({})
};
6.2.2 使用jsdom完整模拟
javascript复制const { JSDOM } = require('jsdom');
const dom = new JSDOM('<!DOCTYPE html>', {
url: 'https://target.site',
runScripts: 'dangerously'
});
global.window = dom.window;
global.document = dom.window.document;
// 其他需要补全的对象...
7. 反调试技术对抗
7.1 常见反调试手段
- 无限debugger:通过定时器不断触发debugger语句
- DevTools检测:检测窗口大小、执行时间等特征
- 断点检测:通过异常处理检测是否设置了断点
- 性能检测:检测代码执行速度是否异常
7.2 反反调试技巧
7.2.1 无限debugger绕过
javascript复制// 方法1:条件断点绕过
// 在debugger语句行设置条件断点,条件设为false
// 方法2:Hook Function构造函数
const originalFunction = Function;
Function = function(...args) {
if (args[0].includes('debugger')) {
return function(){};
}
return originalFunction(...args);
};
7.2.2 DevTools检测绕过
javascript复制// 绕过webdriver检测
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
configurable: true
});
// 绕过chrome检测
window.chrome = {
runtime: {},
loadTimes: () => {},
csi: () => {}
};
8. 完整实战案例:某电商平台价格接口逆向
8.1 分析流程
- 接口定位:通过Network面板找到价格查询接口
- 参数分析:发现sign、timestamp等加密参数
- 加密定位:使用XHR断点找到签名生成函数
- 逻辑还原:分析出sign=MD5(商品ID+时间戳+密钥)
- Python复现:用requests库实现自动化查询
8.2 核心代码实现
python复制import hashlib
import time
import requests
def generate_sign(product_id, secret_key):
timestamp = str(int(time.time() * 1000))
raw_str = f"{product_id}{timestamp}{secret_key}"
return hashlib.md5(raw_str.encode()).hexdigest()
def query_price(product_id):
url = "https://api.ecommerce.com/price"
secret_key = "x12#9@kP" # 逆向获得的密钥
sign = generate_sign(product_id, secret_key)
params = {
"product_id": product_id,
"timestamp": str(int(time.time() * 1000)),
"sign": sign
}
response = requests.get(url, params=params)
return response.json()
9. 高级技巧:RPC远程调用方案
当加密逻辑过于复杂或经常变动时,可以考虑RPC方案。
9.1 实现原理
- 在浏览器中注入RPC服务脚本
- 通过WebSocket或HTTP接口暴露加密函数
- Python程序远程调用这些函数获取加密结果
9.2 油猴脚本示例
javascript复制// ==UserScript==
// @name RPC Service
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Provide encryption RPC service
// @match https://target.site/*
// @grant none
// ==/UserScript==
(function() {
const ws = new WebSocket('ws://localhost:8080/rpc');
ws.onmessage = async (event) => {
const {id, method, params} = JSON.parse(event.data);
try {
const result = await window[method](...params);
ws.send(JSON.stringify({id, result}));
} catch (error) {
ws.send(JSON.stringify({id, error: error.message}));
}
};
})();
10. 安全与法律合规
10.1 合规要求
- 授权明确:必须获得系统所有者的书面授权
- 范围限定:严格控制在授权范围内测试
- 数据保护:不保存、不泄露测试中接触的敏感数据
- 影响最小:避免使用可能造成服务中断的测试方法
10.2 最佳实践
- 使用专门的测试环境而非生产环境
- 测试前进行完整的数据备份
- 测试后清理所有测试数据和痕迹
- 编写详细的测试报告记录所有操作
11. 工具链推荐
11.1 浏览器工具
- Chrome DevTools:核心调试工具
- Firefox Developer Edition:提供不同的调试视角
- Postman:接口测试与调试
11.2 代码分析工具
- AST Explorer:在线AST分析工具
- Fiddler:网络抓包分析
- IDA Pro:高级反编译工具(用于WASM分析)
11.3 开发环境
- Node.js:运行和测试JS代码
- Python + Requests:实现爬虫逻辑
- Jupyter Notebook:分析记录工具
12. 持续学习建议
Web JS逆向是一个需要持续学习的领域,建议:
- 定期研究主流网站的反爬机制演变
- 关注WebAssembly等新技术的安全影响
- 参与安全社区讨论和知识分享
- 通过CTF比赛提升实战能力
在实际工作中,我总结出一个重要经验:逆向分析不是目的,而是手段。真正的价值在于通过分析理解系统工作原理,进而构建更安全的应用程序。每次成功的逆向分析都应该带来防御思路的升级,这才是安全工程师的职业追求。