From f90a686626b8aac9fe1157d346cbfc0407986582 Mon Sep 17 00:00:00 2001 From: n0mad1k Date: Wed, 18 Mar 2026 08:13:55 -0400 Subject: [PATCH] Phase 1: setup script and data bootstrapping - setup.sh: Idempotent bootstrap for OPi Zero 3 / RPi Zero 2W / Debian Platform detection, system packages, Pi optimizations (gpu_mem, swap, sysctl), directory structure, Python venv, bettercap binary install, external tools (Responder, NetExec, Chisel, Ligolo, kerbrute, etc), data database creation, systemd service install, permissions, verification. Supports --reinstall, --jumpbox, --no-tools, --verify-only flags. - scripts/build_data.py: Creates 5 SQLite databases innocuous_macs.db (51 consumer device profiles with OUI/DHCP/TCP fingerprints), oui.db (50 seed OUIs), os_sigs.db (16 p0f-style signatures), dhcp_fingerprints.db (19 option-55 fingerprints), ja3_fingerprints.db (13 hashes) - scripts/build_lkm.sh: LKM rootkit compiler wrapper - scripts/rotate_pcap.sh: tcpdump post-rotation handler with zstd compression, optional AES-256-GCM encryption, disk space monitoring, and auto-purge --- scripts/build_data.py | 458 +++++++++++++++++++++++ scripts/build_lkm.sh | 54 +++ scripts/rotate_pcap.sh | 108 ++++++ setup.sh | 827 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1447 insertions(+) create mode 100755 scripts/build_data.py create mode 100755 scripts/build_lkm.sh create mode 100755 scripts/rotate_pcap.sh create mode 100755 setup.sh diff --git a/scripts/build_data.py b/scripts/build_data.py new file mode 100755 index 0000000..d3b7abe --- /dev/null +++ b/scripts/build_data.py @@ -0,0 +1,458 @@ +#!/usr/bin/env python3 +"""Build SQLite data databases for BigBrother. + +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, model, DHCP hostname pattern, + DHCP vendor class, TTL, TCP window size, and OS family -- everything + needed to impersonate the device at the network fingerprint level. + """ + conn = sqlite3.connect(db_path) + conn.execute("PRAGMA journal_mode=WAL") + conn.executescript(""" + CREATE TABLE IF NOT EXISTS profiles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + category TEXT NOT NULL, + oui TEXT NOT NULL, + vendor TEXT NOT NULL, + model 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_profiles_category ON profiles(category); + CREATE INDEX IF NOT EXISTS idx_profiles_oui ON 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 ──────────────────────────────── + ("speaker", "48:A6:B8", "Sonos", "Sonos One", + "Sonos-", "Sonos", 64, 65535, "Linux"), + ("speaker", "5C:AA:FD", "Sonos", "Sonos Beam", + "Sonos-", "Sonos", 64, 65535, "Linux"), + ("speaker", "30:FD:38", "Google", "Google Home Mini", + "Google-Home-", "Google", 64, 65535, "Linux"), + ("speaker", "1C:F2:9A", "Google", "Google Nest Hub", + "Google-Nest-", "Google", 64, 65535, "Linux"), + ("speaker", "68:54:FD", "Amazon", "Echo Dot", + "amazon-", "AmazonEcho", 64, 26883, "Android"), + ("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"), + + # ── Network gear (hide as infrastructure) ───────────────────── + ("network", "B4:FB:E4", "Ubiquiti", "UniFi AP", + "UAP-", "Ubiquiti", 64, 65535, "Linux"), + ("network", "78:8A:20", "Ubiquiti", "USG Gateway", + "USG-", "Ubiquiti", 64, 65535, "Linux"), + ("network", "14:CC:20", "TP-Link", "TP-Link Archer AX50", + "TL-", "TP-Link", 64, 65535, "Linux"), + ("network", "AC:84:C6", "TP-Link", "TP-Link Deco M5", + "Deco-", "TP-Link", 64, 65535, "Linux"), + ("network", "20:A6:CD", "Netgear", "Netgear Orbi", + "Orbi-", "Netgear", 64, 65535, "Linux"), + ("network", "C4:04:15", "Netgear", "Netgear Nighthawk", + "NETGEAR-", "Netgear", 64, 65535, "Linux"), + ] + + conn.executemany(""" + INSERT INTO profiles (category, oui, vendor, model, + dhcp_hostname, dhcp_vendor_class, + ttl, tcp_window, os_family) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + """, profiles) + + conn.commit() + count = conn.execute("SELECT COUNT(*) FROM 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. + """ + conn = sqlite3.connect(db_path) + conn.execute("PRAGMA journal_mode=WAL") + conn.executescript(""" + CREATE TABLE IF NOT EXISTS fingerprints ( + ja3_hash TEXT PRIMARY KEY, + client TEXT NOT NULL, + version TEXT DEFAULT '' + ); + + CREATE INDEX IF NOT EXISTS idx_ja3_client ON fingerprints(client); + """) + + # Common JA3 hashes -- these rotate with browser versions but + # provide a baseline for spoofing. Updated at runtime via + # ja3er.com or similar feeds. + ja3s = [ + ("cd08e31494f9531f560d64c695473da9", "Chrome", "120+"), + ("b32309a26951912be7dba376398abc3b", "Chrome", "100-119"), + ("eb1d94daa7e0344597e756a1fb6e7054", "Firefox", "120+"), + ("e4bb02b26cf670ba1d2e4942440a1cfc", "Firefox", "100-119"), + ("773906b0efdefa24a7f2b8eb6985bf37", "Safari", "17+"), + ("2ce0b4f7f6e119e26091e74e5c635cca", "Safari", "16"), + ("56c70e24355b14b540300c0c15971b8a", "Edge", "120+"), + ("555af378b96073da365e42db3052cf7a", "Edge", "100-119"), + ("3b5074b1b5d032e5620f69f9f700ff0e", "curl", "7.x"), + ("456523fc94726331a4d5a2e1d40b2cd7", "Python requests", "2.x"), + ("e7d705a3286e19ea42f587b344ee6865", "Wget", "1.x"), + ("6734f37431670b3ab4292b8f60f29984", "Java", "11+"), + ("e35c1eda3a26177f1a3f0aebdc2bd319", "Go", "1.19+"), + ] + + conn.executemany( + "INSERT OR IGNORE INTO fingerprints (ja3_hash, client, version) VALUES (?, ?, ?)", + ja3s + ) + + conn.commit() + count = conn.execute("SELECT COUNT(*) FROM fingerprints").fetchone()[0] + conn.close() + print(f" ja3_fingerprints.db: {count} JA3 fingerprints") + + +def main(): + parser = argparse.ArgumentParser(description="Build BigBrother 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() diff --git a/scripts/build_lkm.sh b/scripts/build_lkm.sh new file mode 100755 index 0000000..1be8f67 --- /dev/null +++ b/scripts/build_lkm.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Build the BigBrother LKM rootkit module (Debian host only) +# Requires kernel headers: apt install linux-headers-$(uname -r) +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +LKM_DIR="$(cd "$(dirname "$0")/../templates/lkm" && pwd)" + +if [[ ! -d "$LKM_DIR" ]]; then + echo -e "${RED}[-]${NC} LKM source directory not found: $LKM_DIR" + exit 1 +fi + +if [[ ! -f "$LKM_DIR/Makefile" ]]; then + echo -e "${RED}[-]${NC} No Makefile found in $LKM_DIR" + exit 1 +fi + +# Check for kernel headers +KVER=$(uname -r) +if [[ ! -d "/lib/modules/${KVER}/build" ]]; then + echo -e "${RED}[-]${NC} Kernel headers not found for ${KVER}" + echo -e "${YELLOW}[!]${NC} Install with: apt install linux-headers-${KVER}" + exit 1 +fi + +# Check for build tools +for tool in make gcc; do + if ! command -v "$tool" &>/dev/null; then + echo -e "${RED}[-]${NC} Required tool not found: $tool" + exit 1 + fi +done + +echo -e "${GREEN}[+]${NC} Building LKM rootkit for kernel ${KVER}..." +cd "$LKM_DIR" +make clean 2>/dev/null || true +make + +if [[ -f "$LKM_DIR/bb_hide.ko" ]]; then + echo -e "${GREEN}[+]${NC} LKM built: $LKM_DIR/bb_hide.ko" + echo -e "${GREEN}[+]${NC} Size: $(stat -c '%s' "$LKM_DIR/bb_hide.ko") bytes" + echo "" + echo -e "${YELLOW}[!]${NC} Load with: insmod $LKM_DIR/bb_hide.ko" + echo -e "${YELLOW}[!]${NC} Verify: lsmod | grep bb_hide" + echo -e "${YELLOW}[!]${NC} Unload: rmmod bb_hide" +else + echo -e "${RED}[-]${NC} Build failed -- bb_hide.ko not found" + exit 1 +fi diff --git a/scripts/rotate_pcap.sh b/scripts/rotate_pcap.sh new file mode 100755 index 0000000..348b500 --- /dev/null +++ b/scripts/rotate_pcap.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# Post-rotation handler for tcpdump -z +# Compresses rotated PCAP with zstd, optionally encrypts with AES-256-GCM +# +# Called by tcpdump like: tcpdump -z /opt/.cache/bb/scripts/rotate_pcap.sh +# tcpdump passes the completed PCAP filename as $1 +# +# Environment variables: +# ZSTD_LEVEL - Compression level (default: 19, jumpbox: 3) +# BB_ENCRYPT - Set to "1" to encrypt after compression +# BB_KEY_FILE - Path to encryption key file (required if BB_ENCRYPT=1) +# BB_VENV - Path to Python venv (for encryption via crypto module) +# BB_MAX_PCAP_PCT - Max disk usage percent before purge (default: 85) +set -euo pipefail + +PCAP="$1" +ZSTD_LEVEL="${ZSTD_LEVEL:-19}" +BB_ENCRYPT="${BB_ENCRYPT:-0}" +BB_KEY_FILE="${BB_KEY_FILE:-}" +BB_VENV="${BB_VENV:-/opt/.cache/bb/.venv}" +BB_MAX_PCAP_PCT="${BB_MAX_PCAP_PCT:-85}" +PCAP_DIR="$(dirname "$PCAP")" + +# Logging (silent by default, tcpdump -z doesn't have a tty) +log() { + logger -t "bb-pcap-rotate" "$*" 2>/dev/null || true +} + +# Verify input file exists and is non-empty +if [[ ! -f "$PCAP" ]]; then + log "ERROR: PCAP file not found: $PCAP" + exit 0 # Exit 0 so tcpdump doesn't complain +fi + +if [[ ! -s "$PCAP" ]]; then + log "WARNING: Empty PCAP file, removing: $PCAP" + rm -f "$PCAP" + exit 0 +fi + +PCAP_SIZE=$(stat -c '%s' "$PCAP" 2>/dev/null || echo 0) +log "Rotating PCAP: $PCAP ($PCAP_SIZE bytes, zstd -${ZSTD_LEVEL})" + +# ── Disk space check ─────────────────────────────────────────────────────── +check_disk_space() { + local usage + usage=$(df --output=pcent "$PCAP_DIR" 2>/dev/null | tail -1 | tr -d '% ') + if [[ -n "$usage" ]] && [[ "$usage" -ge "$BB_MAX_PCAP_PCT" ]]; then + log "WARNING: Disk usage ${usage}% >= ${BB_MAX_PCAP_PCT}%, purging oldest PCAPs" + # Delete oldest compressed PCAPs first, then oldest raw + local oldest + oldest=$(find "$PCAP_DIR" -name '*.pcap.zst*' -type f -printf '%T+ %p\n' 2>/dev/null \ + | sort | head -5 | awk '{print $2}') + for f in $oldest; do + log "Purging: $f" + rm -f "$f" + done + fi +} + +check_disk_space + +# ── Compress with zstd ───────────────────────────────────────────────────── +COMPRESSED="${PCAP}.zst" + +if command -v zstd &>/dev/null; then + zstd "-${ZSTD_LEVEL}" --rm -q "$PCAP" -o "$COMPRESSED" 2>/dev/null + if [[ -f "$COMPRESSED" ]]; then + COMP_SIZE=$(stat -c '%s' "$COMPRESSED" 2>/dev/null || echo 0) + log "Compressed: ${PCAP_SIZE} -> ${COMP_SIZE} bytes (zstd -${ZSTD_LEVEL})" + else + log "ERROR: zstd compression failed for $PCAP" + exit 0 + fi +else + log "ERROR: zstd not found, leaving PCAP uncompressed" + exit 0 +fi + +# ── Encrypt (optional) ──────────────────────────────────────────────────── +if [[ "$BB_ENCRYPT" == "1" ]] && [[ -n "$BB_KEY_FILE" ]] && [[ -f "$BB_KEY_FILE" ]]; then + ENCRYPTED="${COMPRESSED}.enc" + + if [[ -f "${BB_VENV}/bin/python3" ]]; then + "${BB_VENV}/bin/python3" -c " +import sys +sys.path.insert(0, '$(dirname "$(dirname "$0")")') +from utils.crypto import encrypt_file +encrypt_file('${COMPRESSED}', '${ENCRYPTED}', + open('${BB_KEY_FILE}', 'rb').read().strip()) +" 2>/dev/null + + if [[ -f "$ENCRYPTED" ]]; then + rm -f "$COMPRESSED" + ENC_SIZE=$(stat -c '%s' "$ENCRYPTED" 2>/dev/null || echo 0) + log "Encrypted: ${COMPRESSED} -> ${ENCRYPTED} (${ENC_SIZE} bytes)" + else + log "WARNING: Encryption failed, keeping compressed file" + fi + else + log "WARNING: Python venv not found at ${BB_VENV}, skipping encryption" + fi +fi + +# ── Restrictive permissions ──────────────────────────────────────────────── +chmod 600 "${PCAP_DIR}"/*.zst* 2>/dev/null || true + +log "PCAP rotation complete" diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..68e3802 --- /dev/null +++ b/setup.sh @@ -0,0 +1,827 @@ +#!/usr/bin/env bash +# BigBrother setup -- Idempotent bootstrap for network surveillance implant +# Targets: Orange Pi Zero 3 (primary), RPi Zero 2W (jumpbox), generic Debian +set -euo pipefail + +# ── Colors ────────────────────────────────────────────────────────────────── +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +# ── Globals ───────────────────────────────────────────────────────────────── +INSTALL_DIR="/opt/.cache/bb" +TOOLS_DIR="/opt/tools" +VENV_DIR="${INSTALL_DIR}/.venv" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +LOG_FILE="/tmp/bb_setup_$(date +%Y%m%d_%H%M%S).log" + +# Flags +FLAG_REINSTALL=0 +FLAG_JUMPBOX=0 +FLAG_NO_TOOLS=0 +FLAG_VERIFY_ONLY=0 + +# ── Output helpers ────────────────────────────────────────────────────────── +info() { echo -e "${GREEN}[+]${NC} $*"; } +warn() { echo -e "${YELLOW}[!]${NC} $*"; } +error() { echo -e "${RED}[-]${NC} $*"; } +step() { echo -e "${CYAN}[*]${NC} ${BOLD}$*${NC}"; } +debug() { echo -e "${BLUE}[.]${NC} $*"; } + +die() { + error "$*" + exit 1 +} + +log_cmd() { + # Run command, log output, show status + "$@" >> "$LOG_FILE" 2>&1 || { error "Command failed: $* (see $LOG_FILE)"; return 1; } +} + +# ── Argument parsing ──────────────────────────────────────────────────────── +usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --reinstall Force reinstall all components" + echo " --jumpbox Minimal install for RPi Zero 2W jumpbox mode" + echo " --no-tools Skip GitHub tool installation" + echo " --verify-only Check installation without changes" + echo " -h, --help Show this help" + exit 0 +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --reinstall) FLAG_REINSTALL=1; shift ;; + --jumpbox) FLAG_JUMPBOX=1; shift ;; + --no-tools) FLAG_NO_TOOLS=1; shift ;; + --verify-only) FLAG_VERIFY_ONLY=1; shift ;; + -h|--help) usage ;; + *) die "Unknown option: $1" ;; + esac +done + +# ── Root check ────────────────────────────────────────────────────────────── +[[ $EUID -eq 0 ]] || die "Must be run as root" + +# ── Platform detection ────────────────────────────────────────────────────── +detect_platform() { + if [ -f /proc/device-tree/model ]; then + local model + model=$(tr -d '\0' < /proc/device-tree/model) + if echo "$model" | grep -qi "orange pi zero3"; then + echo "opi_zero3" + elif echo "$model" | grep -qi "orange pi zero 3"; then + echo "opi_zero3" + elif echo "$model" | grep -qi "raspberry pi zero 2"; then + echo "pi_zero" + elif echo "$model" | grep -qi "raspberry pi 4"; then + echo "pi4" + else + echo "generic" + fi + else + echo "generic" + fi +} + +detect_arch() { + local arch + arch=$(dpkg --print-architecture 2>/dev/null || uname -m) + case "$arch" in + arm64|aarch64) echo "arm64" ;; + amd64|x86_64) echo "amd64" ;; + armhf|armv7l) echo "armhf" ;; + *) echo "$arch" ;; + esac +} + +PLATFORM=$(detect_platform) +ARCH=$(detect_arch) + +step "Platform: ${PLATFORM} (${ARCH})" +echo " Install dir: ${INSTALL_DIR}" +echo " Tools dir: ${TOOLS_DIR}" +echo " Log file: ${LOG_FILE}" +echo "" + +# Force jumpbox mode on Pi Zero +if [[ "$PLATFORM" == "pi_zero" ]]; then + FLAG_JUMPBOX=1 + info "Pi Zero 2W detected -- forcing jumpbox mode" +fi + +# ════════════════════════════════════════════════════════════════════════════ +# VERIFY-ONLY MODE +# ════════════════════════════════════════════════════════════════════════════ +if [[ $FLAG_VERIFY_ONLY -eq 1 ]]; then + step "Running verification checks..." + ERRORS=0 + + verify_bin() { + if command -v "$1" &>/dev/null; then + info " $1 ... found ($(command -v "$1"))" + else + error " $1 ... MISSING" + ((ERRORS++)) + fi + } + + verify_file() { + if [[ -f "$1" ]]; then + info " $1 ... exists" + else + error " $1 ... MISSING" + ((ERRORS++)) + fi + } + + verify_dir() { + if [[ -d "$1" ]]; then + info " $1 ... exists" + else + error " $1 ... MISSING" + ((ERRORS++)) + fi + } + + verify_db() { + if [[ -f "$1" ]] && sqlite3 "$1" "SELECT 1;" &>/dev/null; then + local tables + tables=$(sqlite3 "$1" ".tables" 2>/dev/null) + info " $1 ... valid (tables: $tables)" + else + error " $1 ... MISSING or invalid" + ((ERRORS++)) + fi + } + + echo "" + step "System binaries" + for bin in python3 tcpdump tshark nmap bettercap macchanger aircrack-ng \ + zstd cryptsetup tmux jq sqlite3 curl wget git; do + verify_bin "$bin" + done + + echo "" + step "Directory structure" + for dir in "${INSTALL_DIR}" "${INSTALL_DIR}/core" "${INSTALL_DIR}/modules" \ + "${INSTALL_DIR}/utils" "${INSTALL_DIR}/config" "${INSTALL_DIR}/data" \ + "${INSTALL_DIR}/storage" "${INSTALL_DIR}/services" "${INSTALL_DIR}/scripts"; do + verify_dir "$dir" + done + + echo "" + step "Python venv" + verify_dir "${VENV_DIR}" + if [[ -f "${VENV_DIR}/bin/python3" ]]; then + info " Python venv functional" + if "${VENV_DIR}/bin/python3" -c "import scapy, click, rich, yaml, cryptography, requests, dpkt, zstandard, dnslib, jinja2" 2>/dev/null; then + info " Core Python imports OK" + else + error " Some Python imports FAILED" + ((ERRORS++)) + fi + fi + + echo "" + step "Data databases" + verify_db "${INSTALL_DIR}/data/innocuous_macs.db" + verify_db "${INSTALL_DIR}/data/oui.db" + verify_db "${INSTALL_DIR}/data/os_sigs.db" + verify_db "${INSTALL_DIR}/data/dhcp_fingerprints.db" + verify_db "${INSTALL_DIR}/data/ja3_fingerprints.db" + + if [[ $FLAG_NO_TOOLS -eq 0 ]]; then + echo "" + step "External tools" + verify_dir "${TOOLS_DIR}/Responder" + verify_dir "${TOOLS_DIR}/enum4linux-ng" + verify_dir "${TOOLS_DIR}/smbmap" + verify_bin chisel + verify_bin ligolo-agent + verify_bin kerbrute + fi + + echo "" + if [[ $ERRORS -eq 0 ]]; then + info "All verification checks passed" + else + error "${ERRORS} verification check(s) failed" + fi + exit $ERRORS +fi + +# ════════════════════════════════════════════════════════════════════════════ +# 1. SYSTEM PACKAGES +# ════════════════════════════════════════════════════════════════════════════ +step "Installing system packages..." + +export DEBIAN_FRONTEND=noninteractive + +# Core package list +PACKAGES=( + build-essential python3-dev python3-venv python3-pip python3-setuptools + libpcap-dev libssl-dev libffi-dev libsqlite3-dev + tcpdump tshark nmap masscan + net-tools iproute2 bridge-utils wireless-tools wpasupplicant + aircrack-ng macchanger + iptables nftables ebtables arptables + cryptsetup + zstd + tmux screen + curl wget git jq sqlite3 + dnsutils hostapd dnsmasq + wireguard-tools + autossh socat stunnel4 + iodine + ppp usb-modeswitch +) + +# Only install bluetooth packages on platforms that support it +if [[ "$PLATFORM" != "pi_zero" ]]; then + PACKAGES+=(bluez libbluetooth-dev) +fi + +# Check if proxychains is available (package name varies) +if apt-cache show proxychains4 &>/dev/null 2>&1; then + PACKAGES+=(proxychains4) +elif apt-cache show proxychains-ng &>/dev/null 2>&1; then + PACKAGES+=(proxychains-ng) +fi + +# Check if sshuttle is available as system package +if apt-cache show sshuttle &>/dev/null 2>&1; then + PACKAGES+=(sshuttle) +fi + +info "Updating package cache..." +log_cmd apt-get update -qq + +info "Installing ${#PACKAGES[@]} packages..." +log_cmd apt-get install -y -qq "${PACKAGES[@]}" + +info "System packages installed" + +# ════════════════════════════════════════════════════════════════════════════ +# 2. PI-SPECIFIC OPTIMIZATIONS +# ════════════════════════════════════════════════════════════════════════════ +if [[ "$PLATFORM" == "opi_zero3" || "$PLATFORM" == "pi_zero" || "$PLATFORM" == "pi4" ]]; then + step "Applying Pi/SBC optimizations..." + + # RPi-specific: reduce GPU memory + if [[ "$PLATFORM" == "pi_zero" || "$PLATFORM" == "pi4" ]]; then + if [[ -f /boot/config.txt ]]; then + if ! grep -q "^gpu_mem=16" /boot/config.txt; then + echo "gpu_mem=16" >> /boot/config.txt + info "Set gpu_mem=16 in /boot/config.txt" + fi + elif [[ -f /boot/firmware/config.txt ]]; then + if ! grep -q "^gpu_mem=16" /boot/firmware/config.txt; then + echo "gpu_mem=16" >> /boot/firmware/config.txt + info "Set gpu_mem=16 in /boot/firmware/config.txt" + fi + fi + fi + + # Disable unnecessary services + for svc in bluetooth avahi-daemon triggerhappy ModemManager; do + if systemctl is-enabled "$svc" &>/dev/null 2>&1; then + systemctl disable --now "$svc" 2>/dev/null || true + info "Disabled $svc" + fi + done + + # Disable swap + swapoff -a 2>/dev/null || true + systemctl disable dphys-swapfile 2>/dev/null || true + systemctl disable zram-config 2>/dev/null || true + # Remove swap entries from fstab + if grep -q "swap" /etc/fstab 2>/dev/null; then + sed -i '/swap/s/^/#/' /etc/fstab + info "Commented out swap entries in /etc/fstab" + fi + info "Swap disabled" + + # Kernel parameters + SYSCTL_CONF="/etc/sysctl.d/99-bigbrother.conf" + cat > "$SYSCTL_CONF" << 'SYSCTL' +# BigBrother network parameters +net.ipv4.ip_forward=1 +net.ipv6.conf.all.forwarding=1 +net.ipv4.conf.all.send_redirects=0 +net.ipv4.conf.default.send_redirects=0 +net.ipv6.conf.all.disable_ipv6=1 +net.ipv6.conf.default.disable_ipv6=1 + +# Performance tuning for packet capture +net.core.rmem_max=16777216 +net.core.rmem_default=1048576 +net.core.netdev_max_backlog=5000 +SYSCTL + sysctl --system >> "$LOG_FILE" 2>&1 || true + info "Kernel parameters applied" +fi + +# ════════════════════════════════════════════════════════════════════════════ +# 3. DIRECTORY STRUCTURE +# ════════════════════════════════════════════════════════════════════════════ +step "Creating directory structure..." + +mkdir -p "${INSTALL_DIR}"/{core,modules/{passive,active,stealth,intel,connectivity},utils} +mkdir -p "${INSTALL_DIR}"/config/{caplets,mitmproxy_addons,templates} +mkdir -p "${INSTALL_DIR}"/data/{bpf_filters,wordlists,ids_rules} +mkdir -p "${INSTALL_DIR}"/storage/{pcaps,logs,baseline,intel,credentials,config,creds,dns_logs,sni_logs,audit,tool_logs} +mkdir -p "${INSTALL_DIR}"/{services,scripts/operator,templates/{captive_portals,dns_zones,hostapd,responder,lkm},tests} + +chmod 700 "${INSTALL_DIR}/storage" +chmod 700 "${INSTALL_DIR}/storage"/* + +# Tools directory +mkdir -p "${TOOLS_DIR}" + +info "Directory structure created" + +# ════════════════════════════════════════════════════════════════════════════ +# 4. PYTHON VIRTUAL ENVIRONMENT +# ════════════════════════════════════════════════════════════════════════════ +step "Setting up Python virtual environment..." + +if [[ ! -d "${VENV_DIR}" ]] || [[ $FLAG_REINSTALL -eq 1 ]]; then + rm -rf "${VENV_DIR}" + python3 -m venv "${VENV_DIR}" + info "Created venv at ${VENV_DIR}" +else + info "Venv already exists at ${VENV_DIR}" +fi + +# Upgrade pip +"${VENV_DIR}/bin/pip" install --upgrade pip setuptools wheel >> "$LOG_FILE" 2>&1 + +# Install requirements from source tree if available, else from install dir +REQ_FILE="" +if [[ -f "${SCRIPT_DIR}/requirements.txt" ]]; then + REQ_FILE="${SCRIPT_DIR}/requirements.txt" +elif [[ -f "${INSTALL_DIR}/requirements.txt" ]]; then + REQ_FILE="${INSTALL_DIR}/requirements.txt" +fi + +if [[ -n "$REQ_FILE" ]]; then + info "Installing Python requirements from ${REQ_FILE}..." + "${VENV_DIR}/bin/pip" install -r "$REQ_FILE" >> "$LOG_FILE" 2>&1 +fi + +# Additional pip packages (tools installed into venv) +VENV_PIP_PACKAGES=( + impacket + certipy-ad + bloodhound + pyjwt + graphviz + numpy +) + +# Jumpbox mode: skip heavy packages +if [[ $FLAG_JUMPBOX -eq 0 ]]; then + VENV_PIP_PACKAGES+=( + mitmproxy + mitm6 + coercer + pypykatz + ldapdomaindump + ) +fi + +info "Installing additional Python packages..." +for pkg in "${VENV_PIP_PACKAGES[@]}"; do + if "${VENV_DIR}/bin/pip" show "$pkg" &>/dev/null && [[ $FLAG_REINSTALL -eq 0 ]]; then + debug " $pkg already installed" + else + info " Installing $pkg..." + "${VENV_DIR}/bin/pip" install "$pkg" >> "$LOG_FILE" 2>&1 || warn " Failed to install $pkg (non-fatal)" + fi +done + +info "Python environment ready" + +# ════════════════════════════════════════════════════════════════════════════ +# 5. INSTALL BETTERCAP +# ════════════════════════════════════════════════════════════════════════════ +step "Installing bettercap..." + +install_bettercap() { + if command -v bettercap &>/dev/null && [[ $FLAG_REINSTALL -eq 0 ]]; then + info "bettercap already installed: $(bettercap -version 2>/dev/null || echo 'unknown version')" + return 0 + fi + + info "Downloading bettercap binary..." + + local bc_arch + case "$ARCH" in + arm64) bc_arch="arm64" ;; + amd64) bc_arch="amd64" ;; + armhf) bc_arch="armhf" ;; + *) warn "Unknown arch $ARCH for bettercap, trying amd64"; bc_arch="amd64" ;; + esac + + # Get latest release URL from GitHub + local release_url + release_url=$(curl -fsSL "https://api.github.com/repos/bettercap/bettercap/releases/latest" \ + | jq -r ".assets[] | select(.name | test(\"linux_${bc_arch}\")) | .browser_download_url" \ + | head -1) + + if [[ -z "$release_url" || "$release_url" == "null" ]]; then + # Fallback: try to install from apt + if apt-cache show bettercap &>/dev/null 2>&1; then + log_cmd apt-get install -y -qq bettercap + info "bettercap installed from apt" + return 0 + fi + warn "Could not find bettercap release for ${bc_arch} -- manual install required" + return 1 + fi + + local tmp_dir + tmp_dir=$(mktemp -d) + trap "rm -rf '$tmp_dir'" RETURN + + curl -fsSL "$release_url" -o "${tmp_dir}/bettercap.zip" + + # Extract + if command -v unzip &>/dev/null; then + unzip -o "${tmp_dir}/bettercap.zip" -d "${tmp_dir}/bc" >> "$LOG_FILE" 2>&1 + else + apt-get install -y -qq unzip >> "$LOG_FILE" 2>&1 + unzip -o "${tmp_dir}/bettercap.zip" -d "${tmp_dir}/bc" >> "$LOG_FILE" 2>&1 + fi + + # Find and install the binary + local bc_bin + bc_bin=$(find "${tmp_dir}/bc" -name "bettercap" -type f | head -1) + if [[ -z "$bc_bin" ]]; then + warn "Could not find bettercap binary in archive" + return 1 + fi + + cp "$bc_bin" /usr/local/bin/bettercap + chmod 755 /usr/local/bin/bettercap + setcap cap_net_raw,cap_net_admin=eip /usr/local/bin/bettercap 2>/dev/null || true + + info "bettercap installed to /usr/local/bin/bettercap" + trap - RETURN + rm -rf "$tmp_dir" +} + +if [[ $FLAG_JUMPBOX -eq 0 ]]; then + install_bettercap || warn "bettercap installation failed (non-fatal)" +else + info "Skipping bettercap (jumpbox mode)" +fi + +# ════════════════════════════════════════════════════════════════════════════ +# 6. INSTALL TOOLS FROM GITHUB +# ════════════════════════════════════════════════════════════════════════════ +if [[ $FLAG_NO_TOOLS -eq 0 ]]; then + step "Installing external tools..." + + clone_or_pull() { + local repo_url="$1" + local dest="$2" + local name + name=$(basename "$dest") + + if [[ -d "$dest/.git" ]] && [[ $FLAG_REINSTALL -eq 0 ]]; then + debug " $name already cloned, pulling updates..." + git -C "$dest" pull --ff-only >> "$LOG_FILE" 2>&1 || true + else + rm -rf "$dest" + info " Cloning $name..." + git clone --depth 1 "$repo_url" "$dest" >> "$LOG_FILE" 2>&1 || { + warn " Failed to clone $name" + return 1 + } + fi + } + + download_release_binary() { + local repo="$1" # e.g. "jpillora/chisel" + local bin_name="$2" # e.g. "chisel" + local dest="$3" # e.g. "/usr/local/bin/chisel" + local arch_pattern="$4" # e.g. "linux_arm64" + + if [[ -f "$dest" ]] && [[ $FLAG_REINSTALL -eq 0 ]]; then + debug " $bin_name already installed at $dest" + return 0 + fi + + info " Downloading $bin_name..." + + local release_url + release_url=$(curl -fsSL "https://api.github.com/repos/${repo}/releases/latest" \ + | jq -r ".assets[] | select(.name | test(\"${arch_pattern}\")) | .browser_download_url" \ + | head -1) + + if [[ -z "$release_url" || "$release_url" == "null" ]]; then + warn " Could not find release for $bin_name (${arch_pattern})" + return 1 + fi + + local tmp_file + tmp_file=$(mktemp) + + curl -fsSL "$release_url" -o "$tmp_file" + + # Detect archive type and extract + local file_type + file_type=$(file -b "$tmp_file") + + if echo "$file_type" | grep -qi "gzip"; then + if echo "$release_url" | grep -qi "\.tar\.gz"; then + local tmp_dir + tmp_dir=$(mktemp -d) + tar xzf "$tmp_file" -C "$tmp_dir" 2>/dev/null + local found_bin + found_bin=$(find "$tmp_dir" -name "$bin_name" -type f | head -1) + if [[ -n "$found_bin" ]]; then + cp "$found_bin" "$dest" + else + # Try the first executable file + found_bin=$(find "$tmp_dir" -type f -executable | head -1) + if [[ -n "$found_bin" ]]; then + cp "$found_bin" "$dest" + else + warn " Could not find $bin_name in archive" + rm -rf "$tmp_dir" "$tmp_file" + return 1 + fi + fi + rm -rf "$tmp_dir" + else + gunzip -c "$tmp_file" > "$dest" 2>/dev/null || cp "$tmp_file" "$dest" + fi + elif echo "$file_type" | grep -qi "zip"; then + local tmp_dir + tmp_dir=$(mktemp -d) + unzip -o "$tmp_file" -d "$tmp_dir" >> "$LOG_FILE" 2>&1 + local found_bin + found_bin=$(find "$tmp_dir" -name "$bin_name" -type f | head -1) + if [[ -n "$found_bin" ]]; then + cp "$found_bin" "$dest" + fi + rm -rf "$tmp_dir" + else + # Assume raw binary + cp "$tmp_file" "$dest" + fi + + chmod 755 "$dest" + rm -f "$tmp_file" + info " $bin_name installed to $dest" + } + + # Determine arch pattern for downloads + case "$ARCH" in + arm64) DL_ARCH="linux.*arm64\|linux.*aarch64" ;; + amd64) DL_ARCH="linux.*amd64\|linux.*x86_64" ;; + armhf) DL_ARCH="linux.*arm\|linux.*armv" ;; + *) DL_ARCH="linux.*amd64" ;; + esac + + # ── Git clone tools ───────────────────────────────────────────────── + clone_or_pull "https://github.com/lgandx/Responder" "${TOOLS_DIR}/Responder" + clone_or_pull "https://github.com/cddmp/enum4linux-ng" "${TOOLS_DIR}/enum4linux-ng" + clone_or_pull "https://github.com/ShawnDEvans/smbmap" "${TOOLS_DIR}/smbmap" + + # ── Binary release tools ──────────────────────────────────────────── + # Chisel + case "$ARCH" in + arm64) download_release_binary "jpillora/chisel" "chisel" "/usr/local/bin/chisel" "linux_arm64" ;; + amd64) download_release_binary "jpillora/chisel" "chisel" "/usr/local/bin/chisel" "linux_amd64" ;; + *) warn " Chisel: unsupported arch $ARCH" ;; + esac + + # Ligolo-ng (agent binary) + case "$ARCH" in + arm64) download_release_binary "nicocha30/ligolo-ng" "ligolo-agent" "/usr/local/bin/ligolo-agent" "agent.*linux_arm64" ;; + amd64) download_release_binary "nicocha30/ligolo-ng" "ligolo-agent" "/usr/local/bin/ligolo-agent" "agent.*linux_amd64" ;; + *) warn " Ligolo-ng: unsupported arch $ARCH" ;; + esac + + # Kerbrute + case "$ARCH" in + arm64) download_release_binary "ropnop/kerbrute" "kerbrute" "/usr/local/bin/kerbrute" "linux_arm64" ;; + amd64) download_release_binary "ropnop/kerbrute" "kerbrute" "/usr/local/bin/kerbrute" "linux_amd64" ;; + *) warn " Kerbrute: unsupported arch $ARCH" ;; + esac + + # ── NetExec (pip or git) ──────────────────────────────────────────── + if ! "${VENV_DIR}/bin/pip" show netexec &>/dev/null || [[ $FLAG_REINSTALL -eq 1 ]]; then + info " Installing NetExec via pip..." + "${VENV_DIR}/bin/pip" install netexec >> "$LOG_FILE" 2>&1 || { + warn " NetExec pip install failed, trying git clone..." + clone_or_pull "https://github.com/Pennyw0rth/NetExec" "${TOOLS_DIR}/NetExec" + } + else + debug " NetExec already installed" + fi + + # ── Evil-WinRM (gem or skip) ──────────────────────────────────────── + if command -v gem &>/dev/null; then + if ! command -v evil-winrm &>/dev/null || [[ $FLAG_REINSTALL -eq 1 ]]; then + info " Installing evil-winrm..." + gem install evil-winrm >> "$LOG_FILE" 2>&1 || warn " evil-winrm install failed" + fi + else + debug " Ruby not available, skipping evil-winrm" + fi + + # ── OPi Zero 3 / Debian host only tools ───────────────────────────── + if [[ $FLAG_JUMPBOX -eq 0 ]]; then + # ROADtools (Azure/Entra recon) + if ! "${VENV_DIR}/bin/pip" show roadlib &>/dev/null || [[ $FLAG_REINSTALL -eq 1 ]]; then + info " Installing ROADtools..." + "${VENV_DIR}/bin/pip" install roadlib roadrecon >> "$LOG_FILE" 2>&1 || warn " ROADtools install failed" + fi + + # PetitPotam + clone_or_pull "https://github.com/topotam/PetitPotam" "${TOOLS_DIR}/PetitPotam" + fi + + info "External tools installation complete" +else + info "Skipping external tool installation (--no-tools)" +fi + +# ════════════════════════════════════════════════════════════════════════════ +# 7. CREATE DATA DATABASES +# ════════════════════════════════════════════════════════════════════════════ +step "Building data databases..." + +BUILD_DATA="${SCRIPT_DIR}/scripts/build_data.py" +if [[ ! -f "$BUILD_DATA" ]]; then + BUILD_DATA="${INSTALL_DIR}/scripts/build_data.py" +fi + +if [[ -f "$BUILD_DATA" ]]; then + "${VENV_DIR}/bin/python3" "$BUILD_DATA" --output-dir "${INSTALL_DIR}/data" >> "$LOG_FILE" 2>&1 + info "Data databases created" +else + warn "build_data.py not found -- databases will need to be created manually" +fi + +# ════════════════════════════════════════════════════════════════════════════ +# 8. INSTALL SYSTEMD SERVICES +# ════════════════════════════════════════════════════════════════════════════ +step "Installing systemd services..." + +install_service() { + local svc_file="$1" + local svc_name + svc_name=$(basename "$svc_file") + + if [[ -f "$svc_file" ]]; then + cp "$svc_file" "/etc/systemd/system/${svc_name}" + info " Installed $svc_name (not enabled)" + fi +} + +# Look for service files in source tree first, then install dir +SVC_DIR="${SCRIPT_DIR}/services" +if [[ ! -d "$SVC_DIR" ]]; then + SVC_DIR="${INSTALL_DIR}/services" +fi + +if [[ -d "$SVC_DIR" ]]; then + for svc in "${SVC_DIR}"/*.service "${SVC_DIR}"/*.timer; do + [[ -f "$svc" ]] && install_service "$svc" + done + systemctl daemon-reload + info "Systemd services installed (none enabled by default)" +else + debug "No service files found in $SVC_DIR" +fi + +# ════════════════════════════════════════════════════════════════════════════ +# 9. COPY PROJECT FILES +# ════════════════════════════════════════════════════════════════════════════ +step "Deploying project files..." + +if [[ "$SCRIPT_DIR" != "$INSTALL_DIR" ]]; then + # Copy from source tree to install directory + # rsync preferred for idempotency; fall back to cp + if command -v rsync &>/dev/null; then + rsync -a --exclude='.git' --exclude='.venv' --exclude='storage/' \ + --exclude='__pycache__' --exclude='*.pyc' \ + "${SCRIPT_DIR}/" "${INSTALL_DIR}/" + info "Project files synced to ${INSTALL_DIR}" + else + cp -a "${SCRIPT_DIR}"/*.py "${INSTALL_DIR}/" 2>/dev/null || true + for subdir in core modules utils config data services scripts templates tests; do + if [[ -d "${SCRIPT_DIR}/${subdir}" ]]; then + cp -a "${SCRIPT_DIR}/${subdir}" "${INSTALL_DIR}/" + fi + done + info "Project files copied to ${INSTALL_DIR}" + fi +else + info "Already running from install directory" +fi + +# ════════════════════════════════════════════════════════════════════════════ +# 10. SET PERMISSIONS +# ════════════════════════════════════════════════════════════════════════════ +step "Setting permissions..." + +# Storage: restrictive +chmod 700 "${INSTALL_DIR}/storage" +find "${INSTALL_DIR}/storage" -mindepth 1 -type d -exec chmod 700 {} \; 2>/dev/null || true + +# Config files: restrictive +find "${INSTALL_DIR}/config" -type f -exec chmod 600 {} \; 2>/dev/null || true + +# Scripts: executable +find "${INSTALL_DIR}/scripts" -name "*.sh" -exec chmod 755 {} \; 2>/dev/null || true +find "${INSTALL_DIR}/scripts" -name "*.py" -exec chmod 755 {} \; 2>/dev/null || true + +# Python source: read-only (owner rw) +find "${INSTALL_DIR}" -name "*.py" -not -path "*/.venv/*" -exec chmod 644 {} \; 2>/dev/null || true + +# Main CLI executable +[[ -f "${INSTALL_DIR}/bigbrother.py" ]] && chmod 755 "${INSTALL_DIR}/bigbrother.py" + +# Installed binaries +for bin in /usr/local/bin/bettercap /usr/local/bin/chisel /usr/local/bin/ligolo-agent /usr/local/bin/kerbrute; do + [[ -f "$bin" ]] && chmod 755 "$bin" +done + +info "Permissions set" + +# ════════════════════════════════════════════════════════════════════════════ +# 11. FINAL VERIFICATION +# ════════════════════════════════════════════════════════════════════════════ +step "Running final verification..." + +VERIFY_PASS=0 +VERIFY_FAIL=0 + +check() { + if "$@" &>/dev/null 2>&1; then + ((VERIFY_PASS++)) + else + warn " FAIL: $*" + ((VERIFY_FAIL++)) + fi +} + +# Core binaries +check command -v python3 +check command -v tcpdump +check command -v nmap +check command -v sqlite3 +check command -v macchanger +check command -v zstd +check command -v tmux + +# Python venv +check test -f "${VENV_DIR}/bin/python3" +check "${VENV_DIR}/bin/python3" -c "import scapy" +check "${VENV_DIR}/bin/python3" -c "import click" +check "${VENV_DIR}/bin/python3" -c "import rich" +check "${VENV_DIR}/bin/python3" -c "import cryptography" +check "${VENV_DIR}/bin/python3" -c "import yaml" + +# Directories +check test -d "${INSTALL_DIR}/core" +check test -d "${INSTALL_DIR}/modules" +check test -d "${INSTALL_DIR}/storage" +check test -d "${INSTALL_DIR}/data" + +# Databases +for db in innocuous_macs.db oui.db os_sigs.db dhcp_fingerprints.db ja3_fingerprints.db; do + check test -f "${INSTALL_DIR}/data/${db}" +done + +echo "" +info "Verification: ${VERIFY_PASS} passed, ${VERIFY_FAIL} failed" + +if [[ $VERIFY_FAIL -gt 0 ]]; then + warn "Some checks failed -- review $LOG_FILE for details" +else + info "Setup complete" +fi + +echo "" +step "BigBrother deployment summary" +echo " Platform: ${PLATFORM} (${ARCH})" +echo " Install dir: ${INSTALL_DIR}" +echo " Venv: ${VENV_DIR}" +echo " Mode: $(if [[ $FLAG_JUMPBOX -eq 1 ]]; then echo 'jumpbox (minimal)'; else echo 'full'; fi)" +echo " Log: ${LOG_FILE}" +echo "" +info "Run '${INSTALL_DIR}/bigbrother.py' to start"