百趣云 百趣云的博客

抖音 web 端 a_bogus 参数生成逻辑分析

先认清对手:a_bogus 是 VMP 保护的

抖音 web 端的签名参数从 _signatureX-Bogus 再到现在的 a_bogus,防护强度一路升级。当前版本的生成代码在 webmssdk.js 里,整个文件跑在字节自研的 JS 虚拟机上——你看到的不是 JS 代码,而是一堆字节码和一个巨大的 switch-case 解释器。

这意味着"读源码理解算法"这条路基本被堵死:不是不能,是投入产出比极低。工程上成熟的打法是整段抠出 + 补环境,让这段 VMP 代码在 Node 里原样跑起来,我们只管调它的导出函数。

第一步:确认签名入口

webmssdk 加载后会在 window 上挂出入口,字节系的经典名字是 byted_acrawler

// Console 里验证
window.byted_acrawler.sign({ url: "https://www.douyin.com/aweme/v1/web/aweme/post/?device_platform=webapp&aid=6383&..." })
// 返回完整的 a_bogus 字符串

不同页面版本入口名字可能不同(有的版本挂在更深的命名空间下),不确定就用 hook 定位:

const _open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(m, u) {
    if (u.includes('a_bogus=')) debugger;
    return _open.apply(this, arguments);
};

断下后看调用栈里最底层(最接近 SDK 内部)的那帧,它读取的那个函数就是入口。

第二步:抠出 SDK,一个字节都别动

这是整个方案里最容易翻车的一步,要点:

  1. 完整保存 webmssdk.js。不要格式化、不要删"无用"代码、不要调整顺序。VMP 代码里有大量基于执行时序的自校验,格式化工具改一个换行都可能导致输出不同。
  2. 找到它的加载方式。有的版本是独立 script 标签,有的是嵌在主 chunk 里通过 eval 释放。后者要把 eval 的字符串参数抠出来。
  3. 记录初始化时序。SDK 可能在 DOMContentLoaded 后才完成初始化,Node 里要手动触发对应流程,或者轮询 window.byted_acrawler 直到可用。

第三步:补环境——真正的战场

VMP 代码对环境的探测非常细致,这是补环境的清单(按踩坑频率排序):

global.window = global;
window.navigator = {
    userAgent: UA,                    // 必须和后续请求的 ua 一致
    platform: 'Win32',
    webdriver: false,                 // 必检项
    languages: ['zh-CN', 'zh'],
    plugins: { length: 3 },           // 空 plugins 是无头浏览器典型特征
    hardwareConcurrency: 8,
    deviceMemory: 8,
};
window.document = {
    cookie: '',
    referrer: 'https://www.douyin.com/',
    createElement: (tag) => tag === 'canvas' ? fakeCanvas : {},
    addEventListener: () => {},
    hidden: false,
    visibilityState: 'visible',
};
window.location = new URL('https://www.douyin.com/');
window.screen = { width: 1920, height: 1080, availWidth: 1920, availHeight: 1040, colorDepth: 24 };
window.performance = { now: () => Date.now() - START_TIME };  // 返回值要单调递增且合理
window.requestAnimationFrame = (cb) => setTimeout(cb, 16);
window.XMLHttpRequest = function() {};  // SDK 内部可能发探测请求,要拦

几个要命的细节:

  • performance.now() 不能返回 0 或定值。SDK 会多次调用计算代码执行耗时,全 0 直接判定异常环境。用真实流逝时间。
  • canvas 要有合理的 toDataURL 返回。不需要真渲染,从真实浏览器采一个 dataURL 固定返回即可,但要和 ua 声称的平台匹配。
  • 函数 toString 检测。SDK 会 Function.prototype.toString.call(someNativeFunc) 验证是不是原生代码。你 hook 过的函数要用 toString 补丁伪装回 function xxx() { [native code] }

第四步:对拍验证

环境补没补对,唯一的裁判是对拍:

// Node 侧
const sig = window.byted_acrawler.sign({ url: TEST_URL });
console.log(sig);

同一个 TEST_URL,浏览器 Console 里算一次,Node 里算一次。注意 a_bogus 含时间戳和随机数,两次结果不一样是正常的——对拍看的不是相等,而是"用 Node 生成的 a_bogus 去发真实请求,接口放不放行"。放行 = 环境合格;返回验证页 = 继续补。

精髓:msToken 和 ttwid 别忘了

a_bogus 只是请求签名,抖音 web 端还有两个配套参数:

  • msToken:由另一个 SDK(或响应 Set-Cookie)下发,风控令牌,请求里要原样带。它有有效期,过期要重取。
  • ttwid:设备级 cookie,由 ttwid.bytedance.com 注册接口下发,长期有效,是设备归因的核心。协议池里每个"设备"固定一个 ttwid。

三者关系:ttwid 定设备,msToken 定会话风险等级,a_bogus 定单次请求合法性。只模拟 a_bogus 而忽视前两个,请求照样被拦。

总结

  1. a_bogus 是 VMP 保护,放弃读算法,走"整段抠出 + 补环境"
  2. 抠代码原样保存,格式化是隐形杀手
  3. 补环境三大考点:performance 计时、canvas 指纹、函数 toString 伪装
  4. 对拍标准是"真实请求放行",不是和浏览器结果相等
  5. msToken、ttwid 与 a_bogus 三位一体,缺一不可
By 百趣云 阅读量:1 On