# SPDX-License-Identifier: GPL-3.0-or-later """memory_obfuscation — memory-map indicators of hiding or injected payloads. Encrypted/packed implants still need executable memory after decrypting code. This detector does not read process memory; it only inspects ``/proc//maps`` for high-signal shapes: executable anonymous mappings, executable memfd/deleted mappings, RWX pages, and mapped libraries associated with process hiding or process injection. """ from __future__ import annotations from collections.abc import Iterator from ..alert import Alert, Severity from ..config import Config from ..system import MemoryMap, SystemState SID_MEMORY_OBFUSCATION = 100039 SID_PROCESS_HIDING_LIBRARY = 100048 SID_PROCESS_INJECTION_LIBRARY = 100049 _HIDE_LIBRARY_HINTS = frozenset({ "libhide", "libprocesshide", "process_hide", "proc_hide", "rootkit_hide", }) _WRITABLE_MAP_PREFIXES = ("/tmp/", "/dev/shm/", "/var/tmp/", "/run/user/") _LIB_EXTENSIONS = (".so", ".so.", ".dylib") def _maps(proc) -> list[MemoryMap]: maps = getattr(proc, "memory_maps", []) return maps() if callable(maps) else maps def _is_anon_exec_path(path: str) -> bool: if not path: return True if path in {"[heap]", "[stack]"}: return True return path.startswith("[anon") or path.startswith("[stack:") def _hide_library(path: str) -> bool: name = path.rsplit("/", 1)[-1].lower() return any(hint in name for hint in _HIDE_LIBRARY_HINTS) def _writable_library(path: str) -> bool: lower = path.lower() if not lower.startswith(_WRITABLE_MAP_PREFIXES): return False name = lower.rsplit("/", 1)[-1] return any(ext in name for ext in _LIB_EXTENSIONS) def _allowed_path(path: str, cfg: Config) -> bool: for prefix in cfg.memory_obfuscation_allow_paths: if prefix and path.startswith(prefix): return True return False def _indicator(mm: MemoryMap, cfg: Config) -> tuple[str, Severity, str]: path = mm.path if path and _allowed_path(path, cfg): return "", Severity.MEDIUM, "" if path and _hide_library(path): return "process_hiding_library", Severity.CRITICAL, ( f"mapped library name suggests process hiding: {path}" ) executable = "x" in mm.perms writable = "w" in mm.perms if executable and path and _writable_library(path): return "process_injection_library", Severity.HIGH, ( f"executable library mapped from writable/runtime path: {path}" ) if executable and ("memfd:" in path or "(deleted)" in path): return "memory_obfuscation", Severity.CRITICAL, ( f"executable transient mapping perms={mm.perms} path={path or ''}" ) if executable and writable: return "memory_obfuscation", Severity.HIGH, ( f"writable+executable memory mapping perms={mm.perms} path={path or ''}" ) if executable and _is_anon_exec_path(path): return "memory_obfuscation", Severity.HIGH, ( f"executable anonymous memory mapping perms={mm.perms} path={path or ''}" ) return "", Severity.MEDIUM, "" def detect(state: SystemState, cfg: Config) -> Iterator[Alert]: for proc in state.processes: comm = proc.comm or "?" seen: set[str] = set() for mm in _maps(proc): signature, severity, detail = _indicator(mm, cfg) if not signature or signature in seen: continue if signature == "memory_obfuscation" and comm in cfg.memory_obfuscation_allow_comms: continue seen.add(signature) sid = { "process_hiding_library": SID_PROCESS_HIDING_LIBRARY, "process_injection_library": SID_PROCESS_INJECTION_LIBRARY, }.get(signature, SID_MEMORY_OBFUSCATION) yield Alert( severity=severity, signature=signature, key=f"mem:{signature}:{proc.pid}:{mm.start:x}-{mm.end:x}", detail=( f"pid={proc.pid} comm={comm} {detail} " f"range={mm.start:x}-{mm.end:x}" ), pids=(proc.pid,), sid=sid, classtype={ "process_hiding_library": "process-hiding", "process_injection_library": "process-injection", }.get(signature, "memory-obfuscation"), )