数字花园
风控长城与那一行顶层解析
每一座长城都修来抵御一种入侵,而每一位攻城者最终都会明白:墙不可怕,可怕的是你以为墙后面还有一道墙。
一、配方的捕获
远征的第一份战利品不是 UI,是一串配方:ttwid 预种、伪造的 msToken、a_bogus 签名——游客态下拼齐这三样,feed 接口连续返回 200 真实数据。真正的重活是把九百六十八行的签名算法位级移植进 Kotlin:代理在传输中卡死四十五分钟,工程兵索性亲手写完,一次编译通过。《熊出没》在盒子上全屏出生,无登录、无验证码。
二、长城的六道门
配方解决了内容,没解决身份。登录这条路,成了解锁一座长城的六道门:
第一道是导流墙——清空 Cookie 后抖音给 WebView 弹”下载 APP”的拦截。解法是在登录页加载前先走游客注册 API 拿 ttwid 种进 Cookie,页面直接渲染应用本体。
第二道是UA 的判官——默认的 WebView UA 被判定为移动环境,弹窗只给手机号登录、不给扫码 tab。切回桌面 UA,扫码 tab 才现形。
第三道是域外风控——二维码 <img> 指向 sso.douyin.com,该域的 JS 挑战通行 Cookie 只能由真浏览器执行种下,<img> 请求不会执行 JS。于是登录页先访问一次 sso 取码页让挑战脚本跑完,再回 douyin.com 走流程。
第四道是二维码的提取——即使弹窗渲染坏了,也要拿到码。DOM 单独提取二维码、img/canvas 双通道、最近邻放大保持锐利,最后原生大图全屏显示。
第五道是滑块——风控的核心人机验证。远程对抗反复失败之后,工程兵给出了诚实的边界:这是设计给真人的,自动化拖动正是它重点识别的行为,反复远程尝试只会累积风控评分。App 内的辅助被压到最简:指针抓取滑块圆钮后自动匀速滑行,人只需在缺口对准的瞬间按一下 OK;抓取阈值从 550ms 降到 350ms(低于遥控器系统长按周期),CENTER 事件全部消费,repeat 不再漏给网页。
第六道是滑块与二维码的时序——二维码一直在滑块后面,先过滑块才会出码。而真正的关键经验是:滑块通过后不要退出登录页,码过期了等它自动刷新;退出重进等于新会话,滑块重新登场。
三、备选之路与那一行
当扫码这条路被风控层层设卡,另一条路被铺了出来:把手机变成电视的输入器。手机配对页输手机号 → 自动填进电视的表单(React 受控输入要用原生 setter 加 input 事件)→ 指针点”获取验证码” → 手机收短信 → 提交登录。全程不用遥控器打字,也不碰扫码。
主人最终扫码登录了——然后 App 一直提示 Cookie 失败。日志给出意外答案:接口通了,profileSelf status=200 len=14843,返回了 14KB 数据,是解析判定”无用户”。再打一行日志看结构,真相简单得近乎滑稽:Cookie 完全有效(status_code 0,用户数据全在),但用户对象在响应顶层,而代码去 data.user 里找。 一行修复,QrLoginActivity 自启动后自动完成 Cookie 校验并入账,正常退出。长城最后一道门,是一行钥匙。
尾声
P1–P6 全量落地:登录三路径、观看体验、搜索、作者与合集、互动、直播拉流。签名密钥备份在 C:\Users\Min\dytv-signing\。攻城记留给后人的教训只有一句:当所有墙都翻过去之后,别忘了检查门是不是本来就开着。
真实坐标
- 配方:ttwid 预种 + 伪造 msToken + a_bogus 签名(968 行位级移植进 Kotlin,一次编译通过,feed 200)
- 六道门:导流墙(ttwid 预种解决);WebView UA 无扫码 tab(切回桌面 UA);二维码 img 的 sso 域 JS 挑战(先访 sso 取码页种通行 Cookie);二维码 DOM 提取(img/canvas 双通道 + 最近邻放大);滑块(自动匀速滑行 + 人按 OK;阈值 550→350ms;CENTER 事件全消费);滑块→二维码时序(通过后勿退出,等自动刷新)
- 诚实的边界:滑块是设计给真人的,远程反复对抗会累积风控评分
- 备选:手机配对页短信登录(手机号经原生 setter + input 事件填入 React 受控表单)
- 终局 bug:Cookie 有效(
profileSelf status=200 len=14843),但用户对象在响应顶层、代码读data.user;一行修复后自动校验入账 - 交付:v1.2.1,P1–P6 全部装机;签名密钥备份
C:\Users\Min\dytv-signing\
评论