做渠道的朋友常年问我同一个问题:「我的量看起来没毛病,IP 分散、UA 齐全、国家也对,为什么归因平台还是判我作弊、广告主还是拒付?」我的回答每次都一样:你看到的是 HTTP 层的自己,反作弊系统看到的是 TLS 层和网络层的你。 这两个「你」经常不是同一个人——而假流量被识破,十有八九就死在这个落差上。
这篇文章把 MMP(AppsFlyer、Adjust 这类归因平台)和反作弊系统在网络层、传输层、请求层到底看什么信号讲透:TLS ClientHello 里每个字段为什么能当指纹用、JA3 到 JA4 这几年发生了什么、HTTP/2 时代连 SETTINGS 帧都在出卖你、IP/ASN 画像和时序行为又是怎么补刀的。最后专门加一节,教广告主怎么读懂拒付报告里那些看起来像黑话的证据。
先立好定位:这是一篇防御与认知科普。只讲检测方怎么识别,不教任何人怎么伪装——帮广告主看懂自己付出去的钱在买什么,帮正规渠道搞明白自己为什么被误伤、点损为什么高。
一、一次点击,其实在五个层面同时「自报家门」
很多人以为一次广告点击就是一个 URL 请求,带上 UA 和参数就完事了。实际上从反作弊视角看,每次点击至少在五个层面留下痕迹,而且层与层之间可以互相对质:
| 层面 | 关键信号 | 能暴露什么 |
|---|---|---|
| 网络层 | IP、ASN、IP 类型(住宅/机房/代理/VPN) | 请求发起的物理环境 |
| 传输层(TLS) | ClientHello 特征、JA3/JA4 类指纹 | 发请求的到底是什么软件栈 |
| 协议层(HTTP) | HTTP 版本、HTTP/2 SETTINGS、Header 顺序 | 请求用的是哪套 HTTP 实现 |
| 应用层 | UA、设备参数、语言时区,以及它们与下层是否自洽 | 请求「声称」自己是谁 |
| 行为层 | 点击时序、跳转链路、CTIT、安装率、留存 | 像不像真人在用真设备 |
图1:反作弊检测信号分层图——越往下越难伪装,越往上越接近「人」
关键在于:应用层的一切都是「声明」,可以随便写;传输层和网络层是「体质」,改起来难得多。 UA 字段填什么完全由发请求的人说了算,但 TLS 握手时客户端支持哪些加密套件、扩展按什么顺序排列,是由底层网络库在编译时就定死的——iPhone 上的 Safari、安卓的 Chrome、Python 的 requests、Go 的默认 http client,各自的 TLS「口音」截然不同,而且极难装。
反作弊系统的核心思路就一句话:把「声明身份」和「真实身份」放在一起对照,冲突就上分。 下面逐层拆开。
二、TLS 指纹:ClientHello 的每个字段都是「口音」
TLS 握手的第一个包叫 ClientHello。这个包在加密建立之前发出,全程明文,服务器(以及服务器前面的任何检测节点)不需要解密任何东西就能完整读到。客户端在里面公开宣布一长串信息,而每一项都带着实现方的烙印:
Cipher suites 列表与顺序。 客户端把自己支持的加密套件按偏好排成一个有序列表。支持哪些、排什么顺序,是 OpenSSL、BoringSSL、Go crypto/tls、苹果 Network 框架这些库的作者在代码里写死的偏好,普通调用方根本碰不到这个排序。Chrome 和 Safari 支持的套件集合有重叠,但顺序不同;Python requests 底下的 OpenSSL 又是另一副面孔。同一份「菜单」,不同的「点菜顺序」,就是口音本身。
Extensions 列表与顺序。 现代 ClientHello 里塞着十几到几十个扩展:SNI、ALPN、supported_versions、key_share、session_ticket、signature_algorithms……哪些扩展出现、按什么顺序出现,同样由实现决定。很多轻量 HTTP 库发出的扩展集合明显偏瘦——真实浏览器为了兼容性和性能塞进去的那些扩展,脚本库压根不需要,也就不会带。
Supported groups 与 signature algorithms。 支持哪些椭圆曲线、接受哪些签名算法,反映的是加密库的版本和平台的安全策略。一个声称是最新 iPhone 的请求,却只支持一套老旧曲线组合,这本身就说不通。
ALPN。 应用层协议协商,浏览器几乎一定会报 h2, http/1.1(宣布自己会说 HTTP/2)。很多脚本默认只报 http/1.1,甚至不带 ALPN 扩展。一个「现代浏览器」不会说 HTTP/2?疑点+1。
GREASE。 这是最有意思的一个。Chrome 系(BoringSSL)会故意往 cipher suites、extensions、supported groups 里插入一些随机的保留值(RFC 8701),本意是防止服务器实现僵化。但站在检测方视角,GREASE 的有无和插入位置本身成了指纹的一部分:真 Chrome 一定带 GREASE,而大量脚本库根本不知道 GREASE 是什么。一个 UA 声称是 Chrome、ClientHello 里却干干净净没有一个 GREASE 值的请求,基本可以直接标记。
把这些字段拼起来做个哈希,就是业内熟知的 JA3:取 TLS 版本、cipher 列表、扩展列表、曲线、点格式五段,串起来算 MD5。一个 32 位的哈希,就能把「这次连接是谁的软件栈发起的」压缩成一个可检索、可聚类的标签。
JA3 到 JA4:一场检测与反僵化的军备升级
JA3 用了很多年,但它有个致命弱点:对顺序敏感。2023 年起 Chrome 为了对抗指纹僵化,开始在每次连接时随机打乱扩展顺序——同一个 Chrome,每次握手 JA3 都不一样,基于 JA3 的精确匹配一夜之间失灵了大半。
