Files
bigbrother/modules/stealth/process_disguise.py
T
Cobra ffd384f64b Strip OPSEC tool identity fingerprints
Replace all sensor.* logger namespaces with __name__ (generic module
identifiers instead of discoverable 'sensor.*' prefixes).

Change hardcoded 'bb' API user to 'admin' in config and code defaults.

Change hardcoded relay_user 'bb' to 'operator' — prevents network
profiling from exposing tool identity via SSH config.

Fixes #457, #458, #459
2026-04-08 22:18:35 -04:00

180 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""Process disguise module — rename all SystemMonitor processes to look like
legitimate system services.
Reads process name mappings from stealth.yaml, renames own processes via
prctl(PR_SET_NAME) and /proc/self/comm, and auto-disguises new module
processes as they start.
"""
import logging
import os
import time
import threading
from typing import Dict, Optional
from modules.base import BaseModule
from utils.stealth import rename_process, spoof_cmdline
logger = logging.getLogger(__name__)
class ProcessDisguise(BaseModule):
"""Rename all managed processes to innocuous system service names."""
name = "process_disguise"
module_type = "stealth"
priority = -400
requires_root = False
def __init__(self, bus, state, config, engine=None):
super().__init__(bus, state, config, engine)
self._name_map: Dict[str, str] = {} # original -> disguised
self._disguised_pids: Dict[int, str] = {} # pid -> fake_name
self._lock = threading.Lock()
# ------------------------------------------------------------------
# BaseModule interface
# ------------------------------------------------------------------
def start(self) -> None:
if self._running:
return
# Load process name mappings from stealth.yaml config
stealth_cfg = self.config.get("stealth_yaml", {})
self._name_map = stealth_cfg.get("process_names", {})
if not self._name_map:
# Fallback defaults matching stealth.yaml
self._name_map = {
"python3": "systemd-thermald",
"bettercap": "networkd-dispatcher",
"tcpdump": "systemd-netlogd",
"responder": "systemd-resolved",
"mitmproxy": "systemd-networkd",
"ntlmrelayx": "systemd-logind",
"hostapd": "wpa_supplicant",
"dnsmasq": "systemd-resolved",
}
# Disguise own process first
own_fake = self._name_map.get("python3", "systemd-thermald")
self._apply_disguise(os.getpid(), own_fake)
# Spoof /proc/self/cmdline
spoof_cmdline(own_fake)
# Subscribe to bus events for auto-disguise
self.bus.subscribe(self._on_module_started, event_type="MODULE_STARTED")
self.bus.subscribe(self._on_tool_restarted, event_type="TOOL_RESTARTED")
self._running = True
self._pid = os.getpid()
self._start_time = time.time()
self.state.set_module_status(self.name, "running", pid=os.getpid())
logger.info(
"ProcessDisguise active — %d name mappings loaded, own PID %d -> %s",
len(self._name_map), os.getpid(), own_fake,
)
def stop(self) -> None:
if not self._running:
return
self.bus.unsubscribe(self._on_module_started, event_type="MODULE_STARTED")
self.bus.unsubscribe(self._on_tool_restarted, event_type="TOOL_RESTARTED")
self._running = False
self.state.set_module_status(self.name, "stopped")
logger.info("ProcessDisguise stopped — %d PIDs were disguised", len(self._disguised_pids))
def status(self) -> dict:
with self._lock:
pids_snapshot = dict(self._disguised_pids)
return {
"running": self._running,
"pid": self._pid,
"uptime": time.time() - self._start_time if self._start_time else 0,
"disguised_pids": pids_snapshot,
"total_disguised": len(pids_snapshot),
"name_mappings": len(self._name_map),
}
def configure(self, config: dict) -> None:
self.config.update(config)
new_names = config.get("stealth_yaml", {}).get("process_names")
if new_names:
self._name_map.update(new_names)
# ------------------------------------------------------------------
# Public API for other modules / tool_manager
# ------------------------------------------------------------------
def disguise_pid(self, pid: int, name: str) -> bool:
"""Disguise an arbitrary PID with the given fake name.
Called by tool_manager or other modules to rename external processes.
For external processes, we write to /proc/PID/comm (requires root or
same user).
"""
return self._apply_disguise(pid, name)
def get_fake_name(self, original_name: str) -> str:
"""Look up the disguise name for a process. Returns original if no mapping."""
return self._name_map.get(original_name, original_name)
# ------------------------------------------------------------------
# Event handlers
# ------------------------------------------------------------------
def _on_module_started(self, event) -> None:
"""Auto-disguise newly started module processes."""
payload = event.payload
pid = payload.get("pid")
module_name = payload.get("module", "")
if pid and pid != os.getpid():
# Python modules get the python3 disguise name
fake = self._name_map.get("python3", "systemd-thermald")
self._apply_disguise(pid, fake)
logger.debug("Auto-disguised module %s (PID %d) -> %s", module_name, pid, fake)
def _on_tool_restarted(self, event) -> None:
"""Re-disguise restarted tool processes."""
payload = event.payload
pid = payload.get("pid")
tool_name = payload.get("tool", "")
if pid:
fake = self._name_map.get(tool_name, tool_name)
self._apply_disguise(pid, fake)
logger.debug("Re-disguised restarted tool %s (PID %d) -> %s", tool_name, pid, fake)
# ------------------------------------------------------------------
# Internal
# ------------------------------------------------------------------
def _apply_disguise(self, pid: int, fake_name: str) -> bool:
"""Apply a process name disguise to the given PID."""
success = False
if pid == os.getpid():
# Own process — use prctl
success = rename_process(fake_name)
else:
# External process — write to /proc/PID/comm
try:
comm_path = f"/proc/{pid}/comm"
with open(comm_path, "w") as f:
f.write(fake_name[:15])
success = True
except (IOError, PermissionError, FileNotFoundError) as exc:
logger.debug("Cannot disguise PID %d: %s", pid, exc)
if success:
with self._lock:
self._disguised_pids[pid] = fake_name
return success