120 lines
4.4 KiB
Python
120 lines
4.4 KiB
Python
# 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/<pid>/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 '<anonymous>'}"
|
|
)
|
|
if executable and writable:
|
|
return "memory_obfuscation", Severity.HIGH, (
|
|
f"writable+executable memory mapping perms={mm.perms} path={path or '<anonymous>'}"
|
|
)
|
|
if executable and _is_anon_exec_path(path):
|
|
return "memory_obfuscation", Severity.HIGH, (
|
|
f"executable anonymous memory mapping perms={mm.perms} path={path or '<anonymous>'}"
|
|
)
|
|
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"),
|
|
)
|