Add Phase 1 utility modules: crypto, networking, logging, stealth, resource, permissions, config_loader, bettercap_api

9 files in utils/ providing the shared infrastructure layer:
- crypto: AES-256-GCM file encryption, argon2id/PBKDF2 key derivation, HKDF network unlock, LUKS container management
- networking: interface detection, MAC/IP helpers, BPF compilation via libpcap ctypes, gratuitous ARP, VLAN creation
- logging: encrypted log writer (BBLogger) with rotation, per-module files, stdout suppression for stealth
- stealth: process rename via prctl, cmdline spoofing, sysctl helpers, timestomping, core dump disable
- resource: hardware tier detection (OPi Zero 3 / Pi Zero / generic) via /proc/device-tree and cpuinfo, resource monitoring
- permissions: root/capability checks via capget ctypes, privilege dropping, directory permission enforcement
- config_loader: YAML merge hierarchy (hardware_tiers -> bigbrother -> modules -> stealth -> CLI), tier auto-detection, SIGHUP reload
- bettercap_api: REST client with per-session random credentials, session/events/command methods
This commit is contained in:
n0mad1k
2026-03-18 08:11:25 -04:00
parent 63e317176d
commit 62a1010ab4
35 changed files with 2145 additions and 0 deletions
+250
View File
@@ -0,0 +1,250 @@
#!/usr/bin/env python3
"""Network interface helpers, BPF compilation, gratuitous ARP. No scapy dependency."""
import ctypes
import ctypes.util
import fcntl
import os
import socket
import struct
import subprocess
from pathlib import Path
from typing import List, Optional, Tuple
SIOCGIFADDR = 0x8915
SIOCGIFHWADDR = 0x8927
SIOCSIFHWADDR = 0x8924
SIOCGIFFLAGS = 0x8913
SIOCSIFFLAGS = 0x8914
IFF_UP = 0x1
IFF_RUNNING = 0x40
ARPHRD_ETHER = 1
_libpcap = None
def _get_libpcap():
global _libpcap
if _libpcap is None:
lib = ctypes.util.find_library("pcap")
if not lib:
raise OSError("libpcap not found")
_libpcap = ctypes.CDLL(lib)
return _libpcap
def _ifreq(ifname: str) -> bytes:
return struct.pack("256s", ifname.encode("utf-8")[:15])
def get_all_interfaces() -> List[str]:
interfaces = []
net_path = Path("/sys/class/net")
if net_path.exists():
for entry in net_path.iterdir():
name = entry.name
if name != "lo":
interfaces.append(name)
return sorted(interfaces)
def get_primary_interface() -> Optional[str]:
try:
with open("/proc/net/route", "r") as f:
for line in f.readlines()[1:]:
fields = line.strip().split()
if fields[1] == "00000000":
return fields[0]
except (IOError, IndexError):
pass
interfaces = get_all_interfaces()
for iface in interfaces:
if iface.startswith(("eth", "en")):
return iface
return interfaces[0] if interfaces else None
def get_bridge_candidates() -> List[str]:
candidates = []
for iface in get_all_interfaces():
if iface.startswith(("eth", "en", "usb")):
candidates.append(iface)
return candidates
def get_wifi_interfaces() -> List[str]:
interfaces = []
for iface in get_all_interfaces():
phy_path = Path(f"/sys/class/net/{iface}/phy80211")
wireless_path = Path(f"/sys/class/net/{iface}/wireless")
if phy_path.exists() or wireless_path.exists() or iface.startswith("wlan"):
interfaces.append(iface)
return interfaces
def get_mac(interface: str) -> str:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
info = fcntl.ioctl(sock.fileno(), SIOCGIFHWADDR, _ifreq(interface))
mac_bytes = info[18:24]
return ":".join(f"{b:02x}" for b in mac_bytes)
finally:
sock.close()
def set_mac(interface: str, mac: str) -> None:
was_up = is_interface_up(interface)
if was_up:
interface_down(interface)
mac_bytes = bytes(int(b, 16) for b in mac.split(":"))
ifreq = struct.pack("16sH6s8s", interface.encode()[:15], ARPHRD_ETHER, mac_bytes, b"\x00" * 8)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
fcntl.ioctl(sock.fileno(), SIOCSIFHWADDR, ifreq)
finally:
sock.close()
if was_up:
interface_up(interface)
def get_ip(interface: str) -> Optional[str]:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
info = fcntl.ioctl(sock.fileno(), SIOCGIFADDR, _ifreq(interface))
return socket.inet_ntoa(info[20:24])
except OSError:
return None
finally:
sock.close()
def is_interface_up(interface: str) -> bool:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
result = fcntl.ioctl(sock.fileno(), SIOCGIFFLAGS, _ifreq(interface))
flags = struct.unpack("16sH", result[:18])[1]
return bool(flags & IFF_UP)
except OSError:
return False
finally:
sock.close()
def interface_up(interface: str) -> None:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
result = fcntl.ioctl(sock.fileno(), SIOCGIFFLAGS, _ifreq(interface))
flags = struct.unpack("16sH", result[:18])[1]
flags |= IFF_UP | IFF_RUNNING
ifreq = struct.pack("16sH", interface.encode()[:15], flags)
fcntl.ioctl(sock.fileno(), SIOCSIFFLAGS, ifreq)
finally:
sock.close()
def interface_down(interface: str) -> None:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
result = fcntl.ioctl(sock.fileno(), SIOCGIFFLAGS, _ifreq(interface))
flags = struct.unpack("16sH", result[:18])[1]
flags &= ~IFF_UP
ifreq = struct.pack("16sH", interface.encode()[:15], flags)
fcntl.ioctl(sock.fileno(), SIOCSIFFLAGS, ifreq)
finally:
sock.close()
def create_vlan_interface(parent: str, vlan_id: int) -> str:
vlan_iface = f"{parent}.{vlan_id}"
subprocess.run(
["ip", "link", "add", "link", parent, "name", vlan_iface,
"type", "vlan", "id", str(vlan_id)],
check=True, capture_output=True,
)
interface_up(vlan_iface)
return vlan_iface
def compile_bpf(expression: str, snaplen: int = 65535, linktype: int = 1) -> bytes:
pcap = _get_libpcap()
class bpf_insn(ctypes.Structure):
_fields_ = [
("code", ctypes.c_ushort),
("jt", ctypes.c_ubyte),
("jf", ctypes.c_ubyte),
("k", ctypes.c_uint32),
]
class bpf_program(ctypes.Structure):
_fields_ = [
("bf_len", ctypes.c_uint),
("bf_insns", ctypes.POINTER(bpf_insn)),
]
handle = pcap.pcap_open_dead(ctypes.c_int(linktype), ctypes.c_int(snaplen))
if not handle:
raise RuntimeError("pcap_open_dead failed")
prog = bpf_program()
expr_bytes = expression.encode("utf-8")
ret = pcap.pcap_compile(
handle,
ctypes.byref(prog),
ctypes.c_char_p(expr_bytes),
ctypes.c_int(1),
ctypes.c_uint32(0xFFFFFFFF),
)
if ret != 0:
err = pcap.pcap_geterr(handle)
pcap.pcap_close(handle)
if err:
raise ValueError(f"BPF compile failed: {ctypes.string_at(err).decode()}")
raise ValueError("BPF compile failed")
insn_size = ctypes.sizeof(bpf_insn)
result = bytes(ctypes.string_at(prog.bf_insns, prog.bf_len * insn_size))
pcap.pcap_freecode(ctypes.byref(prog))
pcap.pcap_close(handle)
return result
def send_gratuitous_arp(interface: str, ip: str, mac: str) -> None:
ETH_P_ARP = 0x0806
sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ARP))
try:
sock.bind((interface, 0))
src_mac = bytes(int(b, 16) for b in mac.split(":"))
dst_mac = b"\xff" * 6
src_ip = socket.inet_aton(ip)
# Ethernet header
frame = dst_mac + src_mac + struct.pack("!H", ETH_P_ARP)
# ARP: hardware=Ethernet, proto=IPv4, hlen=6, plen=4, op=reply(2)
frame += struct.pack("!HHBBH", 1, 0x0800, 6, 4, 2)
# sender MAC + IP
frame += src_mac + src_ip
# target MAC + IP (broadcast)
frame += dst_mac + src_ip
for _ in range(3):
sock.send(frame)
finally:
sock.close()
def mac_to_bytes(mac: str) -> bytes:
return bytes(int(b, 16) for b in mac.split(":"))
def bytes_to_mac(data: bytes) -> str:
return ":".join(f"{b:02x}" for b in data)
def ip_in_subnet(ip: str, cidr: str) -> bool:
network, prefix_len = cidr.split("/")
prefix_len = int(prefix_len)
ip_int = struct.unpack("!I", socket.inet_aton(ip))[0]
net_int = struct.unpack("!I", socket.inet_aton(network))[0]
mask = (0xFFFFFFFF << (32 - prefix_len)) & 0xFFFFFFFF
return (ip_int & mask) == (net_int & mask)