_signature:字节系防护的"入门级样本"
今日头条 web 端 feed 接口 www.toutiao.com/api/pc/list/feed 带 _signature 参数。它和抖音的 a_bogus 同属字节系安全 SDK(acrawler 家族)的产物,但防护强度低一个档次——没有上 VMP,是普通混淆。这使它成为进军抖音之前的最佳热身目标:同一套方法论,更低的对抗强度。
定位:标准 hook 流程
const _open = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(m, u) {
if (u.includes('_signature=')) debugger;
return _open.apply(this, arguments);
};断下后回溯调用栈,签名函数在 pc 端 chunk 里,入参是 url 的 query 部分。字节的签名入口也常挂在 window 全局(acrawler 家族传统),Console 里翻翻 window.byted_acrawler、window._signature 之类的属性,可能有惊喜。
算法结构:自研变换,没有标准密码学
跟进签名函数后会发现,_signature 的生成是纯自研混淆变换:
- 取 url query 字符串(不含 _signature 本身)
- 拼接时间戳和内置 salt
- 两轮变换:字符级位移+异或,然后查表替换
- base64 变体输出(
+/替换成-_,URL 安全)
没有 md5、没有 HMAC——这意味着两件事:一是算法还原后没有"密钥"概念,换 salt 就是全部防护;二是抠代码比手搓划算得多,整个函数连同依赖不到三百行。
抠代码实战
// sign.js —— 最小运行环境
global.window = global;
window.navigator = { userAgent: UA, platform: 'Win32', webdriver: false };
window.document = { cookie: '', referrer: 'https://www.toutiao.com/' };
window.location = new URL('https://www.toutiao.com/');
window.screen = { width: 1920, height: 1080 };
// 粘贴抠出的签名函数及其依赖(保持原始混淆状态,别格式化)
require('./toutiao_sign_chunk.js');
// 调用入口以实际抠取为准
console.log(window.__sign__('channel_id=0&max_behot_time=0&...'));ttwid:比签名更重要的设备标识
头条系接口的真正门槛其实是 ttwid cookie——字节系的设备级标识,由 ttwid.bytedance.com/ttwid/union/register/ 接口注册下发,长期有效。没有 ttwid 或 ttwid 异常,签名对了也返回空数据。
协议模拟流程:
- 请求 ttwid 注册接口拿设备 cookie(或直接访问头条首页自动种下)
- ttwid 固定存档,作为"设备档案"的核心
- 每次请求带 ttwid + 实时计算的 _signature
精髓:字节系的三个共性认知
- 防护强度与业务灰产价值正相关。头条(资讯)< 抖音(短视频)< 电商/支付,同一 SDK 家族,对抗强度逐级递增。从头条入手建立方法论,逐级打怪是合理路径。
- msToken/ttwid/_signature 三位一体。和抖音一样,设备标识(ttwid)、会话令牌(msToken)、请求签名(_signature)分开校验,只模拟签名必挂。
- 对拍标准是"接口放行"。_signature 含时间戳和随机数,两次生成结果不同是正常的——别追求和浏览器输出相等,拿你生成的签名发真实请求,返回数据就是胜利。
总结
- _signature 是 acrawler 家族的轻量版,无 VMP,适合练手
- 算法是自研变换无密钥,抠代码优于手搓
- ttwid 设备标识是隐形门槛,签名之外必须管理
- 头条→抖音→电商,是字节系逆向的合理进阶路线