1. 为什么需要逆向webpack打包的JS代码
在当今的Web开发领域,webpack已经成为前端工程化的事实标准工具。根据2023年的统计数据,超过78%的中大型前端项目使用webpack作为打包工具。这种普及也带来了一个直接影响——越来越多的网站将核心业务逻辑和反爬机制通过webpack打包到JS文件中。
我最近处理的一个电商平台案例中,商品价格数据被webpack打包后的JS文件动态加载,传统的爬虫根本无法直接获取。这就是为什么我们需要掌握webpack逆向技术——它已经成为现代爬虫工程师的必备技能。
webpack打包的JS文件通常具有以下特征:
- 模块化的代码结构,通过__webpack_require__函数加载模块
- 混淆后的变量名和函数名(如a、b、c等单字母命名)
- 将多个源文件合并为一个bundle文件
- 包含webpack运行时环境和模块管理代码
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 快速识别webpack打包的JS文件
2.1 关键特征识别法
在Chrome开发者工具中打开目标网站的Sources面板,查找主要的JS文件。webpack打包的文件通常包含以下明显特征:
javascript复制// 典型webpack模块定义
!(function(e) {
function t(r) {
if (n[r]) return n[r].exports;
// ...其他webpack特有代码
}
// ...模块定义
})([
/* 模块数组 */
]);
另一个明显标志是搜索__webpack_require__函数,这是webpack模块系统的核心加载器。
2.2 模块映射分析技巧
webpack打包的文件通常会包含一个模块映射表,可以通过以下方式定位:
- 在开发者工具中搜索
[(左方括号),查找模块数组定义 - 查找包含大量函数定义的代码段
- 注意
id和exports等关键词
我常用的一个技巧是在控制台输入:
javascript复制Object.keys(__webpack_modules__)
这可以列出所有模块ID(如果webpack运行时环境可用)。
3. webpack模块系统逆向实战
3.1 定位目标模块
假设我们要获取某电商网站的价格加密逻辑,通常需要:
- 在商品页面触发价格加载
- 在Network面板查找XHR请求
- 追踪请求发起位置的调用栈
最近一个项目中,我发现价格加密逻辑被放在模块387中,通过以下调用链执行:
code复制ajax回调 -> 模块542 -> 模块387
3.2 提取关键模块代码
找到目标模块后,可以将其代码提取出来单独分析。我推荐的做法是:
- 在Sources面板找到模块定义
- 右键点击"Save as"保存到本地
- 使用AST解析工具(如babel-parser)进行分析
一个典型的模块结构如下:
javascript复制/* 模块387 */
function(e, t, n) {
"use strict";
n.r(t);
var r = n(12); // 依赖其他模块
function o(e) {
return r.a.createHash("md5").update(e).digest("hex");
}
t.default = o; // 导出加密函数
}
3.3 重构模块执行环境
要让提取的模块代码能够运行,需要模拟webpack的执行环境。我通常创建一个这样的环境:
javascript复制const fakeWebpackEnv = {
__webpack_modules__: {},
__webpack_require__: function(id) {
if (!this.__webpack_modules__[id]) {
throw new Error(`Module ${id} not found`);
}
return this.__webpack_modules__[id];
}
};
// 注册目标模块
fakeWebpackEnv.__webpack_modules__[387] = function(module, exports, require) {
// 粘贴模块代码
};
// 执行模块
const result = {};
fakeWebpackEnv.__webpack_modules__[387]({}, result, fakeWebpackEnv.__webpack_require__);
console.log(result.default("test_input"));
4. 常见问题与解决方案
4.1 模块依赖缺失问题
在实际操作中,经常会遇到目标模块依赖其他模块的情况。我的处理流程是:
- 分析模块函数的参数列表(通常是require, exports, module)
- 查找n(12)这样的调用,数字代表依赖模块ID
- 递归提取所有依赖模块
提示:不必完整还原所有依赖,只需实现关键函数即可。例如加密模块可能依赖Buffer实现,可以直接用Node.js原生Buffer替代。
4.2 作用域问题处理技巧
webpack打包的代码经常使用IIFE(立即执行函数)创建闭包作用域。解决方法:
- 将整个模块代码包裹在函数中
- 使用eval执行代码(谨慎使用)
- 通过AST工具重构代码结构
我最近处理的一个案例中,使用了如下方案:
javascript复制const vm = require('vm');
const context = { console };
vm.runInNewContext(webpackCode, context);
4.3 动态加载模块处理
对于使用import()动态加载的模块,需要:
- 在Network面板查找额外的chunk文件
- 分析webpackJsonp回调函数
- 合并多个chunk的模块定义
一个实际的代码片段示例:
javascript复制window["webpackJsonp"] = window["webpackJsonp"] || [];
window["webpackJsonp"].push([[1], {
// 模块定义
}]);
5. 高级逆向技巧
5.1 AST抽象语法树分析
对于高度混淆的代码,建议使用AST工具进行分析:
- 安装babel相关工具
bash复制npm install @babel/parser @babel/traverse @babel/generator
- 解析和转换代码
javascript复制const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generator = require('@babel/generator').default;
const ast = parser.parse(code);
traverse(ast, {
// 自定义访问器
});
const newCode = generator(ast).code;
5.2 断点调试技巧
在Chrome DevTools中设置条件断点非常有用:
- 在关键函数处设置断点
- 右键点击断点选择"Edit breakpoint"
- 输入条件如
arguments[0] === "price"
5.3 请求参数逆向
处理加密参数时,我的标准流程是:
- 拦截原始请求
- 回溯参数生成过程
- 定位加密函数
- 提取关键算法
例如某网站的签名生成:
javascript复制function generateSign(params) {
const keys = Object.keys(params).sort();
let str = '';
keys.forEach(k => {
str += `${k}=${params[k]}&`;
});
str += 'secret_key=xxxx';
return md5(str);
}
6. 实战案例:某电商平台价格解密
最近我逆向的一个真实案例,主要流程如下:
- 发现商品价格通过AJAX加载
- 响应数据中的price字段是加密字符串
- 通过调用栈找到解密函数位于webpack模块中
- 提取解密模块代码
- 模拟执行环境
关键解密代码:
javascript复制function decryptPrice(encrypted) {
const key = CryptoJS.enc.Utf8.parse("1234567890000000");
const iv = CryptoJS.enc.Utf8.parse("1234567890000000");
const decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decrypted.toString(CryptoJS.enc.Utf8);
}
重构后的Node.js版本:
javascript复制const CryptoJS = require("crypto-js");
function decrypt(encrypted) {
const key = CryptoJS.enc.Utf8.parse("1234567890000000");
const iv = CryptoJS.enc.Utf8.parse("1234567890000000");
const decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decrypted.toString(CryptoJS.enc.Utf8);
}
7. 工具与资源推荐
7.1 常用工具列表
| 工具名称 | 用途 | 备注 |
|---|---|---|
| Chrome DevTools | 代码调试与分析 | 必备 |
| Fiddler/Charles | 网络请求分析 | 可选 |
| Babel AST | 代码解析转换 | 高级分析 |
| Webpack-unpack | webpack解包工具 | 社区工具 |
| Node.js vm模块 | 安全执行环境 | 替代eval |
7.2 学习资源
- Webpack官方文档(了解打包原理)
- Babel AST手册(学习语法树分析)
- 《JavaScript高级程序设计》(夯实基础)
- 各大技术社区逆向案例分享
我在实际工作中发现,最好的学习方式就是动手实践。建议从简单的网站开始,逐步挑战更复杂的逆向目标。每次成功逆向后,记录下关键步骤和心得,这会形成宝贵的经验积累。
