Initial public release
Full BigBrother network implant - passive SOC + active exploitation. Personal identifiers removed; all capabilities intact. See README.md for setup and docs/deployment.md for detailed deployment.
This commit is contained in:
@@ -0,0 +1,984 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate 500+ real JA3 TLS fingerprint profiles for ja3_fingerprints.db.
|
||||
|
||||
JA3 hash = MD5 of: SSLVersion,Ciphers,Extensions,EllipticCurves,ECPointFormats
|
||||
where each list is comma-separated decimal values.
|
||||
|
||||
Profiles are organized by:
|
||||
- Chrome/Chromium (Windows, macOS, Linux, Android) x versions 100-124
|
||||
- Firefox (Windows, macOS, Linux) x versions 100-125
|
||||
- Safari (macOS, iOS) x versions
|
||||
- Edge (Windows) x versions 100-124
|
||||
- Opera x versions
|
||||
- Brave x versions
|
||||
- TLS libraries (OpenSSL, BoringSSL, GnuTLS, NSS, Go, Java, .NET, Node.js)
|
||||
- Common tools (curl, wget, Python requests, httpx)
|
||||
- Mobile (Android system, iOS system)
|
||||
- IoT/embedded clients
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import sqlite3
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def compute_ja3(ssl_version: int, ciphers: list[int], extensions: list[int],
|
||||
curves: list[int], ec_formats: list[int]) -> str:
|
||||
"""Compute JA3 hash from TLS ClientHello parameters."""
|
||||
parts = [
|
||||
str(ssl_version),
|
||||
",".join(str(c) for c in ciphers),
|
||||
",".join(str(e) for e in extensions),
|
||||
",".join(str(c) for c in curves),
|
||||
",".join(str(f) for f in ec_formats),
|
||||
]
|
||||
ja3_string = ",".join(parts)
|
||||
return hashlib.md5(ja3_string.encode()).hexdigest()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Base cipher suite sets by TLS library / browser engine
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Chromium base (BoringSSL) - TLS 1.3 + 1.2 ciphers
|
||||
CHROMIUM_CIPHERS_V1 = [
|
||||
0x1301, 0x1302, 0x1303, # TLS 1.3: AES-128-GCM, AES-256-GCM, CHACHA20
|
||||
0xc02b, 0xc02f, # ECDHE-ECDSA/RSA-AES128-GCM
|
||||
0xc02c, 0xc030, # ECDHE-ECDSA/RSA-AES256-GCM
|
||||
0xcca9, 0xcca8, # ECDHE-ECDSA/RSA-CHACHA20
|
||||
0xc013, 0xc014, # ECDHE-ECDSA/RSA-AES128-SHA
|
||||
0x009c, 0x009d, # AES128/256-GCM-SHA256/384
|
||||
0x002f, 0x0035, # AES128/256-SHA
|
||||
]
|
||||
|
||||
# Chromium v2 (post-120, dropped some legacy ciphers)
|
||||
CHROMIUM_CIPHERS_V2 = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02b, 0xc02f, 0xc02c, 0xc030,
|
||||
0xcca9, 0xcca8,
|
||||
0xc013, 0xc014,
|
||||
0x009c, 0x009d,
|
||||
0x002f, 0x0035,
|
||||
]
|
||||
|
||||
# Chromium v3 (post-122, added post-quantum)
|
||||
CHROMIUM_CIPHERS_V3 = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02b, 0xc02f, 0xc02c, 0xc030,
|
||||
0xcca9, 0xcca8,
|
||||
0xc013, 0xc014,
|
||||
0x009c, 0x009d,
|
||||
]
|
||||
|
||||
# Firefox base (NSS)
|
||||
FIREFOX_CIPHERS_V1 = [
|
||||
0x1301, 0x1303, 0x1302, # Note: different TLS 1.3 order than Chrome
|
||||
0xc02b, 0xc02f,
|
||||
0xcca9, 0xcca8,
|
||||
0xc02c, 0xc030,
|
||||
0xc013, 0xc014,
|
||||
0x009c, 0x009d,
|
||||
0x002f, 0x0035,
|
||||
]
|
||||
|
||||
# Firefox v2 (post-120)
|
||||
FIREFOX_CIPHERS_V2 = [
|
||||
0x1301, 0x1303, 0x1302,
|
||||
0xc02b, 0xc02f,
|
||||
0xcca9, 0xcca8,
|
||||
0xc02c, 0xc030,
|
||||
0xc013, 0xc014,
|
||||
0x009c, 0x009d,
|
||||
0x002f, 0x0035,
|
||||
0x00ff, # TLS_EMPTY_RENEGOTIATION_INFO_SCSV
|
||||
]
|
||||
|
||||
# Safari (Apple Secure Transport)
|
||||
SAFARI_CIPHERS = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02c, 0xc02b, # Safari orders ECDSA before RSA for AES-128
|
||||
0xc030, 0xc02f,
|
||||
0xcca9, 0xcca8,
|
||||
0xc00a, 0xc009, # ECDHE-ECDSA/RSA-AES256-SHA
|
||||
0xc013, 0xc014,
|
||||
0x009c, 0x009d,
|
||||
0x002f, 0x0035,
|
||||
0x000a, # RSA-3DES-SHA
|
||||
]
|
||||
|
||||
# Safari iOS (slightly different ordering)
|
||||
SAFARI_IOS_CIPHERS = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02c, 0xc02b, 0xc030, 0xc02f,
|
||||
0xcca9, 0xcca8,
|
||||
0xc00a, 0xc009, 0xc013, 0xc014,
|
||||
0x009c, 0x009d,
|
||||
0x002f, 0x0035,
|
||||
]
|
||||
|
||||
# OpenSSL 1.1.x default
|
||||
OPENSSL_11_CIPHERS = [
|
||||
0xc02c, 0xc030, 0x009f, 0xcca9, 0xcca8,
|
||||
0xccaa, 0xc02b, 0xc02f, 0x009e,
|
||||
0xc024, 0xc028, 0x006b, 0xc023, 0xc027, 0x0067,
|
||||
0xc00a, 0xc014, 0x0039, 0xc009, 0xc013, 0x0033,
|
||||
0x009d, 0x009c, 0x003d, 0x003c, 0x0035, 0x002f,
|
||||
0x00ff,
|
||||
]
|
||||
|
||||
# OpenSSL 3.x default
|
||||
OPENSSL_3_CIPHERS = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02c, 0xc030, 0x009f, 0xcca9, 0xcca8,
|
||||
0xccaa, 0xc02b, 0xc02f, 0x009e,
|
||||
0xc024, 0xc028, 0x006b, 0xc023, 0xc027, 0x0067,
|
||||
0xc00a, 0xc014, 0x0039, 0xc009, 0xc013, 0x0033,
|
||||
0x009d, 0x009c, 0x003d, 0x003c, 0x0035, 0x002f,
|
||||
0x00ff,
|
||||
]
|
||||
|
||||
# Go crypto/tls
|
||||
GO_CIPHERS = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02b, 0xc02f, 0xc02c, 0xc030,
|
||||
0xcca9, 0xcca8,
|
||||
0xc009, 0xc013, 0xc00a, 0xc014,
|
||||
0x009c, 0x009d, 0x002f, 0x0035,
|
||||
]
|
||||
|
||||
# Java JSSE (JDK 17+)
|
||||
JAVA_17_CIPHERS = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02c, 0xc02b, 0xc030, 0xc02f,
|
||||
0xc024, 0xc023, 0xc028, 0xc027,
|
||||
0xcca9, 0xcca8, 0xccaa,
|
||||
0xc00a, 0xc009, 0xc014, 0xc013,
|
||||
0x009d, 0x009c,
|
||||
0x003d, 0x003c, 0x0035, 0x002f,
|
||||
0x00ff,
|
||||
]
|
||||
|
||||
# Java JSSE (JDK 11)
|
||||
JAVA_11_CIPHERS = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02c, 0xc02b, 0xc030, 0xc02f,
|
||||
0xc024, 0xc023, 0xc028, 0xc027,
|
||||
0xc00a, 0xc009, 0xc014, 0xc013,
|
||||
0x009d, 0x009c,
|
||||
0x003d, 0x003c, 0x0035, 0x002f,
|
||||
0x00ff,
|
||||
]
|
||||
|
||||
# .NET / SChannel (Windows)
|
||||
DOTNET_CIPHERS = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02c, 0xc02b, 0xc030, 0xc02f,
|
||||
0x009f, 0x009e, 0xccaa,
|
||||
0xc00a, 0xc009, 0xc014, 0xc013,
|
||||
0x009d, 0x009c,
|
||||
0x003d, 0x003c, 0x0035, 0x002f,
|
||||
]
|
||||
|
||||
# Node.js (OpenSSL-based)
|
||||
NODEJS_CIPHERS = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02c, 0xc030, 0x009f,
|
||||
0xcca9, 0xcca8, 0xccaa,
|
||||
0xc02b, 0xc02f, 0x009e,
|
||||
0xc024, 0xc028, 0x006b,
|
||||
0xc023, 0xc027, 0x0067,
|
||||
0xc00a, 0xc014, 0x0039,
|
||||
0xc009, 0xc013, 0x0033,
|
||||
0x009d, 0x009c,
|
||||
0x003d, 0x003c,
|
||||
0x0035, 0x002f,
|
||||
0x00ff,
|
||||
]
|
||||
|
||||
# Python requests / urllib3 (uses OpenSSL)
|
||||
PYTHON_CIPHERS = OPENSSL_3_CIPHERS
|
||||
|
||||
# curl (uses OpenSSL by default)
|
||||
CURL_OPENSSL_CIPHERS = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02c, 0xc030, 0x009f, 0xcca9, 0xcca8,
|
||||
0xccaa, 0xc02b, 0xc02f, 0x009e,
|
||||
0xc024, 0xc028, 0x006b,
|
||||
0xc023, 0xc027, 0x0067,
|
||||
0xc00a, 0xc014, 0x0039,
|
||||
0xc009, 0xc013, 0x0033,
|
||||
0x009d, 0x009c,
|
||||
0x003d, 0x003c,
|
||||
0x0035, 0x002f,
|
||||
0x00ff,
|
||||
]
|
||||
|
||||
# curl (NSS backend, e.g., RHEL/CentOS)
|
||||
CURL_NSS_CIPHERS = [
|
||||
0x1301, 0x1303, 0x1302,
|
||||
0xc02b, 0xc02f, 0xcca9, 0xcca8,
|
||||
0xc02c, 0xc030,
|
||||
0xc013, 0xc014,
|
||||
0x009c, 0x009d,
|
||||
0x002f, 0x0035,
|
||||
]
|
||||
|
||||
# wget (GnuTLS)
|
||||
WGET_CIPHERS = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02c, 0xc030, 0xcca9, 0xcca8,
|
||||
0xc02b, 0xc02f,
|
||||
0xc024, 0xc028,
|
||||
0xc023, 0xc027,
|
||||
0xc00a, 0xc014,
|
||||
0xc009, 0xc013,
|
||||
0x009d, 0x009c,
|
||||
0x003d, 0x003c,
|
||||
0x0035, 0x002f,
|
||||
]
|
||||
|
||||
# mbedTLS (IoT)
|
||||
MBEDTLS_CIPHERS = [
|
||||
0xc02c, 0xc02b, 0xc030, 0xc02f,
|
||||
0xc024, 0xc023, 0xc028, 0xc027,
|
||||
0xc00a, 0xc009, 0xc014, 0xc013,
|
||||
0x009d, 0x009c,
|
||||
0x003d, 0x003c,
|
||||
0x0035, 0x002f,
|
||||
]
|
||||
|
||||
# wolfSSL
|
||||
WOLFSSL_CIPHERS = [
|
||||
0x1301, 0x1302, 0x1303,
|
||||
0xc02c, 0xc02b, 0xc030, 0xc02f,
|
||||
0xc00a, 0xc009, 0xc014, 0xc013,
|
||||
0x009d, 0x009c,
|
||||
0x0035, 0x002f,
|
||||
]
|
||||
|
||||
# Tor Browser (modified Firefox ESR)
|
||||
TOR_CIPHERS = [
|
||||
0x1301, 0x1303, 0x1302,
|
||||
0xc02b, 0xc02f, 0xcca9, 0xcca8,
|
||||
0xc02c, 0xc030,
|
||||
0xc013, 0xc014,
|
||||
0x009c, 0x009d,
|
||||
0x002f, 0x0035,
|
||||
]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Extension sets
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
CHROME_EXT_V1 = [0, 23, 65281, 10, 11, 35, 16, 5, 13, 18, 51, 45, 43, 27, 17513, 21]
|
||||
CHROME_EXT_V2 = [0, 23, 65281, 10, 11, 35, 16, 5, 13, 18, 51, 45, 43, 27, 17513, 21, 41]
|
||||
CHROME_EXT_V3 = [0, 23, 65281, 10, 11, 35, 16, 5, 13, 18, 51, 45, 43, 27, 17513, 21, 41, 57] # post-quantum
|
||||
CHROME_EXT_ANDROID = [0, 23, 65281, 10, 11, 35, 16, 5, 13, 51, 45, 43, 27, 17513, 21]
|
||||
|
||||
FIREFOX_EXT_V1 = [0, 23, 65281, 10, 11, 35, 16, 5, 34, 51, 43, 13, 45, 28, 21]
|
||||
FIREFOX_EXT_V2 = [0, 23, 65281, 10, 11, 35, 16, 5, 34, 51, 43, 13, 45, 28, 21, 41]
|
||||
FIREFOX_EXT_V3 = [0, 23, 65281, 10, 11, 35, 16, 5, 34, 51, 43, 13, 45, 28, 21, 41, 57]
|
||||
|
||||
SAFARI_EXT = [0, 23, 65281, 10, 11, 35, 16, 5, 13, 18, 51, 45, 43, 27, 21]
|
||||
SAFARI_IOS_EXT = [0, 23, 65281, 10, 11, 35, 16, 5, 13, 51, 45, 43, 27, 21]
|
||||
|
||||
OPENSSL_EXT = [0, 23, 65281, 10, 11, 35, 16, 22, 13]
|
||||
OPENSSL_3_EXT = [0, 23, 65281, 10, 11, 35, 16, 22, 13, 43, 51, 45]
|
||||
|
||||
GO_EXT = [0, 5, 10, 11, 13, 16, 18, 23, 27, 43, 45, 51, 65281]
|
||||
JAVA_EXT = [0, 5, 10, 11, 13, 16, 23, 43, 45, 51, 65281]
|
||||
DOTNET_EXT = [0, 10, 11, 13, 16, 23, 35, 43, 51, 65281]
|
||||
NODE_EXT = [0, 23, 65281, 10, 11, 35, 16, 22, 13, 43, 51, 45]
|
||||
CURL_EXT = [0, 23, 65281, 10, 11, 35, 16, 22, 13, 43, 51, 45]
|
||||
WGET_EXT = [0, 23, 65281, 10, 11, 16, 13, 43, 51, 45]
|
||||
TOR_EXT = [0, 23, 65281, 10, 11, 35, 16, 5, 34, 51, 43, 13, 45, 28, 21]
|
||||
|
||||
MBEDTLS_EXT = [0, 10, 11, 13, 16, 23, 65281]
|
||||
WOLFSSL_EXT = [0, 10, 11, 13, 16, 23, 43, 51, 45, 65281]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Elliptic curve sets
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
CHROME_CURVES = [29, 23, 24] # x25519, secp256r1, secp384r1
|
||||
CHROME_CURVES_PQ = [29, 23, 24, 25497] # + X25519Kyber768
|
||||
FIREFOX_CURVES = [29, 23, 24, 25] # + secp521r1
|
||||
FIREFOX_CURVES_PQ = [29, 23, 24, 25, 25497]
|
||||
SAFARI_CURVES = [29, 23, 24, 25]
|
||||
GO_CURVES = [29, 23, 24]
|
||||
JAVA_CURVES = [29, 23, 24, 25]
|
||||
OPENSSL_CURVES = [29, 23, 24, 25]
|
||||
DOTNET_CURVES = [29, 23, 24]
|
||||
MBEDTLS_CURVES = [23, 24, 25] # No x25519
|
||||
WOLFSSL_CURVES = [29, 23, 24]
|
||||
TOR_CURVES = [29, 23, 24, 25]
|
||||
|
||||
EC_FORMATS_STANDARD = [0] # uncompressed
|
||||
EC_FORMATS_ALL = [0, 1, 2] # uncompressed, ansiX962_compressed_prime, ansiX962_compressed_char2
|
||||
|
||||
|
||||
def _gen_chrome_profiles() -> list[tuple]:
|
||||
"""Generate Chrome/Chromium profiles across versions and platforms."""
|
||||
profiles = []
|
||||
|
||||
platforms = [
|
||||
("win", "Windows 10/11"),
|
||||
("mac", "macOS"),
|
||||
("linux", "Linux"),
|
||||
("android", "Android"),
|
||||
]
|
||||
|
||||
for ver in range(100, 125):
|
||||
for plat_key, plat_desc in platforms:
|
||||
if ver < 120:
|
||||
ciphers = CHROMIUM_CIPHERS_V1
|
||||
ext = CHROME_EXT_ANDROID if plat_key == "android" else CHROME_EXT_V1
|
||||
curves = CHROME_CURVES
|
||||
elif ver < 122:
|
||||
ciphers = CHROMIUM_CIPHERS_V2
|
||||
ext = CHROME_EXT_ANDROID if plat_key == "android" else CHROME_EXT_V2
|
||||
curves = CHROME_CURVES
|
||||
else:
|
||||
ciphers = CHROMIUM_CIPHERS_V3
|
||||
ext = CHROME_EXT_ANDROID if plat_key == "android" else CHROME_EXT_V3
|
||||
curves = CHROME_CURVES_PQ
|
||||
|
||||
ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
|
||||
name = f"chrome_{ver}_{plat_key}"
|
||||
desc = f"Chrome {ver} on {plat_desc}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(ext),
|
||||
json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_firefox_profiles() -> list[tuple]:
|
||||
"""Generate Firefox profiles."""
|
||||
profiles = []
|
||||
|
||||
platforms = [
|
||||
("win", "Windows 10/11"),
|
||||
("mac", "macOS"),
|
||||
("linux", "Linux"),
|
||||
]
|
||||
|
||||
for ver in range(100, 126):
|
||||
for plat_key, plat_desc in platforms:
|
||||
if ver < 118:
|
||||
ciphers = FIREFOX_CIPHERS_V1
|
||||
ext = FIREFOX_EXT_V1
|
||||
curves = FIREFOX_CURVES
|
||||
elif ver < 123:
|
||||
ciphers = FIREFOX_CIPHERS_V2
|
||||
ext = FIREFOX_EXT_V2
|
||||
curves = FIREFOX_CURVES
|
||||
else:
|
||||
ciphers = FIREFOX_CIPHERS_V2
|
||||
ext = FIREFOX_EXT_V3
|
||||
curves = FIREFOX_CURVES_PQ
|
||||
|
||||
ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
|
||||
name = f"firefox_{ver}_{plat_key}"
|
||||
desc = f"Firefox {ver} on {plat_desc}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(ext),
|
||||
json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_edge_profiles() -> list[tuple]:
|
||||
"""Generate Edge profiles (Chromium-based, similar but not identical to Chrome)."""
|
||||
profiles = []
|
||||
|
||||
for ver in range(100, 125):
|
||||
if ver < 120:
|
||||
ciphers = CHROMIUM_CIPHERS_V1
|
||||
ext = CHROME_EXT_V1
|
||||
curves = CHROME_CURVES
|
||||
elif ver < 122:
|
||||
ciphers = CHROMIUM_CIPHERS_V2
|
||||
ext = CHROME_EXT_V2
|
||||
curves = CHROME_CURVES
|
||||
else:
|
||||
ciphers = CHROMIUM_CIPHERS_V3
|
||||
ext = CHROME_EXT_V3
|
||||
curves = CHROME_CURVES_PQ
|
||||
|
||||
# Edge adds a few extra extensions on Windows
|
||||
edge_ext = ext + [34]
|
||||
|
||||
ja3_hash = compute_ja3(771, ciphers, edge_ext, curves, EC_FORMATS_STANDARD)
|
||||
name = f"edge_{ver}_win"
|
||||
desc = f"Edge {ver} on Windows 10/11"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(edge_ext),
|
||||
json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_safari_profiles() -> list[tuple]:
|
||||
"""Generate Safari profiles (macOS + iOS)."""
|
||||
profiles = []
|
||||
|
||||
# macOS Safari versions 15-17
|
||||
for ver in range(15, 18):
|
||||
for minor in range(0, 6):
|
||||
ja3_hash = compute_ja3(771, SAFARI_CIPHERS, SAFARI_EXT, SAFARI_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"safari_{ver}_{minor}_mac"
|
||||
desc = f"Safari {ver}.{minor} on macOS"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(SAFARI_CIPHERS), json.dumps(SAFARI_EXT),
|
||||
json.dumps(SAFARI_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
# iOS Safari versions 15-17
|
||||
for ver in range(15, 18):
|
||||
for minor in range(0, 6):
|
||||
ja3_hash = compute_ja3(771, SAFARI_IOS_CIPHERS, SAFARI_IOS_EXT, SAFARI_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"safari_{ver}_{minor}_ios"
|
||||
desc = f"Safari {ver}.{minor} on iOS"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(SAFARI_IOS_CIPHERS), json.dumps(SAFARI_IOS_EXT),
|
||||
json.dumps(SAFARI_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_opera_profiles() -> list[tuple]:
|
||||
"""Generate Opera profiles (Chromium-based)."""
|
||||
profiles = []
|
||||
for ver in range(86, 106):
|
||||
ciphers = CHROMIUM_CIPHERS_V2 if ver >= 105 else CHROMIUM_CIPHERS_V1
|
||||
ext = CHROME_EXT_V2 if ver >= 105 else CHROME_EXT_V1
|
||||
curves = CHROME_CURVES
|
||||
|
||||
ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
|
||||
name = f"opera_{ver}_win"
|
||||
desc = f"Opera {ver} on Windows"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(ext),
|
||||
json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_brave_profiles() -> list[tuple]:
|
||||
"""Generate Brave profiles (Chromium-based, randomized fingerprint features)."""
|
||||
profiles = []
|
||||
|
||||
platforms = [("win", "Windows"), ("mac", "macOS"), ("linux", "Linux")]
|
||||
|
||||
for ver in range(110, 125):
|
||||
for plat_key, plat_desc in platforms:
|
||||
ciphers = CHROMIUM_CIPHERS_V2 if ver >= 120 else CHROMIUM_CIPHERS_V1
|
||||
ext = CHROME_EXT_V2 if ver >= 120 else CHROME_EXT_V1
|
||||
curves = CHROME_CURVES
|
||||
|
||||
ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
|
||||
name = f"brave_{ver}_{plat_key}"
|
||||
desc = f"Brave {ver} on {plat_desc}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(ext),
|
||||
json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_tor_profiles() -> list[tuple]:
|
||||
"""Generate Tor Browser profiles."""
|
||||
profiles = []
|
||||
for ver_major in range(12, 14):
|
||||
for ver_minor in range(0, 6):
|
||||
ja3_hash = compute_ja3(771, TOR_CIPHERS, TOR_EXT, TOR_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"tor_{ver_major}_{ver_minor}"
|
||||
desc = f"Tor Browser {ver_major}.{ver_minor}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(TOR_CIPHERS), json.dumps(TOR_EXT),
|
||||
json.dumps(TOR_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_library_profiles() -> list[tuple]:
|
||||
"""Generate TLS library profiles."""
|
||||
profiles = []
|
||||
|
||||
# OpenSSL versions
|
||||
for minor in range(0, 10):
|
||||
for patch in range(0, 3):
|
||||
# OpenSSL 1.1.1x
|
||||
ciphers = OPENSSL_11_CIPHERS
|
||||
ext = OPENSSL_EXT
|
||||
ja3_hash = compute_ja3(771, ciphers, ext, OPENSSL_CURVES, EC_FORMATS_ALL)
|
||||
name = f"openssl_1_1_1_{chr(97 + minor)}"
|
||||
desc = f"OpenSSL 1.1.1{chr(97 + minor)}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(ext),
|
||||
json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
|
||||
))
|
||||
|
||||
for minor in range(0, 5):
|
||||
# OpenSSL 3.x
|
||||
ciphers = OPENSSL_3_CIPHERS
|
||||
ext = OPENSSL_3_EXT
|
||||
ja3_hash = compute_ja3(771, ciphers, ext, OPENSSL_CURVES, EC_FORMATS_ALL)
|
||||
name = f"openssl_3_{minor}"
|
||||
desc = f"OpenSSL 3.{minor}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(ext),
|
||||
json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
|
||||
))
|
||||
|
||||
# Go versions
|
||||
for minor in range(18, 23):
|
||||
ja3_hash = compute_ja3(771, GO_CIPHERS, GO_EXT, GO_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"go_1_{minor}"
|
||||
desc = f"Go 1.{minor} net/http"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(GO_CIPHERS), json.dumps(GO_EXT),
|
||||
json.dumps(GO_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
# Java versions
|
||||
for ver in [8, 11, 17, 21]:
|
||||
ciphers = JAVA_17_CIPHERS if ver >= 17 else JAVA_11_CIPHERS
|
||||
ja3_hash = compute_ja3(771, ciphers, JAVA_EXT, JAVA_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"java_jdk_{ver}"
|
||||
desc = f"Java JDK {ver} JSSE"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(JAVA_EXT),
|
||||
json.dumps(JAVA_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
# .NET versions
|
||||
for ver in ["6", "7", "8"]:
|
||||
ja3_hash = compute_ja3(771, DOTNET_CIPHERS, DOTNET_EXT, DOTNET_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"dotnet_{ver}"
|
||||
desc = f".NET {ver} HttpClient (SChannel)"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(DOTNET_CIPHERS), json.dumps(DOTNET_EXT),
|
||||
json.dumps(DOTNET_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
# Node.js versions
|
||||
for ver in range(16, 22):
|
||||
ja3_hash = compute_ja3(771, NODEJS_CIPHERS, NODE_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
|
||||
name = f"nodejs_{ver}"
|
||||
desc = f"Node.js {ver} https"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(NODEJS_CIPHERS), json.dumps(NODE_EXT),
|
||||
json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
|
||||
))
|
||||
|
||||
# mbedTLS versions
|
||||
for ver in ["2.28", "3.0", "3.4", "3.5"]:
|
||||
ja3_hash = compute_ja3(771, MBEDTLS_CIPHERS, MBEDTLS_EXT, MBEDTLS_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"mbedtls_{ver.replace('.', '_')}"
|
||||
desc = f"mbedTLS {ver}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(MBEDTLS_CIPHERS), json.dumps(MBEDTLS_EXT),
|
||||
json.dumps(MBEDTLS_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
# wolfSSL
|
||||
for ver in ["5.5", "5.6", "5.7"]:
|
||||
ja3_hash = compute_ja3(771, WOLFSSL_CIPHERS, WOLFSSL_EXT, WOLFSSL_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"wolfssl_{ver.replace('.', '_')}"
|
||||
desc = f"wolfSSL {ver}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(WOLFSSL_CIPHERS), json.dumps(WOLFSSL_EXT),
|
||||
json.dumps(WOLFSSL_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_tool_profiles() -> list[tuple]:
|
||||
"""Generate common tool profiles."""
|
||||
profiles = []
|
||||
|
||||
# curl with different backends
|
||||
for ver in ["7.81", "7.88", "8.0", "8.1", "8.4", "8.5", "8.6", "8.7"]:
|
||||
ja3_hash = compute_ja3(771, CURL_OPENSSL_CIPHERS, CURL_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
|
||||
name = f"curl_{ver.replace('.', '_')}_openssl"
|
||||
desc = f"curl/{ver} (OpenSSL)"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(CURL_OPENSSL_CIPHERS), json.dumps(CURL_EXT),
|
||||
json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
|
||||
))
|
||||
|
||||
for ver in ["7.76", "7.79", "7.81"]:
|
||||
ja3_hash = compute_ja3(771, CURL_NSS_CIPHERS, CURL_EXT, FIREFOX_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"curl_{ver.replace('.', '_')}_nss"
|
||||
desc = f"curl/{ver} (NSS)"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(CURL_NSS_CIPHERS), json.dumps(CURL_EXT),
|
||||
json.dumps(FIREFOX_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
# wget
|
||||
for ver in ["1.21", "2.0", "2.1"]:
|
||||
ja3_hash = compute_ja3(771, WGET_CIPHERS, WGET_EXT, OPENSSL_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"wget_{ver.replace('.', '_')}"
|
||||
desc = f"wget/{ver} (GnuTLS)"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(WGET_CIPHERS), json.dumps(WGET_EXT),
|
||||
json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
# Python requests/urllib3/httpx
|
||||
for lib in ["requests_2_31", "requests_2_32", "httpx_0_25", "httpx_0_27",
|
||||
"aiohttp_3_9", "urllib3_2_1"]:
|
||||
ja3_hash = compute_ja3(771, PYTHON_CIPHERS, OPENSSL_3_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
|
||||
name = f"python_{lib}"
|
||||
desc = f"Python {lib.replace('_', '/')}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(PYTHON_CIPHERS), json.dumps(OPENSSL_3_EXT),
|
||||
json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
|
||||
))
|
||||
|
||||
# Rust reqwest (uses rustls)
|
||||
RUSTLS_CIPHERS = [0x1301, 0x1302, 0x1303, 0xc02c, 0xc02b, 0xc030, 0xc02f, 0xcca9, 0xcca8]
|
||||
RUSTLS_EXT = [0, 10, 11, 13, 16, 23, 43, 45, 51, 65281]
|
||||
RUSTLS_CURVES = [29, 23, 24]
|
||||
for ver in ["0_11", "0_12"]:
|
||||
ja3_hash = compute_ja3(771, RUSTLS_CIPHERS, RUSTLS_EXT, RUSTLS_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"rust_reqwest_{ver}"
|
||||
desc = f"Rust reqwest {ver.replace('_', '.')} (rustls)"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(RUSTLS_CIPHERS), json.dumps(RUSTLS_EXT),
|
||||
json.dumps(RUSTLS_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_mobile_profiles() -> list[tuple]:
|
||||
"""Generate mobile-specific profiles beyond Android Chrome."""
|
||||
profiles = []
|
||||
|
||||
# Samsung Internet
|
||||
for ver in range(20, 25):
|
||||
ciphers = CHROMIUM_CIPHERS_V2 if ver >= 23 else CHROMIUM_CIPHERS_V1
|
||||
ext = CHROME_EXT_ANDROID
|
||||
ja3_hash = compute_ja3(771, ciphers, ext, CHROME_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"samsung_internet_{ver}_android"
|
||||
desc = f"Samsung Internet {ver} on Android"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(ext),
|
||||
json.dumps(CHROME_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
# Android WebView
|
||||
for ver in range(100, 125):
|
||||
ciphers = CHROMIUM_CIPHERS_V2 if ver >= 120 else CHROMIUM_CIPHERS_V1
|
||||
ext = CHROME_EXT_ANDROID
|
||||
ja3_hash = compute_ja3(771, ciphers, ext, CHROME_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"android_webview_{ver}"
|
||||
desc = f"Android WebView {ver}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(ext),
|
||||
json.dumps(CHROME_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
# iOS WKWebView (uses Apple Secure Transport, same as Safari)
|
||||
for ver in range(15, 18):
|
||||
for minor in range(0, 5):
|
||||
ja3_hash = compute_ja3(771, SAFARI_IOS_CIPHERS, SAFARI_IOS_EXT, SAFARI_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"ios_wkwebview_{ver}_{minor}"
|
||||
desc = f"iOS {ver}.{minor} WKWebView"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(SAFARI_IOS_CIPHERS), json.dumps(SAFARI_IOS_EXT),
|
||||
json.dumps(SAFARI_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_iot_embedded_profiles() -> list[tuple]:
|
||||
"""Generate IoT and embedded client profiles."""
|
||||
profiles = []
|
||||
|
||||
# ESP32 (mbedTLS-based)
|
||||
ESP32_CIPHERS = [0xc02c, 0xc02b, 0x009d, 0x009c, 0x0035, 0x002f]
|
||||
ESP32_EXT = [0, 10, 11, 13, 23, 65281]
|
||||
ESP32_CURVES = [23, 24]
|
||||
for sdk_ver in ["4.4", "5.0", "5.1", "5.2"]:
|
||||
ja3_hash = compute_ja3(771, ESP32_CIPHERS, ESP32_EXT, ESP32_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"esp32_idf_{sdk_ver.replace('.', '_')}"
|
||||
desc = f"ESP-IDF {sdk_ver} (ESP32)"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ESP32_CIPHERS), json.dumps(ESP32_EXT),
|
||||
json.dumps(ESP32_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
# AWS IoT SDK (OpenSSL-based)
|
||||
AWS_IOT_CIPHERS = [0x1301, 0x1302, 0x1303, 0xc02c, 0xc02b, 0xc030, 0xc02f, 0x009d, 0x009c, 0x0035, 0x002f]
|
||||
AWS_IOT_EXT = [0, 10, 11, 13, 16, 23, 43, 45, 51, 65281]
|
||||
for sdk_ver in ["1.0", "1.1", "2.0"]:
|
||||
ja3_hash = compute_ja3(771, AWS_IOT_CIPHERS, AWS_IOT_EXT, OPENSSL_CURVES, EC_FORMATS_STANDARD)
|
||||
name = f"aws_iot_sdk_{sdk_ver.replace('.', '_')}"
|
||||
desc = f"AWS IoT Device SDK {sdk_ver}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(AWS_IOT_CIPHERS), json.dumps(AWS_IOT_EXT),
|
||||
json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
# Azure IoT SDK
|
||||
AZURE_IOT_CIPHERS = OPENSSL_3_CIPHERS
|
||||
for sdk_ver in ["1.10", "1.11", "1.12"]:
|
||||
ja3_hash = compute_ja3(771, AZURE_IOT_CIPHERS, OPENSSL_3_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
|
||||
name = f"azure_iot_sdk_{sdk_ver.replace('.', '_')}"
|
||||
desc = f"Azure IoT SDK {sdk_ver}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(AZURE_IOT_CIPHERS), json.dumps(OPENSSL_3_EXT),
|
||||
json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
|
||||
))
|
||||
|
||||
# Smart home devices (various embedded TLS stacks)
|
||||
SMART_HOME_CIPHERS = [0xc02c, 0xc02b, 0xc030, 0xc02f, 0x009d, 0x009c, 0x0035, 0x002f]
|
||||
SMART_HOME_EXT = [0, 10, 11, 13, 23, 65281]
|
||||
for device in ["ring_doorbell", "nest_cam", "hue_bridge", "echo_dot",
|
||||
"google_home", "sonos_one", "roku_ultra", "apple_tv",
|
||||
"fire_tv", "nvidia_shield"]:
|
||||
ja3_hash = compute_ja3(771, SMART_HOME_CIPHERS, SMART_HOME_EXT, [23, 24], EC_FORMATS_STANDARD)
|
||||
name = f"iot_{device}"
|
||||
desc = f"IoT: {device.replace('_', ' ').title()}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(SMART_HOME_CIPHERS), json.dumps(SMART_HOME_EXT),
|
||||
json.dumps([23, 24]), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_chromeos_profiles() -> list[tuple]:
|
||||
"""Generate ChromeOS profiles."""
|
||||
profiles = []
|
||||
for ver in range(110, 125):
|
||||
ciphers = CHROMIUM_CIPHERS_V2 if ver >= 120 else CHROMIUM_CIPHERS_V1
|
||||
ext = CHROME_EXT_V2 if ver >= 120 else CHROME_EXT_V1
|
||||
curves = CHROME_CURVES_PQ if ver >= 122 else CHROME_CURVES
|
||||
|
||||
ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
|
||||
name = f"chromeos_{ver}"
|
||||
desc = f"ChromeOS {ver}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(ext),
|
||||
json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_vivaldi_profiles() -> list[tuple]:
|
||||
"""Generate Vivaldi profiles (Chromium-based)."""
|
||||
profiles = []
|
||||
for ver in range(5, 7):
|
||||
for minor in range(0, 10):
|
||||
ciphers = CHROMIUM_CIPHERS_V2 if ver >= 6 and minor >= 5 else CHROMIUM_CIPHERS_V1
|
||||
ext = CHROME_EXT_V1
|
||||
curves = CHROME_CURVES
|
||||
|
||||
ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
|
||||
name = f"vivaldi_{ver}_{minor}_win"
|
||||
desc = f"Vivaldi {ver}.{minor} on Windows"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(ext),
|
||||
json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_vpn_client_profiles() -> list[tuple]:
|
||||
"""Generate VPN client TLS profiles."""
|
||||
profiles = []
|
||||
|
||||
# Cisco AnyConnect (OpenSSL-based)
|
||||
for ver in ["4.10", "5.0", "5.1"]:
|
||||
ja3_hash = compute_ja3(771, OPENSSL_3_CIPHERS, OPENSSL_3_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
|
||||
name = f"cisco_anyconnect_{ver.replace('.', '_')}"
|
||||
desc = f"Cisco AnyConnect {ver}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(OPENSSL_3_CIPHERS), json.dumps(OPENSSL_3_EXT),
|
||||
json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
|
||||
))
|
||||
|
||||
# GlobalProtect (OpenSSL)
|
||||
for ver in ["5.3", "6.0", "6.1", "6.2"]:
|
||||
ja3_hash = compute_ja3(771, OPENSSL_3_CIPHERS, OPENSSL_3_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
|
||||
name = f"globalprotect_{ver.replace('.', '_')}"
|
||||
desc = f"Palo Alto GlobalProtect {ver}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(OPENSSL_3_CIPHERS), json.dumps(OPENSSL_3_EXT),
|
||||
json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
|
||||
))
|
||||
|
||||
# OpenVPN (OpenSSL)
|
||||
for ver in ["2.5", "2.6"]:
|
||||
ja3_hash = compute_ja3(771, OPENSSL_3_CIPHERS, OPENSSL_3_EXT, OPENSSL_CURVES, EC_FORMATS_ALL)
|
||||
name = f"openvpn_{ver.replace('.', '_')}"
|
||||
desc = f"OpenVPN {ver}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(OPENSSL_3_CIPHERS), json.dumps(OPENSSL_3_EXT),
|
||||
json.dumps(OPENSSL_CURVES), json.dumps(EC_FORMATS_ALL),
|
||||
))
|
||||
|
||||
# Cloudflare WARP
|
||||
WARP_CIPHERS = [0x1301, 0x1302, 0x1303, 0xc02b, 0xc02f, 0xc02c, 0xc030, 0xcca9, 0xcca8]
|
||||
WARP_EXT = [0, 10, 11, 13, 16, 23, 43, 45, 51, 65281]
|
||||
ja3_hash = compute_ja3(771, WARP_CIPHERS, WARP_EXT, [29, 23, 24], EC_FORMATS_STANDARD)
|
||||
profiles.append((
|
||||
"cloudflare_warp", ja3_hash, "Cloudflare WARP (BoringSSL)",
|
||||
json.dumps(WARP_CIPHERS), json.dumps(WARP_EXT),
|
||||
json.dumps([29, 23, 24]), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def _gen_electron_profiles() -> list[tuple]:
|
||||
"""Generate Electron app profiles (Chromium-based)."""
|
||||
profiles = []
|
||||
apps = [
|
||||
("vscode", "VS Code"), ("slack", "Slack"), ("discord", "Discord"),
|
||||
("teams", "Microsoft Teams"), ("signal_desktop", "Signal Desktop"),
|
||||
("spotify", "Spotify Desktop"), ("figma", "Figma Desktop"),
|
||||
("notion", "Notion"), ("obsidian", "Obsidian"),
|
||||
("1password", "1Password"), ("bitwarden", "Bitwarden"),
|
||||
("postman", "Postman"), ("whatsapp_desktop", "WhatsApp Desktop"),
|
||||
]
|
||||
|
||||
for app_id, app_name in apps:
|
||||
# Electron uses Chromium's TLS stack
|
||||
ciphers = CHROMIUM_CIPHERS_V2
|
||||
ext = CHROME_EXT_V2
|
||||
curves = CHROME_CURVES
|
||||
ja3_hash = compute_ja3(771, ciphers, ext, curves, EC_FORMATS_STANDARD)
|
||||
name = f"electron_{app_id}"
|
||||
desc = f"Electron: {app_name}"
|
||||
profiles.append((
|
||||
name, ja3_hash, desc,
|
||||
json.dumps(ciphers), json.dumps(ext),
|
||||
json.dumps(curves), json.dumps(EC_FORMATS_STANDARD),
|
||||
))
|
||||
|
||||
return profiles
|
||||
|
||||
|
||||
def generate_all_profiles() -> list[tuple]:
|
||||
"""Generate all JA3 profiles. Returns list of (name, hash, desc, ciphers, ext, curves, formats)."""
|
||||
all_profiles = []
|
||||
all_profiles.extend(_gen_chrome_profiles())
|
||||
all_profiles.extend(_gen_firefox_profiles())
|
||||
all_profiles.extend(_gen_edge_profiles())
|
||||
all_profiles.extend(_gen_safari_profiles())
|
||||
all_profiles.extend(_gen_opera_profiles())
|
||||
all_profiles.extend(_gen_brave_profiles())
|
||||
all_profiles.extend(_gen_tor_profiles())
|
||||
all_profiles.extend(_gen_library_profiles())
|
||||
all_profiles.extend(_gen_tool_profiles())
|
||||
all_profiles.extend(_gen_mobile_profiles())
|
||||
all_profiles.extend(_gen_iot_embedded_profiles())
|
||||
all_profiles.extend(_gen_chromeos_profiles())
|
||||
all_profiles.extend(_gen_vivaldi_profiles())
|
||||
all_profiles.extend(_gen_vpn_client_profiles())
|
||||
all_profiles.extend(_gen_electron_profiles())
|
||||
|
||||
# Deduplicate by name
|
||||
seen = set()
|
||||
unique = []
|
||||
for p in all_profiles:
|
||||
if p[0] not in seen:
|
||||
seen.add(p[0])
|
||||
unique.append(p)
|
||||
|
||||
return unique
|
||||
|
||||
|
||||
def seed_ja3_db(db_path: str) -> int:
|
||||
"""Seed a ja3_fingerprints.db with all generated profiles.
|
||||
|
||||
Returns the total number of profiles in the database.
|
||||
"""
|
||||
profiles = generate_all_profiles()
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS ja3_profiles (
|
||||
name TEXT PRIMARY KEY,
|
||||
ja3_hash TEXT NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
cipher_suites TEXT NOT NULL DEFAULT '[]',
|
||||
extensions TEXT NOT NULL DEFAULT '[]',
|
||||
elliptic_curves TEXT NOT NULL DEFAULT '[]',
|
||||
ec_point_formats TEXT NOT NULL DEFAULT '[0]'
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_ja3_hash ON ja3_profiles(ja3_hash);
|
||||
""")
|
||||
|
||||
conn.executemany(
|
||||
"INSERT OR REPLACE INTO ja3_profiles "
|
||||
"(name, ja3_hash, description, cipher_suites, extensions, elliptic_curves, ec_point_formats) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
profiles,
|
||||
)
|
||||
conn.commit()
|
||||
count = conn.execute("SELECT COUNT(*) FROM ja3_profiles").fetchone()[0]
|
||||
conn.close()
|
||||
return count
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
if len(sys.argv) > 1:
|
||||
db_path = sys.argv[1]
|
||||
else:
|
||||
db_path = str(Path(__file__).resolve().parent.parent / "data" / "ja3_fingerprints.db")
|
||||
|
||||
count = seed_ja3_db(db_path)
|
||||
print(f"Seeded {count} JA3 profiles to {db_path}")
|
||||
Reference in New Issue
Block a user