> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# 抖音 a_bogus 逆向案例总结
- URL: https://blog.vercanti.com/dou-yin-a_bogus-ni-xiang-an-li-zong-jie/
- Published: 2026-08-28T14:34:59.000Z
- Updated: 2026-08-28T14:57:46.000Z
- Description: 本案例对抖音 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
- Author: yellowdog
- Tags: js逆向, douyin

本案例对抖音 Web 端 URL 查询参数 `a_bogus` 完成了从抓包定位到离线复现的全链路逆向，产出了三类成果：方法论教程 [JS逆向入门指南](https://blog.vercanti.com/js-ni-xiang-ru-men-zhi-nan/)、SM3 哈希链算法深挖 [a\_bogus\_sm3\_analysis](https://blog.vercanti.com/douyin-a%5Fbogus-jia-mi-suan-fa-wan-zheng-fen-xi/)，以及可运行工件 `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](https://blog.vercanti.com/douyin-a%5Fbogus-jia-mi-suan-fa-wan-zheng-fen-xi/) |
| 是否确定性  | 否，含时间戳/计数器，同一 URL 多次调用结果不同，令牌不可复用                                                                                                 |
| 离线可行性  | 可在 Node.js + jsdom 离线产出，无需真实浏览器                                                                                                   |
| 版本锁定   | 锁定 bdms\_1.0.1.19\_fix，随抖音版本更新失效                                                                                                  |

定位手段、断点/Hook、识别算法等通用步骤见 [JS逆向入门指南](https://blog.vercanti.com/js-ni-xiang-ru-men-zhi-nan/)，此处不重复。

---

## 二、XHR 调用链还原（独有）

抖音对 `XMLHttpRequest.prototype.open` / `send` 做了安全包装层（security wrapper），覆盖在 bdms 之上。这一层是成品文档未记录的中间环节。

`open` 被替换为包装函数，缓存原始参数并打时间戳后再调原生方法：

```javascript
// XMLHttpRequest.prototype.open（security wrapper）
function () {
  this.secureOpenArgs = arguments;
  this.ucProxyParam = { startTime: Date.now() };
  t.nativeXMLHttpRequestOpen.apply(this, arguments);
}

```

`send` 通过 `hookConfig(l).needProxy` 判定是否走代理，不需代理时直接转发原生 `send`：

```javascript
// 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。

```javascript
// 注入 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+ 种操作码         |

```javascript
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，可用魔法常量直接定位（见 [魔法数字速查](https://blog.vercanti.com/jia-mi-suan-fa-mo-fa-shu-zi-su-cha/)），而 WASM 需反编译/内存断点（WASM 逆向见 [wasm特征](https://blog.vercanti.com/js-ni-xiang-zhong-de-webassembly-wasm-te-zheng-shi-bie-yu-ni-xiang/)）。

---

## 四、SM3 哈希链（摘要，详见深挖文档）

`a_bogus` 的核心是一条 5 步 SM3 哈希链。本篇仅列定位线索，IV/轮常数四重证据、`dhzx` 密钥、`CONST` 常量、类 HMAC-SM3 结构与 Python 复现见 [a\_bogus\_sm3\_analysis](https://blog.vercanti.com/douyin-a%5Fbogus-jia-mi-suan-fa-wan-zheng-fen-xi/)。

定位线索：在混淆 JS 中搜索 SM3 轮常量即可锁定实现（`gr` 类）。

| 常量         | 十六进制       | 角色                                                                                                                             |
| ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------ |
| 1937774191 | 0x7380166F | IV reg\[0\]                                                                                                                    |
| 1226093241 | 0x4914B2B9 | IV reg\[1\]（完整 IV 八元组见 [a\_bogus\_sm3\_analysis](https://blog.vercanti.com/douyin-a%5Fbogus-jia-mi-suan-fa-wan-zheng-fen-xi/)） |
| 2043430169 | 0x79CC4519 | 轮常量 T0（轮 0–15）                                                                                                                 |
| 2055708042 | 0x7A879D8A | 轮常量 T1（轮 16–63）                                                                                                                |

参与签名的是 querystring，路径本身不参与；只有匹配路径过滤规则的请求才会被签名。

---

## 五、离线运行方案（jsdom）

bdms 是独立 JS SDK，不依赖 DOM 渲染，因此可在 jsdom 离线运行。补环境思路见 [补环境](https://blog.vercanti.com/bu-huan-jing/)。

选型对比（为何选 jsdom 而非 Playwright）：

| 维度     | jsdom            | Playwright |
| ------ | ---------------- | ---------- |
| 启动时间   | 约 3s             | 约 10s      |
| 依赖     | jsdom npm 包（已装）  | 完整浏览器      |
| 兼容性    | 仅纯 JS SDK        | 全部场景       |
| canvas | 不支持              | 支持         |
| 适用     | 无 canvas 依赖的 SDK | 需完整渲染      |

落地三要点：

1. 在 bdms 加载**前**安装最小化 mock XHR（bdms 会在 `init()` 时快照 `XMLHttpRequest.prototype` 与 `URLSearchParams.prototype.append`，晚于此的 hook 会被绕过）。
2. 必须调用 `bdms.init(config)` 触发路径过滤规则匹配，否则 `getABogus` 返回空。
3. jsdom 无 canvas，bdms 会降级处理，不阻断签名；jsdom 构造时传 `pretendToBeVisual` 防止 canvas 相关代码崩溃。
4. 结束时 `process.exit(0)`，避免 jsdom timer 挂起进程。

路径过滤规则（仅以下前缀的 URL 被签名）：

```
^/webcast/   ^/aweme/v1/   ^/aweme/v2/   /douplus/
/v1/message/send   ^/live/   ^/captcha/   ^/ecom/   ^/luna/pc

```

最小 mock 与 init 配置：

```javascript
// 最小化 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__` 管理进程关闭：

```python
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](https://blog.vercanti.com/douyin-a%5Fbogus-jia-mi-suan-fa-wan-zheng-fen-xi/) 第五节 |

参见：[JS逆向入门指南](https://blog.vercanti.com/js-ni-xiang-ru-men-zhi-nan/)、[a\_bogus\_sm3\_analysis](https://blog.vercanti.com/douyin-a%5Fbogus-jia-mi-suan-fa-wan-zheng-fen-xi/)、[魔法数字速查](https://blog.vercanti.com/jia-mi-suan-fa-mo-fa-shu-zi-su-cha/)、[补环境](https://blog.vercanti.com/bu-huan-jing/)