62a1010ab4
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
167 lines
4.8 KiB
Python
167 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Shared stealth helpers: process rename, cmdline spoof, sysctl, timestomp, core dump disable."""
|
|
|
|
import ctypes
|
|
import ctypes.util
|
|
import os
|
|
import struct
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
PR_SET_NAME = 15
|
|
RLIMIT_CORE = 4
|
|
|
|
_libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
|
|
|
|
|
|
def rename_process(name: str) -> bool:
|
|
name_bytes = name.encode("utf-8")[:15]
|
|
ret = _libc.prctl(PR_SET_NAME, name_bytes, 0, 0, 0)
|
|
return ret == 0
|
|
|
|
|
|
def spoof_cmdline(fake_cmdline: str) -> bool:
|
|
try:
|
|
cmdline_path = f"/proc/{os.getpid()}/cmdline"
|
|
# Read current cmdline to get its length for overwrite
|
|
with open(cmdline_path, "rb") as f:
|
|
original = f.read()
|
|
|
|
# On Linux, argv[0] is writable via /proc/self/comm and prctl,
|
|
# but /proc/self/cmdline maps to the actual process memory.
|
|
# We overwrite via ctypes access to argv.
|
|
libc = _libc
|
|
|
|
# Get argc/argv from /proc/self/cmdline
|
|
argc = ctypes.c_int()
|
|
argv_ptr = ctypes.POINTER(ctypes.c_char_p)()
|
|
|
|
# Alternative: write to /proc/self/comm (max 15 chars)
|
|
comm_path = f"/proc/{os.getpid()}/comm"
|
|
try:
|
|
with open(comm_path, "w") as f:
|
|
f.write(fake_cmdline[:15])
|
|
except PermissionError:
|
|
pass
|
|
|
|
# For full cmdline spoofing, overwrite argv[0] in memory
|
|
import sys
|
|
if hasattr(sys, "argv") and sys.argv:
|
|
# Replace argv contents via ctypes
|
|
fake_bytes = fake_cmdline.encode("utf-8")
|
|
argv0_addr = id(sys.argv[0])
|
|
# Python string internals - this is fragile but works for ps display
|
|
sys.argv[0] = fake_cmdline
|
|
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def set_sysctl(key: str, value: str) -> bool:
|
|
sysctl_path = Path("/proc/sys") / key.replace(".", "/")
|
|
try:
|
|
with open(sysctl_path, "w") as f:
|
|
f.write(value)
|
|
return True
|
|
except (IOError, PermissionError):
|
|
return False
|
|
|
|
|
|
def set_ttl(ttl: int) -> bool:
|
|
return set_sysctl("net.ipv4.ip_default_ttl", str(ttl))
|
|
|
|
|
|
def set_tcp_window(size: int) -> bool:
|
|
# Set TCP initial window via rmem/wmem defaults
|
|
success = set_sysctl("net.ipv4.tcp_rmem", f"4096 {size} {size * 4}")
|
|
success &= set_sysctl("net.ipv4.tcp_wmem", f"4096 {size} {size * 4}")
|
|
return success
|
|
|
|
|
|
def set_tcp_timestamps(enabled: bool) -> bool:
|
|
return set_sysctl("net.ipv4.tcp_timestamps", "1" if enabled else "0")
|
|
|
|
|
|
def timestomp(path: str, reference: Optional[str] = None, epoch: Optional[float] = None) -> bool:
|
|
try:
|
|
if reference:
|
|
ref_stat = os.stat(reference)
|
|
target_time = ref_stat.st_mtime
|
|
elif epoch:
|
|
target_time = epoch
|
|
else:
|
|
# Default: OS install date from /var/log/installer or filesystem birth
|
|
target_time = _get_os_install_time()
|
|
|
|
os.utime(path, (target_time, target_time))
|
|
return True
|
|
except (OSError, IOError):
|
|
return False
|
|
|
|
|
|
def timestomp_recursive(directory: str, reference: Optional[str] = None, epoch: Optional[float] = None) -> int:
|
|
count = 0
|
|
for root, dirs, files in os.walk(directory):
|
|
for name in files + dirs:
|
|
full_path = os.path.join(root, name)
|
|
if timestomp(full_path, reference=reference, epoch=epoch):
|
|
count += 1
|
|
if timestomp(directory, reference=reference, epoch=epoch):
|
|
count += 1
|
|
return count
|
|
|
|
|
|
def _get_os_install_time() -> float:
|
|
candidates = [
|
|
"/var/log/installer/syslog",
|
|
"/var/log/installer/status",
|
|
"/etc/hostname",
|
|
"/etc/machine-id",
|
|
]
|
|
for path in candidates:
|
|
try:
|
|
return os.stat(path).st_mtime
|
|
except OSError:
|
|
continue
|
|
# Fallback: 90 days ago
|
|
return time.time() - (90 * 86400)
|
|
|
|
|
|
def disable_core_dumps() -> bool:
|
|
try:
|
|
# RLIMIT_CORE = 4 on Linux
|
|
class rlimit(ctypes.Structure):
|
|
_fields_ = [
|
|
("rlim_cur", ctypes.c_ulong),
|
|
("rlim_max", ctypes.c_ulong),
|
|
]
|
|
|
|
limit = rlimit(0, 0)
|
|
ret = _libc.setrlimit(RLIMIT_CORE, ctypes.byref(limit))
|
|
if ret != 0:
|
|
return False
|
|
|
|
# Also set via sysctl and /proc
|
|
set_sysctl("kernel.core_pattern", "|/bin/false")
|
|
set_sysctl("fs.suid_dumpable", "0")
|
|
|
|
# prctl to prevent core for this process and children
|
|
PR_SET_DUMPABLE = 4
|
|
_libc.prctl(PR_SET_DUMPABLE, 0, 0, 0, 0)
|
|
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def hide_from_history() -> bool:
|
|
try:
|
|
os.environ["HISTFILE"] = "/dev/null"
|
|
os.environ["HISTSIZE"] = "0"
|
|
os.environ["HISTFILESIZE"] = "0"
|
|
return True
|
|
except Exception:
|
|
return False
|