在浏览器控制台敲一行 Math.tanh(0.8),Linux 上的 Chrome 给出 0.6640367702678491,苹果电脑给出 0.664036770267849,Windows 给出 0.6640367702678489——最后几位比特全都不一样。这不是浏览器算错了,而是从 Chrome 148 开始,V8 引擎悄悄换了这个函数的计算方式:一次数学运算,就能测出你用的到底是哪套操作系统,不用看 User-Agent,也不用碰 canvas、WebGL 这些反检测早就盯防的老地方。
追出这条线索的是 Scrapfly,一家靠伪装浏览器指纹吃饭的反检测服务商。它顺着一个几乎没人关心的 V8 提交(commit c1486295ae5)往回查,发现这个函数从"跨平台结果完全一致的自研实现",换成了"直接调用操作系统底层数学库"。这确实是此前被忽略的一条指纹通道,但把它说成"一次调用即签名"还为时过早——真正决定杀伤力大小的,是反爬系统愿不愿意把它算进判定逻辑。
Chrome 148换的是什么
在这次改动之前,V8 计算 Math.tanh 用的是自己打包的 fdlibm 移植版本,不管浏览器跑在哪个系统上,结果都是同一串比特。commit 标题写明是"用 std::tanh 替换自研 tanh",直接改成调用宿主系统的 libm:Linux 走 glibc,macOS 走苹果自己的 libsystem_m,Windows 走 UCRT。这一改动最早出现在 V8 14.8.57,对应 Chrome 148;Chrome 147 及更早版本不受影响。
需要说清楚的是,Chrome 148 的官方发布说明里并没有单独记载这处改动,"对应到 148"这个版本号是从提交时间线倒推出来的,缺一份 Chromium 官方版本历史的独立交叉确认——细节层面还留了个尾巴,值得后续核实,但不影响"改动确实存在、确实泄露"这个主判断。
规范留了口子,不是谁犯了错
IEEE 754 只规定 double怎么存,从没要求 sin、cos、tanh、exp 这类超越函数必须"正确舍入"——把每个输入都算到理论最精确的那一位太贵,各家系统的 libm 都用自己的多项式系数和查表法换速度,代价是约四分之一的输入上会差 1 个最小可表示单位(ULP)。这不是谁的实现出了 bug,是标准给工程实现留的自由度,Chrome 148 只是把这份自由暴露到了 JS 层面。
Scrapfly 实测三台真机上的 Chrome 150,把这个分歧摆得很直观:
| 调用 | Linux(glibc) | macOS(libsystem_m) | Windows(UCRT) |
|---|---|---|---|
Math.tanh(0.5) | 0.46211715726000974 | 0.46211715726000974 | 0.46211715726000974 |
Math.tanh(0.7) | 0.6043677771171636 | 0.6043677771171635 | 0.6043677771171635 |
Math.tanh(0.8) | 0.6640367702678491 | 0.664036770267849 | 0.6640367702678489 |
Math.tanh(0.9) | 0.7162978701990245 | 0.7162978701990245 | 0.7162978701990244 |
tanh(0.5)三系统全一样,测不出任何东西;tanh(0.8)三系统各不相同,是一次就能定位操作系统的"强探针"。有意思的是,这类风险早有人预警过——Mozilla 社区此前就讨论过用 fdlibm 来规避数学函数带来的跨平台指纹泄露,Chrome 148 相当于自己把已知的坑又踩了一遍。
- 提醒.V8 内置的其他数学函数(
exp、pow、atan、sin、cos)仍然用自带实现,全平台一致,只有tanh走了系统库——这种"只有一个函数不一样"的不对称,本身也是可以被检测方拿来交叉验证的信号。
macOS更麻烦:一台机器上两套数学库
CSS 里的 sin()、cos()、atan2() 走的是另一条代码路径,角度先按度做归约再调系统库,七个 CSS 三角函数全都会泄露操作系统。更麻烦的是苹果自己:Apple Silicon 上同时存在标量的 libsystem_m 和向量化的 Accelerate 框架,两者算法不同,同一个输入两套库能差出一位甚至更多。Math.tanh、CSS 三角函数走的是标量库,Web Audio 里的 FFT 和滤波器走的是 Accelerate——伪装 macOS 时,光"学会苹果怎么算"还不够,还得知道浏览器在哪个调用点用哪一套。
反爬系统真正在意的是什么
真正落地的反机器人系统,判断依据从来不是一次探针的结果,而是跨信号是不是自洽——声称的操作系统,和 TLS 指纹、HTTP/2 帧序、时序特征对不对得上。Math.tanh 更可能是打分体系里加的一个新维度,而不是单独就能判定真假的开关。原文"一次调用即签名"的说法,在实际检测场景里大概率要打折扣。
同样值得留意的是 Scrapfly 自身的位置:它是靠帮爬虫伪装浏览器指纹赚钱的商业公司,同类玩家还有 Bright Data、Oxylabs、ZenRows、Apify 这些名字。文章讲清楚了发现过程和技术难点,却没有交代自己具体怎么修——是给每台机器配上对应系统的真实 libm,还是纯软件模拟,语焉不详。这不代表结论不成立,但读这类"技术揭秘"时,多一分审慎总没坏处。
标准没逼出漏洞,工程选择才把它变成了指纹。
接下来值得盯的,是主流指纹识别厂商是不是已经把这条信号用进了商用产品,以及 V8 团队会不会针对 Math.tanh 的这次改动做出回应——目前公开信息里都还看不到答案。
