ffd384f64b
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
180 lines
6.6 KiB
Python
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
|