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
This commit is contained in:
n0mad1k
2026-03-18 08:13:55 -04:00
parent 0a05f009e8
commit f90a686626
4 changed files with 1447 additions and 0 deletions
+458
View File
@@ -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()
+54
View File
@@ -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
+108
View File
@@ -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"
Executable
+827
View File
@@ -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"