前段时间给大家拆解了国内抖音 A-Bogus 加密参数,同时分享了对应的播放量简易工具。文章发布之后我发现,大家对字节系的签名逆向、实操工具类内容热情特别高,后台也收到了非常多小伙伴的私信催更。
既然大家喜欢这类实战逆向内容,那我们就趁热打铁,继续深耕字节系加密逻辑,本次完整落地一套专业 TikTok 逆向实操方案。
国内某音的参数我们已经吃透了,今天我们直接进阶对标国际版 TikTok,重点攻克目前全网刚需、风控最严、逆向难度最高的核心参数——X-Gnarly。
熟悉TikTok接口开发的朋友都知道,目前几乎所有列表、详情、数据请求接口,全部强制校验 X-Gnarly 参数。参数错误、格式不对、复用旧参数,都会直接403拦截请求。
今天这篇 TikTok 逆向干货,我们就从零开始,完整研究、拆解 TikTok X-Gnarly 的加密逻辑与校验规则。
一、目标定位——X-Gnarly是什么?长什么样?
1. 接口抓包筛选目标接口
我们以用户主页作品列表接口 /api/post/item_list/ 作为本次逆向演示接口。
打开浏览器开发者抓包工具,访问任意TikTok博主主页,在网络请求面板筛选 /api/post/item_list/ 接口。

选中该接口请求,切换至载荷选项卡,可以看到接口携带40~50个查询参数,参数繁杂难以快速定位加密字段。
滑动至参数列表最底部,末尾三组加密参数分别为 msToken、X-Bogus、X-Gnarly。本文核心逆向目标 X-Gnarly 加密参数 固定长度为 332位。

二、油猴Hook脚本监控,拦截X-Gnarly生成断点
之前教程《油猴脚本逆向实战:一行Hook脚本代码,精准定位加密参数入口》介绍过通过篡改浏览器原生API定位加密逻辑,本次沿用相同思路,编写专用URL监控脚本拦截 X-Gnarly 加密参数 生成断点。
新建油猴脚本,完整代码如下:
// ==UserScript==
// @name X-Gnarly URL 断点监控
// @namespace http://tampermonkey.net/
// @version 5.0
// @description 只监控 window.URL 构建时是否包含 X-Gnarly
// @author You
// @match *://*/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
console.log(' X-Gnarly URL 断点监控已启动');
const OrigURL = window.URL;
window.URL = function(url, base) {
if (typeof url === 'string' && (url.includes('X-Gnarly') || url.includes('X-Gnarly'))) {
console.log(' [new URL]', url);
debugger;
}
return new OrigURL(url, base);
};
window.URL.prototype = OrigURL.prototype;
console.log(' 已监控 window.URL');
})();
以上代码为精简提炼版本,完整拦截URL构造时携带X-Gnarly的调用,自动触发调试断点。

脚本启用后刷新TikTok作品页面,会自动触发debugger断点,停留在 window.URL 构造函数内。展开调用栈可见调用链路:window.URL → 匿名函数D → k函数。

三、调用栈逐层回溯,锁定核心生成函数
顺着调用栈向上追溯,在匿名函数D中发现 n.o[7] 已拼接X-Gnarly参数,继续向上进入 k 函数。
在k函数入口断点观察,i[0].url、t.o[6][0] 均已携带完整加密参数链接。持续向上回溯会反复循环匿名函数与k函数,无有效上层入口,切换分析思路。
在k函数返回语句 return M(u, 4) 添加日志断点,打印返回值。控制台持续大量输出字符串,其中332位长度字符串与抓包获取的X-Gnarly完全匹配。
添加条件断点精准拦截参数生成:
M(u, 4).length == 332
条件断点触发后回溯顶层调用位置,找到生成入口函数:
t.o[806] = {
v: function(n, r, i) {
return k(88, t, this, arguments, 0, 54)
}
}
结论:v 函数为X-Gnarly生成入口,内部调用虚拟机函数k,最终通过 M(u,4) 返回加密字符串。同时捕获函数入参三组原始数据:
- n 参数:固定加密密钥串
- r 参数:固定字符编码映射串
- i 参数:布尔控制值 false
四、提取加密入口函数,改造webmssdk.js导出签名方法
加密逻辑全部存放于 webmssdk.js,完整保存该JS文件,修改源码挂载全局签名方法,方便外部调用。
修改代码片段,将v函数暴露至全局window对象:
t.o[806] = {
v: function(n, r, i) {
return k(88, t, this, arguments, 0, 54)
}
},window.sign = t.o[806].v,
t.o[808]

五、NodeJS本地离线运行,批量生成X-Gnarly
新建 main.js 模拟浏览器window运行环境,加载修改后的webmssdk.js,离线调用签名函数生成参数。
main.js 完整运行代码:
global.window = global;
global.self = global;
(function(global) {
'use strict';
function ensureGlobalDwInfl(value) {
if (typeof global !== 'undefined') {
if (typeof global.dwInfl === 'undefined' && value !== undefined) {
global.dwInfl = value;
}
return global.dwInfl;
}
return value;
}
if (typeof module !== 'undefined' && module.exports && typeof require === 'function') {
var env = null;
var signModule = null;
try { signModule = require('./sign.js'); } catch (e) {}
ensureGlobalDwInfl(signModule || (typeof global.dwInfl !== 'undefined' ? global.dwInfl : undefined));
module.exports = { env: env, dwInfl: global.dwInfl, signModule: signModule };
return;
}
})(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this);
// 固定入参
n = 'Kzx86d¶´ò]x14%Çx1Bx99ëx07x83Öx88:£$¼jZx1D*ðgx8Bx89¾YáÝ0Ex1BfÄ$U<ÿQx9C¸çx9Eüx8Fx9Eâx87x94{x90x92L`bGÇêFT6x01%¨x9DÍÆå`*x8F¾tx10ÿx¶!°3»ùG[=Éx18¼Ùx1Cx85ÙçwJçØýá!ÏÃÎZx01²x9CÂà'x04x83px1Eáx03x97þx84x98¨Ù»b¹x9Aþ¹GÑGIx80&æ¾±Æôx8A5ºx93x80¸x96x8Ex83S/x80"x96ºx90x9Cx04Ðx05x8Fx8Dx11Äx1Cx98øx84µÈ°{_x1F=Íýx8FKx0Ex7F/-x05.¾++×Y¤}Dzx0Ex03x1A¶x82Vhzx01x1Cx1F$7cÊüx89£ÁJµÚx1F/dx8FÃçx9Dx03eñ>ðx8B¤GAö x88]±°é÷'
r = 'u09tbS3UvgDEe6r-ZVMXzLpsAohTn7mdINQlW412GqBjfYiyk8JORCF5/xKHwacP='
// 调用全局签名函数
s = window.sign(n, r,false)
// 输出结果与长度
console.log(s);
console.log(s.length);

执行 node main.js 即可离线生成合法X-Gnarly加密串,无需浏览器环境,可集成爬虫、接口请求脚本使用。
整套TikTok逆向操作流程全部演示完毕,下面整理实操过程中高频遇到的各类问题。
六、TikTok 逆向过程常见踩坑问题汇总
- 问:Hook脚本无法触发断点?
答:脚本运行时机设置为document-start,关闭浏览器缓存,无痕窗口重新加载页面。 - 问:Node运行提示window.sign不存在?
答:确认webmssdk.js已挂载全局sign函数,文件路径引用无误。 - 问:生成的X-Gnarly请求返回403?
答:n、r内置密钥会随TikTok版本更新变动,需重新抓包提取最新参数。 - 问:调用栈循环无法找到顶层入口?
答:放弃逐层单步调试,改用返回值条件断点拦截生成时机。