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"