)
【免费下载链接】Cybersecurity-ProjectsBuilding 70 Projects ranging from beginner to advanced so anyone can — learn from, build upon, use as a reference, or even copy directly. Gamified Cybersecurity learning 项目地址https://gitcode.com/gh_mirrors/cy/Cybersecurity-Projects点击查看免费下载本文以ja3-ja4-tls-fingerprinting项目的教学文档 01-CONCEPTS.md 为主体系统讲解被动 TLS 指纹识别技术背后的全部核心概念为什么一个明文的 ClientHello 就能暴露客户端软件身份、JA3 为何在浏览器流量上失效、JA4 如何通过先排序再哈希存活下来、GREASE 噪声为何必须剔除、JA4 家族如何跨层交叉验证揭穿伪装以及被动 QUIC 解密、TCP 流重组与内存安全等底层机制。读完本文你将掌握这套传感器的工作原理与设计动机并能结合仓库源码ja4.rs、grease.rs、quic.rs 等深入理解每个指纹从线上字节到哈希字符串的完整推导过程。1. ClientHello 是一份告白TLS 连接的第一步是客户端发出ClientHello——这是线上传输的第一条真实消息并且以明文传输因为此时通信双方还没有协商出任何加密密钥。在这条明文中客户端精确地列出了一切它是如何配置的TLS 版本它支持哪些协议版本密码套件cipher suites按偏好顺序列出它愿意采用的套件扩展列表extensionsSNI、ALPN、supported groups、signature algorithms、key share 等椭圆曲线supported groups它能完成哪些 ECDHE 曲线协商EC 点格式EC point formats它理解哪些点格式。这些内容没有一项是秘密而它们全部由软件而非用户决定。Chrome 的 TLS 栈给出的套件集合与顺序和 Firefox 不同、和 Go 的crypto/tls不同、和 Python 的ssl不同也和恶意软件内部自带的 TLS 库不同。同一版本 Chrome 的两次安装产生的 ClientHello 几乎完全相同Chrome 与一个 Python 脚本产生的 ClientHello 则一眼可辨。一个典型的 ClientHello 结构如下ClientHello ├── legacy_version: 0x0303 (TLS 1.2, frozen here even for TLS 1.3) ├── random: 32 bytes (ignored, it changes every time) ├── cipher_suites: [0x1301, 0x1302, 0x1303, 0xc02b, ...] ├── compression_methods: [0x00] └── extensions: ├── 0x0000 server_name example.com ├── 0x000a supported_groups [0x001d, 0x0017, ...] ├── 0x000d signature_algorithms [0x0403, 0x0804, ...] ├── 0x0010 alpn [h2, http/1.1] ├── 0x002b supported_versions [0x0304, 0x0303] └── ...指纹fingerprint就是把这种结构压缩成一段简短、稳定的字符串。这一洞见源自 Salesforce 在 2017 年的工作ClientHello 的形状即使在其他所有标识都被伪造的情况下也能识别出背后的软件。攻击者可以随意更换 IP、域名和证书这些都不花钱但要更换 TLS 栈就意味着重写自己的工具链。在仓库中ClientHello 的解析位于 parse/hello.rs返回带生命周期的ClientHellopkt通过server_name、alpn_protocols、supported_groups等访问器按需解码单个扩展——这正是形状的原始来源。2. JA3最初的方案以及它为何死去JA3以三位作者 John Althouse、Jeff Atkinson、Josh Atkins 命名以最直观的方式构建指纹按顺序取出五个字段——版本、密码套件、扩展、supported groups、点格式——每一项写成十进制数字并连接最后取 MD5769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0 │ MD5 ▼ ada70206e40642a3e4461f35503241d5上面这个字符串就是仓库中 ja3.rs 里salesforce_client_vector_one测试所固定的 Salesforce 原始向量769即0x0301TLS 1.0 的十进制表示现代 TLS 1.2/1.3 客户端会在 legacy 字段写入771真实版本改由supported_versions扩展表达。JA3 的构建规则字段间以-连接值、字段间以,连接、最后 MD5在 ALGORITHMS.md 中有逐字节说明五个字段中密码套件、扩展类型、supported groups 都要先剔除 GREASE 再按线序wire order连接点格式直接连接。多年间 JA3 工作得非常好Cobalt Strike 的默认 profile、Trickbot、Emotet 各自有已知的 JA3abuse.ch SSLBL 等情报源发布了数千个恶意软件 JA3 哈希防御者可以直接封禁。但随后它坏掉了原因非常具体且具有教学意义JA3 按客户端发送扩展的原始顺序做哈希。2021 年Chrome随后是其他浏览器开始做 TLS 规范明确允许的一件事在每次连接时洗牌扩展顺序作为一种有意的反固化anti-ossification手段。密码套件没变、扩展集合没变但顺序每次都变。由于 JA3 把顺序也哈希进去每次 Chrome 连接都产生一个新的 JA3 哈希。一夜之间JA3 对浏览器指纹识别失效了——一个每次都在变的签名什么都识别不了。但 JA3 对恶意软件并未失效这正是本项目仍然计算它的原因恶意软件工具很少洗牌扩展、公共情报源仍以 JA3 表达而且在真实浏览器流量上观察 JA3 哈希在稳定的 JA4 旁边不断碎片化是理解为什么需要后继者最清晰的一次演示。值得特别记住的一个教训是Emotet 与 Cobalt Strike 的碰撞两者都曾出现在同一个 JA3 哈希72a589da586844d7f0818ce684948eea下。不是因为它们是同一个恶意软件而是因为它们基于同一个 TLS 库、以相同方式配置。指纹识别的是工具链toolchain。把指纹命中当作特定攻击者的实锤是错误把它当作来自已知可疑工具链才是正确。3. JA4先排序再存活JA4 由 FoxIO 于 2023 年发布用一个改动解决了顺序问题在哈希之前先对密码套件列表和扩展列表排序。如果客户端提供的扩展集合是稳定的、只是顺序随机那么排序就丢掉了随机性、保留住了信号。Chrome 被打乱的扩展排序回去后每次都相同于是 JA4 重新变得稳定。JA4 还改变了格式使其部分可读——这是 JA3 那种不透明的 MD5 不具备的。一个 JA4 指纹由三个下划线分隔的部分组成t13d1516h2_8daaf6152771_e5627efa2ab1 │ │ │ │ │ └── 排序后扩展签名算法的截断 SHA-256 │ └── 排序后密码套件的截断 SHA-256 └── 10 个可读字符 t transport: tTCP, qQUIC, dDTLS 13 TLS 版本: 1.3 d SNI 存在 (ddomain, ino SNI) 15 密码套件数量 (15) 16 扩展数量 (16) h2 第一个 ALPN 值 (h2 HTTP/2)一眼就能读出前缀的含义TCP 之上的 TLS 1.3带 SNI15 个密码套件16 个扩展说 HTTP/2。前缀不同的两个客户端在比较哈希之前就能看出是不同软件哈希部分则进一步区分共享同一前缀的客户端。先排序和可读前缀这两个设计选择正是 JA4 在 JA3 失败之处取得成功的原因。其逐字节构造细节参见 ALGORITHMS.md。从源码看核心实现在 ja4.rs前缀由ja4_prefix构造传输标记 ja4_version_code(select_version(ch)) SNI 标记 两位套件数 两位扩展数 两位 ALPN 字符。其中版本取自supported_versions中最高的非 GREASE 值select_version而不是冻结在 1.2 的 legacy 字段套件数与扩展数统计时同样剔除 GREASE并封顶在 99。ALPN 两个字符alpn_chars取第一个 ALPN 协议的第一个与最后一个字节若都是 ASCII 字母数字则直接输出h2保持h2http/1.1变h1否则退回用十六进制编码的首尾两个 nibble无 ALPN 时为00。值得注意源码注释明确指出这里按 JA4 规范实现而非 FoxIO Python 参考实现因为两者在非字母数字 ALPN 值的处理上存在分歧规范本身更信息丰富、更可移植。套件哈希剔除 GREASE每个套件格式化为四位小写 hex 后按字符串排序以,连接取截断 SHA-256。FoxIO 向量8daaf6152771正是这样从002f,0035,...,cca9这一有序 CSV 得到的。扩展哈希ja4_extension_raw扩展类型先剔除 GREASE 以及已在前缀中体现的 SNI0x0000与 ALPN0x0010排序、连接若存在signature_algorithms扩展则追加_与签名算法——但签名算法保持原始顺序不排序因为其顺序本身携带意义且稳定。全部内容取截断 SHA-256。所谓截断 SHA-256指的是取 SHA-256 输出的前 12 个 hex 字符即前 6 字节。而 JA3 使用完整 MD532 个 hex 字符。哈希选择并非安全考量指纹是标识符而非认证器JA3 用 MD5 是为了与原始定义及所有公共 JA3 情报源完全一致JA4 改用 SHA-256 则是因为这是一次全新定义、没有历史兼容负担。4. GREASE客户端故意撒谎如果转储一个真实 Chrome 的 ClientHello你会看到类似0x0a0a、0x1a1a、0x2a2a的密码套件和扩展值。它们并不是真的。它们是GREASEGenerate Random Extensions And Sustain ExtensibilityRFC 8701 定义——客户端故意注入的随机保留值目的是让中间盒和服务器不能假设固定的值集合从而保持生态系统可扩展避免未来的新客户端因为添加新值而被打断。对指纹识别而言忽略 GREASE 就是中毒。GREASE 的意义就在于其值每次连接随机任何把 GREASE 包含进去的指纹都会每次都变——这正是 JA3 顺序问题的另一种变装。因此JA3 和 JA4 都会在哈希前从每个列表里剔除所有 GREASE 值。GREASE 值集合是固定且已知的形如0x?a?a共十六个剔除只是一个简单的过滤器offered: [0x1a1a, 0x1301, 0x1302, 0x1303, 0xc02b] - 0x1a1a is GREASE hashed: [0x1301, 0x1302, 0x1303, 0xc02b] - stripped before hashing本项目将 GREASE 表集中存放在一处grease.rs并对每个指纹应用同样的剔除逻辑——因为一个忘记剔除 GREASE 的指纹不是偶尔出错而是在每一个现代客户端上都会出错。源码层面的一个精巧细节is_grease并未线性扫描 16 项常量表而是利用 GREASE 集合的结构规律做一次常数时间判断——两个字节相等且低 nibble 为0xapub const fn is_grease(value: u16) - bool { let high (value 8) as u8; let low (value 0x00ff) as u8; high low (low 0x0f) 0x0a }其单元测试table_matches_structural_check遍历整个u16空间断言结构判断与常量表逐值等价从而证明这单个比较对全部 65536 个可能值都正确——这正是在每个数据包的路径上保持廉价的实现思路。5. JA4 是一个家族一个指纹永远不够JA4 是 TLS 客户端指纹但同一思路适用于其他协议层整个家族统称 JA4彼此交叉验证。本项目计算它们全部指纹识别对象读取位置JA4TLS 客户端软件ClientHelloJA4STLS 服务器软件ServerHelloJA4HHTTP 客户端软件明文 HTTP 请求JA4X证书签发工具链X.509 证书JA4T客户端 TCP/IP 栈操作系统SYN 包要计算多个指纹的原因是规避evasion。不断增长的一类工具curl-impersonate、utls等存在的目的就是伪造浏览器的 ClientHello它们能让脚本的 JA4 与真实 Chrome 完全相同于是只依赖 JA4 的过滤器会放行它们。但伪造一层并不能伪造其他层一个运行在 Linux 上的 Python 脚本即使伪装成 Chrome 的 TLS发出的仍然是Linux的 TCP SYN所以它的JA4T 出卖了它它仍然按脚本的顺序发送 HTTP 头所以它的JA4H 出卖了它它仍然声称User-Agent: ...Chrome...这现在与它的 TCP 栈互相矛盾。这种跨层不一致是本工具产生的最有价值信号也是头条检测规则。从 02-ARCHITECTURE.md 可知detect.rs 中的六个规则正是围绕这一理念设计known_bad命中恶意情报源、ua_mismatch头条规则JA4 与同一 IP 的 User-Agent 不一致、os_mismatchJA4T 的 OS 与 User-Agent 声称的 OS 不一致、first_seen、fp_rotation、monoculture。其中ua_mismatch与os_mismatch由 signal.rs 提供启发式判断例如 Windows 与 Linux 的 TCP 栈在 SYN 选项上的差异足以让一个 JA4T 与声称 Windows 的 User-Agent 互相核对。这就是浏览器伪装军备竞赛的教训。只要防御者只对 TLS 做指纹识别攻击者就只需要伪造 TLS。防御的关键不是更好的单指纹而是跨层关联——攻击者独立控制这些层而一个把所有层都完美伪造的攻击者实际上等于重造了一个真实浏览器这既昂贵又罕见。各指纹的构造实现分散在 ja4h.rsJA4H读取方法、HTTP 版本、是否携带 cookie 与 referer、其余头名称的计数与顺序、accept-language、cookie 名称与值、ja4x.rsJA4X签发者/主体/扩展三组 OID 各取截断 SHA-256按 DER 书写顺序、ja4t.rsJA4Twindow_size_option_kinds-mss-window_scale四段式缺失的 MSS/窗口缩放记为0如64240_2-1-3-1-1-4_1460_8。各实现的字节级构造详见 ALGORITHMS.md。值得一提的边界JA4H 只适用于明文 HTTPHTTPS 下请求被加密、HTTP/2 下头被 HPACK 压缩被动观察者都读不到JA4X 被动读取只在TLS 1.2 及更早有效TLS 1.3 将 Certificate 消息加密这些在 CONFORMANCE.md 中被明确记录为被动观察的性质而非实现缺陷。6. 被动 QUIC加密不等于保密QUICHTTP/3 之下的传输层在自己的前几个包里携带 TLS 握手而这些包是加密的——看起来被动观察者被完全排除在外。但事实并非如此原因是一个精妙的细节QUIC 的Initial包虽然是加密的但密钥是从**连接的目标连接 IDDestination Connection ID**派生出来的而这个 ID以明文形式写在包头里。这种加密的目的是对付那些会乱改包的笨拙中间盒而不是保守握手的机密。任何读到该包的人都能派生同样的密钥、解密 Initial、读到里面的 ClientHello就像读 TCP 一样。QUIC Initial packet ├── header (cleartext): ... Destination Connection ID D ... └── payload (encrypted under a key derived from D) │ HKDF(D) ──────────┘ anyone can compute this ▼ decrypted CRYPTO frames ▼ TLS ClientHello - q-transport JA4本项目从每个包自身的连接 ID 派生客户端 Initial 密钥遵循 QUIC v1 的 RFC 9001 与 v2 的 RFC 9369不依赖任何服务端机密、也不需要方向判定。完整密钥调度在 ALGORITHMS.mdinitial_secret HKDF-Extract(salt INITIAL_SALT[version], ikm DCID) client_secret HKDF-Expand-Label(initial_secret, client in, 32) key HKDF-Expand-Label(client_secret, version key label, 16) iv HKDF-Expand-Label(client_secret, version iv label, 12) hp HKDF-Expand-Label(client_secret, version hp label, 16)v1 与 v2 之间唯一的差别是盐以及扩展标签这是有意为之在错误版本的盐下派生的密钥会失败 AEAD tag 校验——这正是本工具在不需要信任尚未认证的版本字段的情况下区分 v1 包与 v2 包的方式。在 quic.rs 的实现流程中InitialPacket::parse读取明文的版本、DCID、token连接 ID 超过 20 字节即拒绝InitialPacket::client_keys调用InitialKeys::client(dcid, version)完成上述派生InitialPacket::open去除头保护并执行 AEAD open——tag 校验在此处至关重要被动观察者无法凭任何明文字段区分客户端 Initial 与服务器 Initial但只有客户端真正用这些密钥保护的包才能通过校验所以成功打开本身就是它属于客户端方向的证明。伪造包或服务器包失败 tag 后被丢弃绝不会被放行给解析器。随后CryptoAssembler按偏移把可能跨多个 Initial 包的 CRYPTO 帧拼接成完整的 ClientHelloclient_hello()再送入与 TCP 路径同一个ja4()产出一个带q传输标记的指纹。这套密钥派生被逐字节钉死在 RFC 附录测试向量上quic.rs中的client_initial_keys_match_rfc9001_appendix_a1与client_initial_keys_match_rfc9369_appendix_a从附录给出的连接 ID 派生密钥并断言其 key、IV、头保护值与附录完全一致。仓库测试数据testdata/pcap/quic-with-several-tls-frames.pcapng的集成测试还稳定地产出q13d0310h3_55b375c5d22e_cd85d2d88918。7. 被动捕获与重组问题以上所有内容都假设你能看到一条干净的 ClientHello。真实捕获中你看不到。TCP 交付的是一字节流被切成若干段可能乱序到达、被重传、偶尔重叠。一个大到跨两个段的 ClientHello只看其中任何一个段都无法解析。因此在任何指纹识别之前工具必须做 TCP 端点自己做的事把每条会话的每个方向重组为连续字节流。它追踪每个流源/目的地址与端口的四元组把乱序段缓冲到前面的缺口被填满并解决重叠问题。只有当一段连续字节包含完整的 TLS 记录时解析器才会运行。这同时也是传感器自身安全的所在。被动工具按定义处理攻击者可控的字节监控网络上的任何人都可以给它发送任何内容。因此重组层在每个维度上都有限制追踪流的最大数量上限驱逐陈旧流的空闲超时每个方向缓冲乱序字节的字节上限。没有这些限制攻击者可以打开数百万条流或发送永远不会补上缺口的段然后看着传感器内存一路爬升直到宿主机死亡。限制把无界的对抗输入转化为固定、可存活的成本。03-IMPLEMENTATION.md 给出了确切的封顶值。从源码看这些边界落在 pipeline/flow.rs 的ReassemblyLimits每方向连续字节上限、停驻乱序字节上限、停驻段数量上限与 pipeline 级的流数量上限和空闲超时。当某条流撞上上限它被标记为capped()并停止接收新数据而不是继续增长Counters-v输出暴露segments_dropped与unfinished_tls_streams让运维能看到边界何时生效。StreamReassembler::push将载荷放置到其序列号偏移处若填满缺口则并入连续段、若提前到达则停驻、并与已持字节解决重叠——这正是让跨三个乱序段的 ClientHello能正确解析的机制。8. 内存安全在此处是一种安全属性本工具使用 Rust 编写指纹引擎彻底禁用unsafe。这不是风格偏好而是对此类代码的直接回应。tlsfp-core 中的每个解析器都在读取攻击者可控的二进制输入来自线上的 TLS 记录、来自恶意主机的 X.509 证书、来自任意位置的 QUIC 包。这正是 C 世界中最容易产生严重漏洞的代码类别。HeartbleedCVE-2014-0160就是一个 TLS 解析漏洞长度字段未被边界检查就得到信任让攻击者读取服务器内存。而一个指纹传感器除了 TLS 与证书解析之外什么都不是所以用 C 写的话它就会是一座 Heartbleed 工厂。在 Rust 中声称的字节数超过实际存在的长度字段会产生类型化的ParseError而不是越界读取。JA4X 的证书解析器行走的是所有格式中对攻击者最友好的 X.509 DER它配有属性测试模糊 harness位于 tests/ja4x.rs专门向它抛入随机与截断的证书以证明它返回错误而不是 panic。安全正是那个让被动传感器可以坐在敌意网络上、而不让自己变成漏洞的特性。CONFORMANCE.md 精确记录了解析器接受与拒绝的内容。实现上的支撑来自两个结构选择详见 03-IMPLEMENTATION.md一切皆借用zero-copyparse_client_hello返回的ClientHellopkt持有指向原始包缓冲区的切片解析过程中不复制任何字段热点路径零分配一个含上百个扩展的 ClientHello 不比包本身多占内存。每次读取都边界检查越界是错误而非 panicparse/reader.rs 是唯一推进字节游标的东西它的每次读取都先检查剩余长度。截断的长度字段、声称超过记录容量的扩展、中途结束的证书——每个都返回Err(ParseError)。因为整个 crate 在Cargo.toml中设置unsafe_code forbid解析器 bug 没有任何逃生通道能变成越界读。这是对 Heartbleed 一类 bug 的结构性回答在 C 里这种 bug 是内存泄露在这里它是Result::Err。概念如何落地从文档到可运行证据以上八个概念并不是停留在纸面上的理论项目把每个概念都落成了代码、测试与真实运行证据指纹构造每个指纹都被钉在公开向量上——JA3 钉在 Salesforce 原始博客向量ada70206e40642a3e4461f35503241d5JA4 钉在 FoxIO 的 cipher 段8daaf6152771与 extension 段e5627efa2ab1即真实 Chrome 指纹t13d1516h2_8daaf6152771_e5627efa2ab1QUIC 密钥钉在 RFC 9001/9369 附录 A重组tlsfp-core/tests/reassembly.rs从乱序、重叠的段重建 ClientHello验证第 7 节的重组逻辑跨层检测tlsfp-intel/tests/detect.rs用构造的FingerprintEvent断言ua_mismatch等规则确实产生告警真实运行tlsfp pcap testdata/pcap/tls-handshake.pcapng能对随附的抓包文件输出一行一个握手的指纹同一文件里既有t传输TCP 之上的 TLS也有q传输QUIC 内部的 TLS恰好演示第 3 节与第 6 节并存。建议的阅读路线读完本文后接着读 02-ARCHITECTURE.md三个 crate 如何分工、抓包流水线、情报存储与威胁模型再读 ALGORITHMS.md每个指纹的逐字节构造最后用 03-IMPLEMENTATION.md 追踪一帧从磁盘到告警表的完整旅程。延伸阅读本文源自 learn/01-CONCEPTS.md同一教学系列还包括learn/00-OVERVIEW.md——项目总览、快速上手命令与常见问题排查learn/02-ARCHITECTURE.md——三个 crate 的划分、抓包流水线、情报存储与威胁模型learn/03-IMPLEMENTATION.md——从原始帧到评分告警的端到端代码走读learn/ALGORITHMS.md——每个指纹的精确字节构造与 QUIC 密钥调度learn/CONFORMANCE.md——每个指纹钉死的公开向量与所有刻意的边界learn/04-CHALLENGES.md——从入门到研究级的 12 个扩展练习。相关规范背景RFC 8701 GREASE、RFC 9001/RFC 9369 QUIC 的 TLS 使用在仓库内以测试向量形式固化于 grease.rs 与 quic.rs公共威胁情报源abuse.ch SSLBL、Salesforce osx-nix JA3 列表、自建 C2 集合以 CSV 形式随源码分发于 crates/tlsfp-intel/seeds每个源的实际运行验证可执行cargo test --workspace复现。赞分享【免费下载链接】Cybersecurity-ProjectsBuilding 70 Projects ranging from beginner to advanced so anyone can — learn from, build upon, use as a reference, or even copy directly. Gamified Cybersecurity learning 项目地址https://gitcode.com/gh_mirrors/cy/Cybersecurity-Projects点击查看免费下载相关推荐TLS 指纹算法许可全景ja3-ja4-tls-fingerprinting 项目如何合规实现 JA3、JA4 与 JA4 系列指纹TLS 指纹算法许可全景ja3 ja4 tls fingerprinting 项目如何合规实现 JA3、JA4 与 JA4 系列指纹 本指南以 ja3 jaJA3/JA4 TLS 指纹识别实战指南用 Rust 构建被动 TLS 指纹传感器 tlsfpJA3/JA4 TLS 指纹识别实战指南用 Rust 构建被动 TLS 指纹传感器 tlsfp 本文以 Cybersecurity Projects 仓库中的s2n-tls指纹识别功能JA3和JA4协议的完整实现指南s2n tls指纹识别功能JA3和JA4协议的完整实现指南 在网络安全领域 s2n tls指纹识别 技术正成为识别恶意客户端的关键工具。作为Amazon开发网络安全密码学通信上一篇Loop 快速上手macOS 窗口管理免费工具完整指南下一篇【亲测免费】 Fido简化Windows和UEFI Shell ISO下载的PowerShell脚本创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考