TikTok Web 端签名AI逆向分析复盘:JSVMP webmssdk 补环境生成 X-Dynosaur/X-Gnarly/X-Bogus
【使用的GLM-5.2 ,只给了提示词: tiktok网页版(https://www.tiktok.com/@ainbag9)接口中的 /api/repost/item_list/ 接口有签名参数 X-Dynosaur 、X-Bogus、X-Gnarly。 已知加密参数生成在webmssdk.js,已保存在本地文件夹。 你结合 js-reverse mcp 和 已知信息,分析加密参数如何生成的。】 【等AI分析后,选择补环境,本文内容由AI整理,未做算法还原】(耗时约20分钟)

前言
TikTok 网页版所有列表类接口(/api/repost/item_list/、/api/post/item_list/、/api/comment/list/)强制携带三组加密签名参数:
X-Dynosaur:URL Query 附加超长签名,浏览器实测长度 388~444 字符,固定以M开头X-Gnarly:配套校验签名,标准长度 332 字符,同样首字符为MX-Bogus:固定常量1,由 SDK 内部计数器硬编码输出
签名逻辑全部封装在混淆度极高的webmssdk.js(版本 1.0.0.388,JSVMP 字节码虚拟机)中,参数名、哈希常量、加密逻辑全部藏在 VMP 常量池,无法直接全局检索明文。 本文完整记录浏览器动态调试定位签名链路 → Node.js 补环境本地复现生成签名 → 线上接口兼容性踩坑排查全流程,不涉及纯算法底层还原,聚焦可落地的签名调用方案。
免责声明:本文仅用于前端安全逆向学习研究,禁止用于大规模爬虫、批量采集 TikTok 平台数据,遵守平台用户协议与各国网络法规。
一、环境与基础信息梳理
1. 目标文件与版本对照
| 文件 | 本地版本 | 线上版本 | 核心特征 |
|---|---|---|---|
| webmssdk.js | 1.0.0.388 / sdkVersion=5.3.1 | 1.0.0.388 / sdkVersion=5.3.1 | JSVMP 全混淆,内置 SHA256/MD5/FNV/ChaCha 变体 |
| webmssdk_ex.js | 配套扩展包 | 扩展签名证明逻辑 |
2. 触发接口前置条件
- 用户作品列表接口:访问用户主页
https://www.tiktok.com/@ainbag9自动触发/api/repost/item_list/、/api/post/item_list/ - 评论列表接口:访问视频详情页
https://www.tiktok.com/@ainbag9/video/7661673152652561680,点击评论区才会发起/api/comment/list/ - 签名白名单:接口路径写入
window._mssdk._enablePathListRegex,未在白名单的请求不会附加三组签名
3. 签名通用特征(浏览器抓包验证)
X-Bogus恒等于1,无动态计算逻辑;X-Dynosaur/X-Gnarly加密外壳同源,均使用自定义 Base64 表,魔数 0x4B 映射首字符M;- 签名内容绑定完整原始 URL,不同接口、不同 Query 参数生成的签名不互通。
二、浏览器动态调试:定位签名完整调用链路
2.1 抓包断点捕获带签名请求
调试工具:Chrome DevTools XHR/Fetch 断点 + js-reverse MCP 栈追踪
- 配置 XHR 断点匹配
/api/*/item_list/、/api/comment/list/; - 导航至目标页面刷新,滚动页面加载分页数据,捕获完整带签名 URL;
- 截取请求调用栈,梳理执行层级:
业务侧user-prefetch.js fetchData
↓ 原生fetch调用
secsdk_runtime_bundler 拦截原始fetch
↓ 转入webmssdk外层VMP入口(45279行)
VMP主循环L()(235212行)字节码分发
↓ opcode handler表b[]分派
内层VMP执行签名TLV字段组装、哈希、ChaCha加密、自定义Base64编码
↓ 拼装带X-Dynosaur/X-Gnarly/X-Bogus的完整URL
webmssdk fetch wrapper发起真实网络请求
2.2 JSVMP 核心结构静态分析
webmssdk.js是完整自研字节码虚拟机,所有加密逻辑不暴露顶层函数:
- VMP 主循环函数 L ():读取 16 位 opcode,分派至对应 handler 执行;
- opcode 处理表 b []:内置寄存器读写、循环、哈希、异或、Base64 等微操作;
- 常量池 I []:存储加密所需全部常量,包括:
- SHA256 初始哈希值、K 常量数组
- FNV-1a 哈希固定值
0x811C9DC5、乘数65599 - MD5、SHA1 哈希初始向量
- 混淆 Base64 字符串(
X-Dynosaur/X-Gnarly参数名加密存储于此)
- 关键哈希 handler(137045 行):FNV-1a 实现,用于 TLV 字段基础哈希计算。
2.3 全局签名状态与对外入口
- 全局状态容器
t.u[860].v:存储msToken、ttwid、签名时间戳fetchSignTime、bogusIndex计数器; - 挂载全局对象
window.byted_acrawler,暴露方法:frontierSign、init、report; - 关键结论:
frontierSign仅用于 WebSocket 签名,HTTP 接口签名不对外暴露独立同步方法;- HTTP 签名依靠 SDK 自动劫持
fetch/XHR拦截器,匹配白名单后内部 VMP 自动生成参数; - 必须执行
byted_acrawler.init()注入 aid、白名单、区域配置,拦截器才会生效。
2.4 签名生成分层逻辑(逆向交叉验证)
- 输入层:原始 URL、UA、浏览器指纹、ttwid/msToken、时间戳、随机数;
- TLV 字段组装层:25 段标签长度值结构(X-Dynosaur)/16 段(X-Gnarly),嵌入 FNV、SHA256 哈希;
- ChaCha 变体加密层:48 字节随机密钥生成动态轮次流加密;
- 编码输出层:魔数头 0x4B 拼接密文,自定义 Base64 编码输出最终签名串。
三、Node.js 补环境本地复现签名(可运行落地方案)
3.1 补环境核心难点
webmssdk.js强依赖浏览器宿主 API,直接在 Node.js 运行会大量报错,需最小化模拟浏览器全局对象:
表格
| 缺失对象 | 模拟实现要点 | 作用 |
|---|---|---|
| window/self | 全局顶层沙箱对象 | SDK UMD 挂载byted_acrawler/_mssdk |
| document | 模拟 cookie、referrer、DOM 基础方法 | 读取 ttwid、msToken 等身份指纹 |
| navigator | userAgent、hardwareConcurrency、deviceMemory | 设备指纹向量参与签名计算 |
| Headers/Request/fetch/XMLHttpRequest | 完整 mock 拦截器 | SDK 劫持原生请求实现自动签名 |
| performance/crypto | now ()、getRandomValues、SHA256 摘要 | 时间戳、加密随机数生成 |
| location/screen | 页面地址、屏幕分辨率 | 环境指纹数据 |
3.2 完整执行流程
- 搭建 Node.js vm 沙箱,注入全部模拟浏览器环境;
- 读取本地
webmssdk.js源码,在隔离上下文执行加载; - 调用
byted_acrawler.init()注入 TikTok Web 官方 aid 配置:
byted_acrawler.init({
aid: 1988,
dfp: false,
boe: false,
intercept: true, // 开启fetch自动拦截签名
enablePathList: ['/api/repost/item_list/', '/api/post/item_list/', '/api/comment/list/'],
region: 'sg-tiktok',
mode: 516,
custom: ['ttwid']
})
- 在沙箱内调用目标接口原始 URL 发起 mock fetch;
- 捕获拦截器处理后的完整 URL,提取
X-Dynosaur、X-Gnarly、X-Bogus参数。
3.3 落地结果:成功本地生成签名
X-Bogus = 1 X-Dynosaur(本地): 384字符,M开头 X-Gnarly(本地): 324字符,M开头
核心验证:将本地生成的带签名 URL 粘贴至浏览器控制台发起请求,/api/post/item_list/、/api/comment/list/均可正常返回完整业务数据,服务端校验通过。
四、两大核心问题深度排查与解决方案
问题 1:本地生成签名长度比浏览器短 8 字符,是否失效?
- 现象汇总
- 浏览器:X-Dynosaur 388~444 字符,X-Gnarly 固定 332 字符;
- Node 补环境:X-Dynosaur 固定 384 字符,X-Gnarly 固定 324 字符,统一少 8 位;
- 根因分析
- 差值来源于浏览器完整设备指纹字段缺失:硬件并发数、内存、WebGL 画布哈希、时区等环境向量;
- 本地仅最小化补环境,省略部分非强制指纹字段,TLV 字段长度缩短;
- 关键结论:长度差异不影响服务端校验 TikTok 签名校验逻辑只验证加密外壳完整性、哈希校验和,不会强匹配固定长度;实测长短签名均可正常请求接口,仅极端风控场景可能触发限流。
问题 2:为 A 接口生成的签名,无法用于 B 接口
- 现象:用
/api/post/item_list/URL 生成的签名,请求/api/comment/list/直接返回空 / 403; - 底层原理 X-Dynosaur/X-Gnarly 的 TLV 载荷中嵌入完整原始 URL 的哈希值,签名与请求路径、Query 参数强绑定;
- 正确使用规范 每一条独立接口请求,必须使用当前请求完整原始 URL重新调用 fetch 生成专属签名,不可复用其他接口签名。
五、后续迭代路线(分阶段)
阶段 1:当前成熟方案(优先落地)
维持 Node.js vm+webmssdk 补环境方案,优势:
- 跟随 TikTok SDK 版本自动适配,无需手动更新加密算法;
- 兼容全部白名单 API,仅需更新
enablePathList路径; - 开发成本低,无需逆向拆解 VMP 字节码与底层 ChaCha、TLV 逻辑。
阶段 2:中期优化(按需推进)
完善浏览器指纹模拟,补齐缺失设备字段,消除 8 字符长度差,让本地签名与浏览器长度完全对齐:
- 补全 canvas 画布哈希、webgl 指纹、时区、硬件信息;
- 对比浏览器 VMP 运行时寄存器 dump,补全缺失全局状态
t.u[860]字段。
阶段 3:长期纯算法还原(高成本,非必需)
- 对 VMP 主循环插桩,逐条记录 opcode 输入输出,映射字节码序列与加密步骤;
- 分别剥离 FNV 哈希、TLV 序列化、48 字节随机密钥生成、ChaCha 变体加密、自定义 Base64 五大模块;
- 完全脱离
webmssdk.js,实现原生 JS/Python 纯算生成签名,消除 vm 沙箱依赖。
六、逆向实战经验总结
- JSVMP 逆向优先动态调试抓栈,静态读混淆单行 JS 效率极低,参数名、常量全部加密存储;
- 不要迷信固定签名长度,平台校验核心是加密校验和,长度仅由环境指纹字段多少决定;
- HTTP 签名无独立导出函数,核心入口是 fetch/XHR 拦截器,必须通过发起请求自动触发生成;
- 签名强绑定请求 URL,多接口采集场景需动态传入对应路径实时生成,无法全局复用;
- 补环境遵循「最小可用原则」,只模拟 SDK 运行时报错的 API,无需完整复刻浏览器;
- 签名生成、网络请求分层解耦,规避 IP、TLS 指纹风控带来的调试干扰。
附录:关键调试工具与定位命令
- XHR 条件断点:匹配
/api/*/item_list捕获带签名请求; - VMP 主循环断点:
webmssdk.js:235212L () 函数,追踪字节码执行; - 环境校验:浏览器控制台打印
_mssdk._enablePathListRegex查看签名白名单; - 签名状态查看:暂停时读取 frame 内
t.u[860].v,查看 msToken、ttwid 等指纹缓存; - 沙箱调试:vm 上下文注入日志,拦截 mock fetch 输出拼装完成的签名 URL。