拼多多 anti-content 参数逆向实战:Webpack 模块加载器与代理监控 原文标题:拼多多anti-content参数逆向实战:Webpack模块加载器与代理监控 - 道满PythonAI
内容概要总结
一篇以拼多多移动端网页(mobile.pinduoduo.com)为例的 anti-content 签名逆向实战笔记。anti-content 是拼多多网页/小程序端数据接口的“准入门票”,融合时间戳、浏览器/设备指纹、操作轨迹特征、动态加密盐,每次请求重新生成,且经过高度混淆与 Webpack 模块化封装并检测真实浏览器环境。文章指出四大难点(动态混淆更新、模块化封装、浏览器指纹检测、加密链路嵌套),给出完整方法论:先用 Chrome 抓包定位携带 anti_content 的 XHR/Fetch 请求;再通过全局搜索 anti_content、断点调用栈回溯锁定模块;关键突破口是加载器把 __pdd_require__ 暴露到全局 window.rrr。文中提供一套 JS Proxy 代理监控方案,为 window/navigator/screen/location/document/canvas/WebGLRenderingContext/performance 等敏感对象加 get/set 监听以发现缺失的环境属性,并附基础环境补全代码、模块加载器结构与常见报错的排查方法。结论建议:优先用 Playwright/Puppeteer 注入补丁,离线复刻需处理 canvas/WebGL 随机性,并持续跟踪核心 JS 更新。
原文内容(原文即中文)
实战网址:https://mobile.pinduoduo.com/
#实战目标
快速定位生成 anti-content 签名的核心 JS 模块,厘清其环境依赖,并输出一套可运行的代理监控环境补全方案,为签名复刻或自动化获取打下坚实基础。
#概述
拼多多的 anti-content 是网页/小程序端数据接口的“准入门票”。它融合了时间戳、浏览器/设备指纹、轻量级操作轨迹特征、动态加密盐等多维度信息,每次请求都会重新生成。其核心代码经过高度混淆、压缩与模块化封装,并且会检测是否运行在真实浏览器环境中。本文将以移动端网页为例,带你一步步拆解它的生成逻辑。
#一、网页调试与抓包分析
#1.1 接口抓包
打开 Chrome 开发者工具的 Network 面板,勾选 Preserve log,刷新页面或在拼多多首页点击商品分类,找到携带业务数据的 XHR/Fetch 请求(例如 goods_search、goods_detail)。在请求参数或 Query 中,就能看到 anti_content 字段。
#1.2 关键调试截图集
下面汇总了逆向全流程中使用的核心断点、调用栈及代码片段截图,点击可放大查看。












#二、核心技术难点
动态混淆与更新
核心 JS 代码会定期更换混淆规则,静态分析的复用性极为有限。
模块化封装
所有逻辑被打包进 Webpack 风格的自执行函数,没有直接暴露的全局方法,需要先突破模块加载器。
浏览器指纹检测
脚本会检测 window.navigator、canvas、WebGL 等近百个环境属性,任何一个异常都可能触发拦截。
加密链路嵌套
anti-content 内部可能组合了 Base64、SHA-1/SHA-256、自定义压缩等多种算法,追踪难度大。
#三、环境补全与代理监控
为了在补全浏览器环境的同时追踪所有环境属性的访问/修改,我们设计了一套轻量级的 JS 代理监控方案:先补全基础对象,再用 Proxy 监听对全局对象及 DOM/BOM 敏感对象的操作。
#3.1 基础环境补全(极简版)
// 先创建基础全局对象,防止第一帧报错
this.window = this;
this.navigator = {
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
platform: "iPhone",
language: "zh-CN",
languages: ["zh-CN", "zh", "en"],
// 后续可通过监控补全
};
this.document = {
// 可补全基础DOM元素,但拼多多检测较少
};
this.canvas = {
// 同样后续补全
};
#3.2 通用代理监控函数
下面这个函数可以为任意全局对象加上“监控层”,在所有 get / set 操作时输出详细日志,帮助我们快速发现缺失的环境属性。
/**
* 给指定对象数组添加 get/set 代理,打印所有访问/修改操作
* @param {Array<string>} proxyObjArr 要代理的全局对象名数组
*/
function setProxy(proxyObjArr) {
for (const objName of proxyObjArr) {
let targetObj;
try {
targetObj = this[objName];
} catch (e) {
targetObj = {};
}
const handler = {
get(target, property, receiver) {
console.log(
`🔍 GET | 对象: ${objName} | 属性: ${String(property)} | 类型: ${typeof property}`
);
const value = target[property];
console.log(` ↳ 返回值: ${value} | 类型: ${typeof value}\n`);
return value;
},
set(target, property, value, receiver) {
console.log(
`✏️ SET | 对象: ${objName} | 属性: ${String(property)} | 新值: ${value} | 类型: ${typeof value}\n`
);
return Reflect.set(target, property, value, receiver);
},
};
this[objName] = new Proxy(targetObj, handler);
}
}
#3.3 监控敏感对象配置
const monitorTargets = [
"window",
"navigator",
"screen",
"location",
"document",
"canvas",
"WebGLRenderingContext",
"performance",
];
setProxy(monitorTargets);
提示:运行时先注入监控代码,再加载业务 JS,控制台会打印所有被访问和修改的环境属性。依据这些日志,就能有条不紊地完成“补环境”。
#四、代码分析与逆向思路
#4.1 模块加载器拆解
从截图中可以看出,拼多多使用的是简化版 Webpack 5 自执行模块加载器,结构如下:
!function(modules) {
// 1. 模块缓存
var moduleCache = {};
// 2. 核心加载函数
function __pdd_require__(moduleId) {
// 命中缓存直接返回
if (moduleCache[moduleId]) return moduleCache[moduleId].exports;
// 未命中则初始化模块
var newModule = moduleCache[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// 执行模块代码
modules[moduleId].call(newModule.exports, newModule, newModule.exports, __pdd_require__);
newModule.l = true;
return newModule.exports;
}
// 3. 暴露工具和模块数组
__pdd_require__.m = modules;
__pdd_require__.c = moduleCache;
// ... 省略其他 Webpack 内置工具
__pdd_require__.p = ""; // 公共资源路径
// 4. 暴露到全局,方便调试!
window.rrr = __pdd_require__;
}(/* 这里是混淆压缩后的模块数组 */);
关键点:加载器通过 window.rrr 暴露到了全局,这是后续快速定位模块的核心入口。
#4.2 定位核心 anti-content 生成模块
#步骤1:全局搜索入口
在开发者工具 Sources 面板的全局搜索框输入 anti_content 或 anti-content,找到给接口参数赋值的位置。
#步骤2:调用栈回溯
在赋值处打断点,刷新页面触发请求,查看 Call Stack 面板,找到调用链上层最接近混淆模块的位置。通常会看到类似 (new window.rrr(xxx))().messagePack() 的调用。
#步骤3:测试模块有效性
在控制台输入 window.rrr(找到的模块ID),如果能返回一个构造函数,再依次调用构造函数和 messagePack(),并得到合法的 anti-content 或一段加密字符串,就说明找对了模块。
#五、常见问题解决
#5.1 模块调用报错「x is not a function/undefined」
原因:核心模块依赖其他混淆模块,单独运行时这些依赖未被加载。
解决方法:
- 在真实浏览器的控制台,先运行抓包到的完整加载器代码,再调用核心模块;
- 或者通过 pdd_require.m 遍历所有模块,手动补全依赖链。
#5.2 环境检测拦截请求
原因:补全的环境属性数量、顺序、值的类型/范围与真实浏览器不符。
解决方法:
- 先运行我们写好的代理监控代码,在真实浏览器完整触发一次接口请求;
- 对照记录逐个补全环境对象的属性。特别要注意属性的 getter/setter 逻辑以及值的随机性(例如 canvas.toDataURL() 每次生成的 base64 数据都不相同)。
#六、总结与建议
本次逆向充分利用了 拼多多模块加载器暴露到全局 这一突破口,结合 XHR 断点 + 调用栈回溯 快速锁定了核心模块,并借助 Proxy 代理监控 完整追踪了环境依赖。后续如果希望稳定获取 anti-content 签名,建议从以下方向继续深入:
- 优先使用自动化浏览器工具(Playwright / Puppeteer)注入补丁代码,降低环境模拟的复杂度;
- 若必须离线复刻算法,一定要重点处理 canvas、WebGL 等指纹的随机性问题;
- 关注核心 JS 文件的更新频率,定期重新定位模块 ID 与加密逻辑;
- 利用本文提供的代理监控方案,持续完善环境属性,直至完全消除环境检测报错。