#!/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 OR IGNORE 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 OR IGNORE 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 and seed JA3 TLS client fingerprint database with 500+ profiles. Uses seed_ja3.py to generate comprehensive profiles across browsers, TLS libraries, tools, mobile clients, and IoT devices. """ from seed_ja3 import seed_ja3_db count = seed_ja3_db(db_path) 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()