Skip to content

实现原理:指纹库匹配

对应代码:pkg/fpmatch.go · 入口 MatchFingerprints / matchHash

采集到的 JARM/JA3/cert/SPKI 哈希怎么匹配到已知服务?本页讲库结构、精确匹配算法与自定义扩展。

FingerprintMatch 结构体

go
type FingerprintMatch struct {
    Type       string  // "jarm" | "ja3" | "cert_sha256" | "spki"
    Hash       string
    Label      string  // "Cloudflare" / "Cobalt Strike Default" / ...
    Category   string  // cdn/cloud/c2/vpn/web/mail/database/other
    Confidence float64 // 0.0-1.0
    Source     string  // builtin / custom / file:<path>
}

内置库分布

fingerprintDB(fpmatch.go L40-122)共 47 条 builtin,source 全为 "builtin"

Category条数代表条目
cdn7Cloudflare, AWS CloudFront, Azure Front Door, Akamai, Fastly, Sucuri
cloud10AWS ALB/ELB, GCP, Azure, DigitalOcean, Linode, Vultr, Hetzner, OVHcloud
c27Cobalt Strike, Metasploit, Sliver, Havoc, Brute Ratel, Mythic, PoshC2
vpn5OpenVPN, WireGuard, NordVPN, ExpressVPN, Tor(obfs4)
web9Nginx, Apache, Caddy, LiteSpeed, Tomcat, Traefik, HAProxy, IIS, Envoy
mail5Postfix, Dovecot, Exim, Sendmail, Exchange
database6MySQL, PostgreSQL, Redis, MSSQL, MongoDB, Elasticsearch
other10DigiCert Root, Let's Encrypt ISRG X1, Sectigo, GlobalSign, Entrust;Chrome/Firefox/Safari/Edge/Python/Go/curl 等 JA3

按 Type 分布:JARM 35 条、JA3 10 条、SPKI 5 条。

匹配算法:精确等值

matchHash(L175-193)——精确等值匹配,非模糊

go
normalized := strings.ToLower(strings.ReplaceAll(hash, ":", ""))
// 对 db entry 同样归一化后比较

精确等值,非模糊匹配

matchHash精确等值匹配,不是相似度匹配。所以指纹库的价值在覆盖广度——这也是为什么支持加载自定义指纹:你私有资产的特征录进库才能命中。

MatchFingerprints 全流程

四路指纹(JARM/JA3/cert_sha256/SPKI)顺序采集,每路成功才追加 matches;cert 取 Fingerprints["sha256"](→cert_sha256)与 ["public_key_sha256"](→spki)。

SPKI 计算

go
func ComputeCertSPKIHash(cert *x509.Certificate) string {
    return sha256(cert.RawSubjectPublicKeyInfo)  // SPKI 的 DER 编码做 SHA-256
}

这正是 HPKP(HTTP Public Key Pinning) 的标准算法——对 SubjectPublicKeyInfo 的 DER 做 SHA-256。空 Raw 返回 ""

SPKI 字段主要给自定义库用

内置库的 SPKI 哈希多为示例占位,真实生产匹配以 JARM/JA3 为主。SPKI 字段的真正价值在自定义库——把你内部资产的 SPKI 录入,做内部识别与证书绑定监控。

自定义扩展

LoadFingerprintDB(JSON 字节)/ LoadFingerprintDBFromFile 追加自定义条目:

去重 key(isDuplicateFingerprint L199-218):三项全等才算重复——Type + 归一化 hash + ToLower(Label)

json
[
  {
    "type": "jarm",
    "hash": "07d14d16d21d21d07c42d41d00041d24de...",
    "label": "Internal App Gateway",
    "category": "web",
    "confidence": 0.95,
    "source": "internal-asset-inventory"
  }
]

加载后立即对所有后续匹配生效。LoadFingerprintDBFromFile 返回新增计数。

统计:GetFingerprintDBStats

go
type FingerprintDBStats struct {
    Total        int
    ByType       map[string]int
    ByCategory   map[string]int
    BySource     map[string]int
    BuiltinCount int
    CustomCount  int
}

BySource"file:..." 前缀归一为 "file"BuiltinCount 统计 src=="builtin",其余计入 CustomCount。对应 fp-db stats 命令。

输出

go
type FingerprintMatchResult struct {
    Matches  []FingerprintMatch
    JARMHash string
    JA3Hash  string
    CertHash string
    SPKIHash string
}

红队场景

JARM 命中 c2 ≠ 一定是 C2

同款合法软件默认配置会撞同一个 JARM。JARM 命中 c2 类别只是强信号,不是定罪证据——需结合置信度、上下文、其他证据(端口行为、JA3、证书特征)综合判断。误报会把合法运维人员当攻击者。详见 证书事件应急取证工作流

下一步