抖音 a_bogus 逆向案例总结
本案例对抖音 Web 端 URL 查询参数 a_bogus 完成了从抓包定位到离线复现的全链路逆向,产出了三类成果:方法论教程 JS逆向入门指南(/js-ni-xiang-ru-men-zhi-nan/)、SM3 哈希链算法深挖 a_bogus_sm3_analysis(/douyin-a_bogus-jia-mi-suan-fa-wan-zheng-fen-xi/),以及可运行工件 gen_abogus.mjs(Node + jsdom 生成器)/ abogus.py(Python 封装)/ example.py(调用样板)/ probe_sm3.mj
本案例对抖音 Web 端 URL 查询参数 a_bogus 完成了从抓包定位到离线复现的全链路逆向,产出了三类成果:方法论教程 JS逆向入门指南、SM3 哈希链算法深挖 a_bogus_sm3_analysis,以及可运行工件 gen_abogus.mjs(Node + jsdom 生成器)/ abogus.py(Python 封装)/ example.py(调用样板)/ probe_sm3.mjs(SM3 插桩侦察)。本篇是这三者的总览索引,只收录前两篇成品未覆盖的工程链路、字节码 VM 结构与服务化实现,三篇分工不重叠。
一、案例概述与最终结论
| 项目 | 结论 |
|---|---|
| 目标参数 | a_bogus(URL 查询参数,约 160 字符,base64-url 变体) |
| 生成 SDK | bdms_1.0.1.19_fix.js(147,530 字节,webpack 打包) |
| 哈希算法 | SM3(国密 GB/T 32905-2016),详见 a_bogus_sm3_analysis |
| 是否确定性 | 否,含时间戳/计数器,同一 URL 多次调用结果不同,令牌不可复用 |
| 离线可行性 | 可在 Node.js + jsdom 离线产出,无需真实浏览器 |
| 版本锁定 | 锁定 bdms_1.0.1.19_fix,随抖音版本更新失效 |
定位手段、断点/Hook、识别算法等通用步骤见 JS逆向入门指南,此处不重复。
二、XHR 调用链还原(独有)
抖音对 XMLHttpRequest.prototype.open / send 做了安全包装层(security wrapper),覆盖在 bdms 之上。这一层是成品文档未记录的中间环节。
open 被替换为包装函数,缓存原始参数并打时间戳后再调原生方法:
// XMLHttpRequest.prototype.open(security wrapper)
function () {
this.secureOpenArgs = arguments;
this.ucProxyParam = { startTime: Date.now() };
t.nativeXMLHttpRequestOpen.apply(this, arguments);
}
send 通过 hookConfig(l).needProxy 判定是否走代理,不需代理时直接转发原生 send:
// XMLHttpRequest.prototype.send(security wrapper)
function () {
var h = t.hookConfig(l) || {}, v = h.needProxy;
if (!p && !y && !b) return t.nativeXMLHttpRequestSend.apply(this, o);
// ...needProxy 分支
}
完整调用链(从页面代码到原生 XHR):
page code → security wrapper → bdms VM hook → initScript hook → native XHR
关键实证(来自 _pendingXhrUrls 观测):在 open() 时捕获的完整 URL 已经包含 a_bogus。这证明 bdms 在 open() 阶段就生成并附加了 a_bogus,随后才进入 security wrapper 的下一环。成品文档只给出"生成时机 = open()"的结论,本篇补上观测证据。
由此推出捕获正确测试 URL 的方法:从 _pendingXhrUrls 提取真实请求 URL,去掉已有 a_bogus 参数后再喂给 getABogus(url)。用伪造参数(fake params)会因不匹配 bdms 路径过滤而失败。
浏览器阶段验证:Promise 桥接 + 超时兜底
在离线复现前,先在真实浏览器里确认能拿到 a_bogus 是稳妥的做法。难点是签名经由 hook 异步附加,无法直接 return。通用技巧:在 initScript 里定义 window.getABogus(url),用 Promise 把"hook 捕获"桥接成可 await 的同步取值,并加超时兜底防止路径不匹配时永久挂起。这套脚手架可迁移到其他同类 SDK。
// 注入 initScript:把异步 hook 捕获改造成可 await 的取值
window.getABogus = function (url) {
return new Promise((resolve) => {
// 由 URLSearchParams.prototype.append 的 a_bogus hook 调用它 resolve
window.__resolveABogus = resolve;
const xhr = new XMLHttpRequest();
xhr.open("GET", url); // open() 阶段 bdms 即生成并附加 a_bogus
xhr.send();
// 路径不匹配过滤规则时不会触发 hook,1s 兜底避免永久 pending
setTimeout(() => resolve(null), 1000);
});
};
// 用法:const ab = await getABogus("/aweme/v1/web/...");
三、bdms 字节码 VM 内部结构(独有)
bdms 的核心逻辑跑在自实现的 JavaScript 字节码虚拟机(VMP 混淆)里,由三个函数构成。成品文档只在调用栈里提到 d() / X() 的行号,未记录三函数职责与操作码语义。
| 函数 | 职责 |
|---|---|
D(t, r) |
VM 包装函数工厂:取 z[t](z[] 为字节码模块数组),返回调用 X 的闭包 |
X(t, r, e, n) |
VM 主循环:do { try { d(); } catch {...} } while (y()) |
d() |
指令执行器:o[a++] 取下一条指令,t < 38 时分发约 60+ 种操作码 |
function D(t, r) { // 创建 VM 包装函数
var e = z[t]; // z[] = 字节码模块数组
var n = function () { return X(e, this, arguments, r); };
return n;
}
function X(t, r, e, n) { // VM 主循环
do { try { d(); } catch (t) { /* ... */ } } while (y());
}
function d() { // 指令执行器
var t = o[a++]; // 读取下一条指令
if (t < 38) { /* 指令分发 */ }
}
部分操作码(opcode)语义:
| 操作码 | 含义 |
|---|---|
| 0 | 函数调用(call) |
| 7 | 创建空对象 {}(new obj) |
| 8 | 属性读取 obj[key](getprop) |
| 9 | push true |
| 14 | 属性写入 obj[key] = val(setprop) |
| 15 | 全局变量写入(setglobal) |
字节码块为 base64 编码,约位于文件 offset 120000–130000。
这是纯 JavaScript 自实现的字节码 VM,不是 WebAssembly:文件内无
.wasm模块、无WebAssembly.instantiate。其指令分发模型与 WASM 同构,可作为理解 WASM 解释执行的轻量类比,但逆向路径不同——此处密码学原语(SM3)以明文 webpack 模块独立存在、未进 VM,可用魔法常量直接定位(见 魔法数字速查),而 WASM 需反编译/内存断点(WASM 逆向见 wasm特征)。
四、SM3 哈希链(摘要,详见深挖文档)
a_bogus 的核心是一条 5 步 SM3 哈希链。本篇仅列定位线索,IV/轮常数四重证据、dhzx 密钥、CONST 常量、类 HMAC-SM3 结构与 Python 复现见 a_bogus_sm3_analysis。
定位线索:在混淆 JS 中搜索 SM3 轮常量即可锁定实现(gr 类)。
| 常量 | 十六进制 | 角色 |
|---|---|---|
| 1937774191 | 0x7380166F | IV reg[0] |
| 1226093241 | 0x4914B2B9 | IV reg[1](完整 IV 八元组见 a_bogus_sm3_analysis) |
| 2043430169 | 0x79CC4519 | 轮常量 T0(轮 0–15) |
| 2055708042 | 0x7A879D8A | 轮常量 T1(轮 16–63) |
参与签名的是 querystring,路径本身不参与;只有匹配路径过滤规则的请求才会被签名。
五、离线运行方案(jsdom)
bdms 是独立 JS SDK,不依赖 DOM 渲染,因此可在 jsdom 离线运行。补环境思路见 补环境。
选型对比(为何选 jsdom 而非 Playwright):
| 维度 | jsdom | Playwright |
|---|---|---|
| 启动时间 | 约 3s | 约 10s |
| 依赖 | jsdom npm 包(已装) | 完整浏览器 |
| 兼容性 | 仅纯 JS SDK | 全部场景 |
| canvas | 不支持 | 支持 |
| 适用 | 无 canvas 依赖的 SDK | 需完整渲染 |
落地三要点:
- 在 bdms 加载前安装最小化 mock XHR(bdms 会在
init()时快照XMLHttpRequest.prototype与URLSearchParams.prototype.append,晚于此的 hook 会被绕过)。 - 必须调用
bdms.init(config)触发路径过滤规则匹配,否则getABogus返回空。 - jsdom 无 canvas,bdms 会降级处理,不阻断签名;jsdom 构造时传
pretendToBeVisual防止 canvas 相关代码崩溃。 - 结束时
process.exit(0),避免 jsdom timer 挂起进程。
路径过滤规则(仅以下前缀的 URL 被签名):
^/webcast/ ^/aweme/v1/ ^/aweme/v2/ /douplus/
/v1/message/send ^/live/ ^/captcha/ ^/ecom/ ^/luna/pc
最小 mock 与 init 配置:
// 最小化 mock XHR(必须在 bdms 加载前)
class MockXHR {
open(method, url) { this._url = String(url); }
send() {} setRequestHeader() {}
addEventListener() {} removeEventListener() {}
}
window.XMLHttpRequest = MockXHR;
// bdms.init 配置(取自页面 sdk-glue.js)
bdms.init({ aid: 6383, pageId: 6241, paths: [/* 见上 */], boe: false, ddrt: 8.5, ic: 8.5 });
六、工程实现:Node + Python 服务化(独有)
abogus.py 的服务模式通过 subprocess 拉起 Node server,并用一套握手协议确认就绪——这套 ready 握手与进程生命周期管理是成品文档未记录的内部实现。
握手时序:
Python process ──spawn──▶ Node.js server
│ stdout: {"status":"ready"}
Python(readline 等到 ready)──▶ HTTP GET /sign?url=...
│ JSON response
Python(解析)──▶ a_bogus token
gen_abogus.mjs 提供 CLI 与 --server 两种模式;abogus.py 用 subprocess.Popen 启动 Node,循环 readline 直到 stdout 出现 {"status":"ready"} 才发请求,并用 __enter__/__exit__ 管理进程关闭:
class ABogus:
def _start_server(self):
self._proc = subprocess.Popen(cmd, stdout=PIPE, ...)
for _ in range(30): # 最多等约 6s
line = self._proc.stdout.readline()
if '"ready"' in line:
self._ready.set(); return
time.sleep(0.2)
def __enter__(self): return self
def __exit__(self, *_): self.close()
性能:服务模式启动约 3s(jsdom + bdms 初始化),之后约 0.03s/请求(实测 3 请求 0.08s)。单次 CLI 模式每次都需重新初始化(约 3s),仅适合低频调用。
七、维护与风险
| 项 | 说明 |
|---|---|
| 版本锁定 | 锁定 bdms_1.0.1.19_fix.js,抖音更新后需重新下载 |
| 监控点 | 监控 /tos-cn-i-9r5gewecjs/bdms_*.js URL 变化以发现版本更替 |
| canvas 降级 | jsdom 无 canvas,bdms 设备指纹退化,长期存在被检测风险 |
| 令牌特性 | a_bogus 不可复用,每次请求需重新生成 |
| 配套参数 | 需配合 msToken、webid、verifyFp、uifid、fp 等真实参数使用 |
| 签名参数辨析 | X-Bogus(webmssdk 内部生成但不发送)、x-secsdk-web-signature / x-secsdk-csrf-token(runtime_bundler 生成的请求头)与 a_bogus(bdms 生成的 URL 参数)是不同体系,勿混淆 |
八、文件清单与参见
工件(位于 js逆向/douyin/):
| 文件 | 作用 |
|---|---|
lib/bdms_1.0.1.19_fix.js |
逆向对象本体(版本锁定) |
gen_abogus.mjs |
Node + jsdom 生成器,CLI 与 --server 模式 |
abogus.py |
Python 封装,含 subprocess 服务模式与 ready 握手 |
example.py |
配合 msToken/webid 等参数发请求的样板 |
probe_sm3.mjs |
SM3 插桩侦察脚本,对应 a_bogus_sm3_analysis 第五节 |