0f754cfb7f
build_data.py created 'profiles' but mac_manager queries 'mac_profiles'. Columns category/model renamed to device_type/device_name. device_type values aligned with _CATEGORY_MAP in mac_manager (streaming, smart_tv, phone, tablet, smart_speaker, iot, printer, gaming). ja3_fingerprints.db table renamed from 'fingerprints' to 'ja3_profiles' and expanded with all columns ja3_spoofer queries (name, description, cipher_suites, extensions, elliptic_curves, ec_point_formats as JSON). StateManager.reset_module_status() clears stale 'running' entries on startup so watchdog doesn't flag old-session PIDs as dead modules. Engine.start_all() calls reset before launch and does a 3s post-startup liveness check, logging any modules that crashed immediately after fork.
510 lines
22 KiB
Python
Executable File
510 lines
22 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Build SQLite data databases for SystemMonitor.
|
|
|
|
Creates:
|
|
- innocuous_macs.db : Consumer device MAC profiles for stealth spoofing
|
|
- oui.db : IEEE OUI vendor lookup (schema only, populated at first run)
|
|
- os_sigs.db : Passive OS fingerprint signatures (schema only)
|
|
- dhcp_fingerprints.db : DHCP option 55 fingerprints (schema only)
|
|
- ja3_fingerprints.db : JA3 TLS client fingerprints (schema only)
|
|
|
|
Called by setup.sh during bootstrap.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import sqlite3
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def create_innocuous_macs_db(db_path: str) -> None:
|
|
"""Create innocuous MAC profiles database with ~50 real device profiles.
|
|
|
|
Each profile includes OUI, vendor, device_name, DHCP hostname pattern,
|
|
DHCP vendor class, TTL, TCP window size, and OS family -- everything
|
|
needed to impersonate the device at the network fingerprint level.
|
|
|
|
Table name and column names match what mac_manager.py queries:
|
|
mac_profiles(device_type, device_name, vendor, oui, ...)
|
|
"""
|
|
conn = sqlite3.connect(db_path)
|
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
conn.executescript("""
|
|
CREATE TABLE IF NOT EXISTS mac_profiles (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
device_type TEXT NOT NULL,
|
|
oui TEXT NOT NULL,
|
|
vendor TEXT NOT NULL,
|
|
device_name TEXT NOT NULL,
|
|
dhcp_hostname TEXT,
|
|
dhcp_vendor_class TEXT,
|
|
ttl INTEGER DEFAULT 64,
|
|
tcp_window INTEGER DEFAULT 65535,
|
|
os_family TEXT DEFAULT 'Linux'
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_mac_profiles_type ON mac_profiles(device_type);
|
|
CREATE INDEX IF NOT EXISTS idx_mac_profiles_oui ON mac_profiles(oui);
|
|
""")
|
|
|
|
profiles = [
|
|
# ── Streaming devices ───────────────────────────────────────────
|
|
("streaming", "FC:65:DE", "Amazon", "Fire TV Stick 4K",
|
|
"amazon-", "AmazonFireTV", 64, 26883, "Android"),
|
|
("streaming", "A0:02:DC", "Amazon", "Fire TV Stick Lite",
|
|
"amazon-", "AmazonFireTV", 64, 26883, "Android"),
|
|
("streaming", "44:D5:F2", "Amazon", "Fire TV Cube",
|
|
"amazon-", "AmazonFireTV", 64, 26883, "Android"),
|
|
("streaming", "D8:31:34", "Roku", "Roku Ultra",
|
|
"Roku-", "Roku/DVP-12.0", 64, 65535, "Linux"),
|
|
("streaming", "B0:A7:37", "Roku", "Roku Express",
|
|
"Roku-", "Roku/DVP-10.0", 64, 65535, "Linux"),
|
|
("streaming", "54:60:09", "Google", "Chromecast",
|
|
"Chromecast-", "Google", 64, 65535, "Linux"),
|
|
("streaming", "F4:F5:D8", "Google", "Chromecast with Google TV",
|
|
"Chromecast-", "Google", 64, 65535, "Android"),
|
|
("streaming", "A4:83:E7", "Apple", "Apple TV 4K",
|
|
"Apple-TV", "AAPLBK", 64, 65535, "tvOS"),
|
|
("streaming", "40:CB:C0", "Apple", "Apple TV HD",
|
|
"Apple-TV", "AAPLBK", 64, 65535, "tvOS"),
|
|
|
|
# ── Smart TVs ──────────────────────────────────────────────────
|
|
("smart_tv", "8C:79:F5", "Samsung", "Samsung Smart TV",
|
|
"Samsung-TV", "Samsung", 128, 65535, "Tizen"),
|
|
("smart_tv", "BC:14:EF", "Samsung", "Samsung QLED TV",
|
|
"Samsung-TV", "Samsung", 128, 65535, "Tizen"),
|
|
("smart_tv", "A8:23:FE", "LG", "LG webOS TV",
|
|
"LG-TV", "LG", 64, 65535, "webOS"),
|
|
("smart_tv", "58:FD:B1", "LG", "LG OLED TV",
|
|
"LG-TV", "LG", 64, 65535, "webOS"),
|
|
("smart_tv", "D4:38:9C", "Vizio", "Vizio SmartCast TV",
|
|
"VIZIO-TV", "Vizio", 64, 65535, "Linux"),
|
|
("smart_tv", "C0:79:82", "TCL", "TCL Roku TV",
|
|
"TCL-TV", "Roku/DVP", 64, 65535, "Linux"),
|
|
("smart_tv", "C8:02:10", "Sony", "Sony Bravia Android TV",
|
|
"Sony-TV", "Sony", 64, 65535, "Android"),
|
|
|
|
# ── Smart speakers / assistants ────────────────────────────────
|
|
("smart_speaker", "48:A6:B8", "Sonos", "Sonos One",
|
|
"Sonos-", "Sonos", 64, 65535, "Linux"),
|
|
("smart_speaker", "5C:AA:FD", "Sonos", "Sonos Beam",
|
|
"Sonos-", "Sonos", 64, 65535, "Linux"),
|
|
("smart_speaker", "30:FD:38", "Google", "Google Home Mini",
|
|
"Google-Home-", "Google", 64, 65535, "Linux"),
|
|
("smart_speaker", "1C:F2:9A", "Google", "Google Nest Hub",
|
|
"Google-Nest-", "Google", 64, 65535, "Linux"),
|
|
("smart_speaker", "68:54:FD", "Amazon", "Echo Dot",
|
|
"amazon-", "AmazonEcho", 64, 26883, "Android"),
|
|
("smart_speaker", "74:C2:46", "Amazon", "Echo Show",
|
|
"amazon-", "AmazonEcho", 64, 26883, "Android"),
|
|
|
|
# ── Smart home / IoT ──────────────────────────────────────────
|
|
("iot", "4C:EB:D6", "Amazon", "Ring Doorbell",
|
|
"Ring-", "Amazon", 64, 65535, "Linux"),
|
|
("iot", "18:B4:30", "Nest", "Nest Thermostat",
|
|
"Nest-", "Nest", 64, 65535, "Linux"),
|
|
("iot", "18:B4:30", "Nest", "Nest Protect",
|
|
"Nest-", "Nest", 64, 65535, "Linux"),
|
|
("iot", "00:17:88", "Philips", "Philips Hue Bridge",
|
|
"Philips-hue", "Philips-hue", 64, 65535, "Linux"),
|
|
("iot", "D8:0D:17", "TP-Link", "TP-Link Kasa Smart Plug",
|
|
"TP-Link-", "TP-Link", 64, 65535, "Linux"),
|
|
("iot", "2C:AA:8E", "Wyze", "Wyze Cam v3",
|
|
"Wyze-", "Wyze", 64, 65535, "Linux"),
|
|
("iot", "44:61:32", "Ecobee", "Ecobee Thermostat",
|
|
"ecobee-", "ecobee", 64, 65535, "Linux"),
|
|
("iot", "50:14:79", "iRobot", "Roomba i7",
|
|
"iRobot-", "iRobot", 64, 65535, "Linux"),
|
|
("iot", "24:E1:24", "iRobot", "Roomba j7",
|
|
"iRobot-", "iRobot", 64, 65535, "Linux"),
|
|
("iot", "DC:A6:32", "Raspberry Pi", "IoT Sensor",
|
|
"raspberrypi", "RaspberryPi", 64, 29200, "Linux"),
|
|
("iot", "B4:E6:2D", "Raspberry Pi", "IoT Gateway",
|
|
"raspberrypi", "RaspberryPi", 64, 29200, "Linux"),
|
|
("iot", "B8:27:EB", "Raspberry Pi", "IoT Controller",
|
|
"raspberrypi", "RaspberryPi", 64, 29200, "Linux"),
|
|
|
|
# ── Printers ──────────────────────────────────────────────────
|
|
("printer", "3C:D9:2B", "HP", "HP LaserJet Pro",
|
|
"HP", "Hewlett-Packard", 128, 8192, "HP JetDirect"),
|
|
("printer", "C8:B5:B7", "HP", "HP OfficeJet Pro",
|
|
"HP", "Hewlett-Packard", 128, 8192, "HP JetDirect"),
|
|
("printer", "30:05:5C", "Brother", "Brother HL-L2350DW",
|
|
"BRN", "Brother", 128, 8192, "Brother"),
|
|
("printer", "00:80:77", "Brother", "Brother MFC-L2750DW",
|
|
"BRN", "Brother", 128, 8192, "Brother"),
|
|
("printer", "00:1B:A9", "Brother", "Brother DCP-L2550DW",
|
|
"BRN", "Brother", 128, 8192, "Brother"),
|
|
("printer", "2C:F0:5D", "Epson", "Epson EcoTank ET-4760",
|
|
"EPSON", "EPSON", 128, 8192, "Epson"),
|
|
("printer", "64:EB:8C", "Canon", "Canon PIXMA G6020",
|
|
"Canon", "Canon", 128, 8192, "Canon"),
|
|
|
|
# ── Gaming consoles ───────────────────────────────────────────
|
|
("gaming", "7C:ED:8D", "Microsoft", "Xbox Series X",
|
|
"Xbox-", "Microsoft", 128, 65535, "Windows"),
|
|
("gaming", "00:D9:D1", "Sony", "PlayStation 5",
|
|
"PS5-", "Sony", 64, 65535, "FreeBSD"),
|
|
("gaming", "98:B6:E9", "Sony", "PlayStation 4",
|
|
"PS4-", "Sony", 64, 65535, "FreeBSD"),
|
|
("gaming", "7C:BB:8A", "Nintendo", "Nintendo Switch",
|
|
"Nintendo-", "Nintendo", 64, 65535, "Nintendo"),
|
|
|
|
# ── Phones / tablets ──────────────────────────────────────────
|
|
("phone", "A4:83:E7", "Apple", "iPhone 15 Pro",
|
|
"iphone", "dhcpcd-6.x.x", 64, 65535, "iOS"),
|
|
("phone", "40:CB:C0", "Apple", "iPad Air",
|
|
"ipad", "dhcpcd-6.x.x", 64, 65535, "iPadOS"),
|
|
("tablet", "3C:28:6D", "Samsung", "Galaxy Tab S9",
|
|
"Samsung-Tab", "dhcpcd-6.x.x", 64, 65535, "Android"),
|
|
("phone", "DC:1A:C5", "Samsung", "Galaxy S24",
|
|
"Galaxy-S", "dhcpcd-6.x.x", 64, 65535, "Android"),
|
|
|
|
# ── Network gear (hide as infrastructure) ─────────────────────
|
|
("iot", "B4:FB:E4", "Ubiquiti", "UniFi AP",
|
|
"UAP-", "Ubiquiti", 64, 65535, "Linux"),
|
|
("iot", "78:8A:20", "Ubiquiti", "USG Gateway",
|
|
"USG-", "Ubiquiti", 64, 65535, "Linux"),
|
|
("iot", "14:CC:20", "TP-Link", "TP-Link Archer AX50",
|
|
"TL-", "TP-Link", 64, 65535, "Linux"),
|
|
("iot", "AC:84:C6", "TP-Link", "TP-Link Deco M5",
|
|
"Deco-", "TP-Link", 64, 65535, "Linux"),
|
|
("iot", "20:A6:CD", "Netgear", "Netgear Orbi",
|
|
"Orbi-", "Netgear", 64, 65535, "Linux"),
|
|
("iot", "C4:04:15", "Netgear", "Netgear Nighthawk",
|
|
"NETGEAR-", "Netgear", 64, 65535, "Linux"),
|
|
]
|
|
|
|
conn.executemany("""
|
|
INSERT INTO mac_profiles (device_type, oui, vendor, device_name,
|
|
dhcp_hostname, dhcp_vendor_class,
|
|
ttl, tcp_window, os_family)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
""", profiles)
|
|
|
|
conn.commit()
|
|
count = conn.execute("SELECT COUNT(*) FROM mac_profiles").fetchone()[0]
|
|
conn.close()
|
|
print(f" innocuous_macs.db: {count} device profiles")
|
|
|
|
|
|
def create_oui_db(db_path: str) -> None:
|
|
"""Create OUI database schema (populated from IEEE list at first run)."""
|
|
conn = sqlite3.connect(db_path)
|
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
conn.executescript("""
|
|
CREATE TABLE IF NOT EXISTS oui (
|
|
prefix TEXT PRIMARY KEY,
|
|
vendor TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_oui_vendor ON oui(vendor);
|
|
""")
|
|
|
|
# Seed with common OUIs used by profiles to ensure basic functionality
|
|
# before the full IEEE list is downloaded
|
|
seed_ouis = [
|
|
("FC:65:DE", "Amazon Technologies Inc."),
|
|
("A0:02:DC", "Amazon Technologies Inc."),
|
|
("44:D5:F2", "Amazon Technologies Inc."),
|
|
("D8:31:34", "Roku, Inc."),
|
|
("B0:A7:37", "Roku, Inc."),
|
|
("54:60:09", "Google, Inc."),
|
|
("F4:F5:D8", "Google, Inc."),
|
|
("A4:83:E7", "Apple, Inc."),
|
|
("40:CB:C0", "Apple, Inc."),
|
|
("8C:79:F5", "Samsung Electronics Co.,Ltd"),
|
|
("BC:14:EF", "Samsung Electronics Co.,Ltd"),
|
|
("A8:23:FE", "LG Electronics"),
|
|
("58:FD:B1", "LG Electronics"),
|
|
("D4:38:9C", "Vizio, Inc."),
|
|
("C0:79:82", "TCL King Electrical Appliances"),
|
|
("C8:02:10", "Sony Interactive Entertainment Inc."),
|
|
("48:A6:B8", "Sonos, Inc."),
|
|
("5C:AA:FD", "Sonos, Inc."),
|
|
("30:FD:38", "Google, Inc."),
|
|
("1C:F2:9A", "Google, Inc."),
|
|
("68:54:FD", "Amazon Technologies Inc."),
|
|
("74:C2:46", "Amazon Technologies Inc."),
|
|
("4C:EB:D6", "Amazon Technologies Inc."),
|
|
("18:B4:30", "Nest Labs Inc."),
|
|
("00:17:88", "Philips Lighting BV"),
|
|
("D8:0D:17", "TP-Link Technologies Co.,Ltd."),
|
|
("2C:AA:8E", "Wyze Labs Inc."),
|
|
("44:61:32", "ecobee inc."),
|
|
("50:14:79", "iRobot Corporation"),
|
|
("24:E1:24", "iRobot Corporation"),
|
|
("DC:A6:32", "Raspberry Pi Trading Ltd"),
|
|
("B4:E6:2D", "Raspberry Pi Trading Ltd"),
|
|
("B8:27:EB", "Raspberry Pi Foundation"),
|
|
("3C:D9:2B", "HP Inc."),
|
|
("C8:B5:B7", "HP Inc."),
|
|
("30:05:5C", "Brother Industries, LTD."),
|
|
("00:80:77", "Brother Industries, LTD."),
|
|
("00:1B:A9", "Brother Industries, LTD."),
|
|
("2C:F0:5D", "Seiko Epson Corporation"),
|
|
("64:EB:8C", "Canon Inc."),
|
|
("7C:ED:8D", "Microsoft Corporation"),
|
|
("00:D9:D1", "Sony Interactive Entertainment Inc."),
|
|
("98:B6:E9", "Sony Interactive Entertainment Inc."),
|
|
("7C:BB:8A", "Nintendo Co.,Ltd"),
|
|
("B4:FB:E4", "Ubiquiti Inc"),
|
|
("78:8A:20", "Ubiquiti Inc"),
|
|
("14:CC:20", "TP-Link Technologies Co.,Ltd."),
|
|
("AC:84:C6", "TP-Link Technologies Co.,Ltd."),
|
|
("20:A6:CD", "NETGEAR"),
|
|
("C4:04:15", "NETGEAR"),
|
|
]
|
|
|
|
conn.executemany(
|
|
"INSERT OR IGNORE INTO oui (prefix, vendor) VALUES (?, ?)",
|
|
seed_ouis
|
|
)
|
|
|
|
conn.commit()
|
|
count = conn.execute("SELECT COUNT(*) FROM oui").fetchone()[0]
|
|
conn.close()
|
|
print(f" oui.db: {count} seed OUI entries (full IEEE list loaded at first run)")
|
|
|
|
|
|
def create_os_sigs_db(db_path: str) -> None:
|
|
"""Create passive OS fingerprint signature database.
|
|
|
|
p0f-style: TCP SYN parameters -> OS identification.
|
|
"""
|
|
conn = sqlite3.connect(db_path)
|
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
conn.executescript("""
|
|
CREATE TABLE IF NOT EXISTS signatures (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ttl INTEGER NOT NULL,
|
|
window INTEGER NOT NULL,
|
|
df INTEGER NOT NULL DEFAULT 1,
|
|
mss INTEGER DEFAULT 0,
|
|
os TEXT NOT NULL,
|
|
version TEXT DEFAULT ''
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_sigs_ttl_window ON signatures(ttl, window);
|
|
""")
|
|
|
|
# Common OS fingerprints (TTL, window, DF, MSS, OS, version)
|
|
sigs = [
|
|
# Linux
|
|
(64, 29200, 1, 1460, "Linux", "3.x-5.x"),
|
|
(64, 65535, 1, 1460, "Linux", "2.6.x"),
|
|
(64, 5840, 1, 1460, "Linux", "2.6.x (older)"),
|
|
(64, 14600, 1, 1460, "Linux", "3.x"),
|
|
(64, 26883, 1, 1460, "Linux", "Android"),
|
|
# Windows
|
|
(128, 65535, 1, 1460, "Windows", "10/11/Server 2016+"),
|
|
(128, 8192, 1, 1460, "Windows", "7/Server 2008"),
|
|
(128, 16384, 1, 1460, "Windows", "Vista"),
|
|
(128, 64240, 1, 1460, "Windows", "10"),
|
|
# macOS
|
|
(64, 65535, 1, 1460, "macOS", "10.x-14.x"),
|
|
# FreeBSD
|
|
(64, 65535, 1, 1460, "FreeBSD", "12.x-14.x"),
|
|
# iOS / tvOS
|
|
(64, 65535, 1, 1460, "iOS", "15.x-17.x"),
|
|
# Network devices
|
|
(255, 4128, 1, 536, "Cisco IOS", ""),
|
|
(64, 16384, 1, 1460, "Juniper JunOS", ""),
|
|
# Printers
|
|
(128, 8192, 1, 1460, "HP JetDirect", ""),
|
|
(128, 8192, 1, 1460, "Brother", ""),
|
|
]
|
|
|
|
conn.executemany("""
|
|
INSERT INTO signatures (ttl, window, df, mss, os, version)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
""", sigs)
|
|
|
|
conn.commit()
|
|
count = conn.execute("SELECT COUNT(*) FROM signatures").fetchone()[0]
|
|
conn.close()
|
|
print(f" os_sigs.db: {count} OS fingerprint signatures")
|
|
|
|
|
|
def create_dhcp_fingerprints_db(db_path: str) -> None:
|
|
"""Create DHCP option 55 fingerprint database.
|
|
|
|
Maps DHCP parameter request lists to device/OS identification.
|
|
Fingerbank-style: the ordered list of requested DHCP options is a
|
|
surprisingly reliable device identifier.
|
|
"""
|
|
conn = sqlite3.connect(db_path)
|
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
conn.executescript("""
|
|
CREATE TABLE IF NOT EXISTS fingerprints (
|
|
fingerprint TEXT PRIMARY KEY,
|
|
device TEXT NOT NULL,
|
|
os TEXT DEFAULT ''
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_fp_device ON fingerprints(device);
|
|
""")
|
|
|
|
# Common DHCP fingerprints (option 55 parameter request lists)
|
|
fps = [
|
|
("1,3,6,15,26,28,51,58,59", "Windows", "Windows 10/11"),
|
|
("1,3,6,15,26,28,51,58,59,43", "Windows", "Windows 10 (domain)"),
|
|
("1,15,3,6,44,46,47,31,33,121,249,43", "Windows", "Windows 7"),
|
|
("1,3,6,15,119,252", "macOS", "macOS 10.x-14.x"),
|
|
("1,121,3,6,15,119,252", "macOS", "macOS 12+"),
|
|
("1,3,6,15,119,252,95,44,46", "iOS", "iOS 15+"),
|
|
("1,3,6,28,51,58,59", "Android", "Android 10+"),
|
|
("1,3,6,15,26,28", "Linux", "Linux (generic)"),
|
|
("1,28,2,3,15,6,12", "Linux", "Linux (systemd-networkd)"),
|
|
("1,3,6,12,15,26,28,42", "Roku", "Roku OS"),
|
|
("1,3,6,12,15,28,42,125", "Amazon", "Fire TV / Echo"),
|
|
("1,3,6,15,28,33", "Chromecast", "Google Cast"),
|
|
("1,3,6,12,15,28,42", "Apple TV", "tvOS"),
|
|
("1,3,6,15,44,46,47", "Sonos", "Sonos"),
|
|
("1,3,6,12,15,28,42,120,119", "Samsung TV", "Tizen"),
|
|
("1,3,6,12,15,17,28,40,41,42", "HP Printer", "JetDirect"),
|
|
("1,3,6,12,15,28,44", "Brother Printer", "Brother"),
|
|
("1,3,6,12,15", "Philips Hue", "Hue Bridge"),
|
|
("1,3,6,15,28", "IoT Generic", "Embedded Linux"),
|
|
]
|
|
|
|
conn.executemany(
|
|
"INSERT OR IGNORE INTO fingerprints (fingerprint, device, os) VALUES (?, ?, ?)",
|
|
fps
|
|
)
|
|
|
|
conn.commit()
|
|
count = conn.execute("SELECT COUNT(*) FROM fingerprints").fetchone()[0]
|
|
conn.close()
|
|
print(f" dhcp_fingerprints.db: {count} DHCP fingerprints")
|
|
|
|
|
|
def create_ja3_fingerprints_db(db_path: str) -> None:
|
|
"""Create JA3 TLS client fingerprint database.
|
|
|
|
JA3 hashes uniquely identify TLS client implementations.
|
|
Used by ja3_spoofer to match outbound traffic to common browsers.
|
|
|
|
Table name and column names match what ja3_spoofer.py queries:
|
|
ja3_profiles(name, ja3_hash, description, cipher_suites,
|
|
extensions, elliptic_curves, ec_point_formats)
|
|
All list columns are stored as JSON arrays.
|
|
"""
|
|
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);
|
|
""")
|
|
|
|
# Mirror of BUILTIN_PROFILES in ja3_spoofer.py — ensures the DB has
|
|
# the same data available for external queries and future updates.
|
|
import json as _json
|
|
ja3s = [
|
|
(
|
|
"chrome_120_win",
|
|
"cd08e31494f9531f560d64c695473da9",
|
|
"Chrome 120 on Windows 10/11",
|
|
_json.dumps([0x1301, 0x1302, 0x1303, 0xc02b, 0xc02f, 0xc02c, 0xc030,
|
|
0xcca9, 0xcca8, 0xc013, 0xc014, 0x009c, 0x009d, 0x002f, 0x0035]),
|
|
_json.dumps([0, 23, 65281, 10, 11, 35, 16, 5, 13, 18, 51, 45, 43, 27, 17513, 21]),
|
|
_json.dumps([0x001d, 0x0017, 0x0018]),
|
|
_json.dumps([0]),
|
|
),
|
|
(
|
|
"firefox_121_win",
|
|
"579ccef312d18482fc42e2b822ca2430",
|
|
"Firefox 121 on Windows 10/11",
|
|
_json.dumps([0x1301, 0x1303, 0x1302, 0xc02b, 0xc02f, 0xcca9, 0xcca8,
|
|
0xc02c, 0xc030, 0xc013, 0xc014, 0x009c, 0x009d, 0x002f, 0x0035]),
|
|
_json.dumps([0, 23, 65281, 10, 11, 35, 16, 5, 34, 51, 43, 13, 45, 28, 21]),
|
|
_json.dumps([0x001d, 0x0017, 0x0018, 0x0019]),
|
|
_json.dumps([0]),
|
|
),
|
|
(
|
|
"edge_120_win",
|
|
"b32309a26951912be7dba376398abc3b",
|
|
"Edge 120 on Windows 10/11",
|
|
_json.dumps([0x1301, 0x1302, 0x1303, 0xc02b, 0xc02f, 0xc02c, 0xc030,
|
|
0xcca9, 0xcca8, 0xc013, 0xc014, 0x009c, 0x009d, 0x002f, 0x0035]),
|
|
_json.dumps([0, 23, 65281, 10, 11, 35, 16, 5, 13, 18, 51, 45, 43, 27, 17513, 21]),
|
|
_json.dumps([0x001d, 0x0017, 0x0018]),
|
|
_json.dumps([0]),
|
|
),
|
|
(
|
|
"chrome_120_linux",
|
|
"a17a3bfd385b62b1e15606dbd08c9f89",
|
|
"Chrome 120 on Linux",
|
|
_json.dumps([0x1301, 0x1302, 0x1303, 0xc02b, 0xc02f, 0xc02c, 0xc030,
|
|
0xcca9, 0xcca8, 0xc013, 0xc014, 0x009c, 0x009d, 0x002f, 0x0035]),
|
|
_json.dumps([0, 23, 65281, 10, 11, 35, 16, 5, 13, 18, 51, 45, 43, 27, 17513, 21]),
|
|
_json.dumps([0x001d, 0x0017, 0x0018]),
|
|
_json.dumps([0]),
|
|
),
|
|
]
|
|
|
|
conn.executemany(
|
|
"INSERT OR IGNORE INTO ja3_profiles "
|
|
"(name, ja3_hash, description, cipher_suites, extensions, elliptic_curves, ec_point_formats) "
|
|
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
ja3s,
|
|
)
|
|
|
|
conn.commit()
|
|
count = conn.execute("SELECT COUNT(*) FROM ja3_profiles").fetchone()[0]
|
|
conn.close()
|
|
print(f" ja3_fingerprints.db: {count} JA3 profiles")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Build SystemMonitor data databases")
|
|
parser.add_argument(
|
|
"--output-dir", "-o",
|
|
default=os.path.join(os.path.dirname(os.path.dirname(__file__)), "data"),
|
|
help="Output directory for database files"
|
|
)
|
|
parser.add_argument(
|
|
"--force", "-f",
|
|
action="store_true",
|
|
help="Overwrite existing databases"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
output_dir = Path(args.output_dir)
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
print(f"Building data databases in {output_dir}/")
|
|
|
|
databases = [
|
|
("innocuous_macs.db", create_innocuous_macs_db),
|
|
("oui.db", create_oui_db),
|
|
("os_sigs.db", create_os_sigs_db),
|
|
("dhcp_fingerprints.db", create_dhcp_fingerprints_db),
|
|
("ja3_fingerprints.db", create_ja3_fingerprints_db),
|
|
]
|
|
|
|
for db_name, create_func in databases:
|
|
db_path = str(output_dir / db_name)
|
|
if os.path.exists(db_path) and not args.force:
|
|
print(f" {db_name}: already exists (use --force to rebuild)")
|
|
continue
|
|
if os.path.exists(db_path):
|
|
os.remove(db_path)
|
|
create_func(db_path)
|
|
|
|
print("Done.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|