拼多多电商数据 anti-content 参数逆向实战

实战网址https://mobile.pinduoduo.com/

实战目标

快速定位生成 anti-content 签名的核心 JS 模块,了解其环境依赖,给出可运行的代理监控环境补全方案,为后续签名复刻或自动化获取打下基础。

概述

拼多多的 anti-content 是网页/小程序端数据接口的「准入门票」——它包含时间戳、浏览器/设备指纹、轻量级操作轨迹特征、动态加密盐等多维度信息,每次请求都会重新生成,且代码经过高度混淆、压缩与模块化封装,还会检测是否运行在真实浏览器环境中。


网页与调试分析

1.1 接口抓包

打开 Chrome 开发者工具的「Network」面板,勾选「Preserve log」,刷新或在拼多多首页点击商品分类,找到带业务数据的 XHR/Fetch 请求(比如 goods_searchgoods_detail),在请求参数/Query 里就能看到 anti_content

1.2 关键调试截图集

(下方为逆向全流程用到的核心断点、调用栈、代码片段截图,点击可放大查看)

点击展开截图

接口抓包 全局搜索关键词 XHR 断点触发 调用栈回溯 模块加载器入口 全局暴露的加载器 核心模块调用 环境检测属性访问 补环境报错排查 监控代理生效 插件安装思路 插件配置


核心技术难点

本次逆向的核心挑战集中在4点:

  1. 动态混淆与更新:核心 JS 代码会定期更新混淆规则,静态分析的复用性有限
  2. 模块化封装:所有逻辑被打包进 Webpack 风格的自执行函数,没有直接暴露的全局方法
  3. 浏览器指纹检测:会检测 window.navigatorcanvasWebGL 等近百个环境属性
  4. 加密链路嵌套anti-content 内部可能组合了 Base64、SHA-1/SHA-256、自定义压缩等算法

环境补全与代理监控

为了复刻浏览器环境的同时追踪所有环境属性的访问/修改,我们可以写一个轻量级的 JS 代理监控方案——先补全基础对象,再用 Proxy 监听全局对象、DOM/BOM 敏感对象的操作。

2.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 = {
  // 同样后续补全
};

2.2 通用代理监控函数

/**
 * 给指定对象数组添加 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}`
        );
        // 如果属性不存在,打印空占位符,避免访问 undefined 直接报错
        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);
  }
}

2.3 监控敏感对象配置

拼多多重点检测以下对象,建议优先加入监控数组:

const monitorTargets = [
  "window",
  "navigator",
  "screen",
  "location",
  "document",
  "canvas",
  "WebGLRenderingContext",
  "performance",
];
setProxy(monitorTargets);

代码分析与逆向思路

3.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 暴露到了全局,这是后续快速定位模块的核心入口。

3.2 定位核心 anti-content 生成模块

步骤1:全局搜索入口

在开发者工具「Sources」面板的全局搜索框输入 anti_contentanti-content,找到给接口参数赋值的位置。

步骤2:调用栈回溯

在赋值处打断点,刷新触发请求,查看「Call Stack」面板,找到调用链上层最接近混淆模块的位置——通常会看到类似 (new window.rrr(xxx))().messagePack() 的调用。

步骤3:测试模块有效性

在控制台输入 window.rrr(找到的模块ID),如果能返回一个构造函数,再调用构造函数和 messagePack(),能得到合法的 anti-content 或至少是一段字符串,说明找对了模块。


常见问题解决

4.1 模块调用报错「x is not a function/undefined」

原因:核心模块依赖其他混淆模块,这些依赖在单独运行时可能没有加载。
解决:在真实浏览器的控制台,先运行抓包到的完整加载器代码,再调用核心模块;或者用 __pdd_require__.m 遍历所有模块,补全依赖链。

4.2 环境检测拦截请求

原因:补全的环境属性数量、顺序、值的类型/范围与真实浏览器不符。
解决

  1. 先运行我们写的代理监控,在真实浏览器完整触发一次接口请求
  2. 把控制台打印的所有 GET 操作记录下来
  3. 对照记录逐个补全环境对象的属性,注意属性的 getter/setter 逻辑、值的随机性(比如 canvas.toDataURL() 每次生成的都不一样)

总结

本次逆向主要利用了「拼多多模块加载器暴露到全局」这个突破口,结合「XHR断点+调用栈回溯」快速定位核心模块,再通过「Proxy 代理监控」追踪环境依赖。后续如果要实现稳定的签名获取,建议:

  1. 优先使用自动化浏览器工具(Playwright/Puppeteer)注入代码获取,减少环境补全的工作量
  2. 如果必须离线复刻,要重点处理 canvas/WebGL 指纹的随机性
  3. 关注代码的更新频率,定期重新定位模块

(全文完,约2700字)