Add memory obfuscation and ps-hidden process detection
This commit is contained in:
parent
cb334c0c94
commit
893409b549
17 changed files with 353 additions and 26 deletions
|
|
@ -20,6 +20,7 @@ from . import (
|
|||
egress,
|
||||
input_snooper,
|
||||
ld_preload,
|
||||
memory_obfuscation,
|
||||
new_listener,
|
||||
new_suid,
|
||||
persistence,
|
||||
|
|
@ -46,6 +47,7 @@ REGISTRY: tuple[Detector, ...] = (
|
|||
Detector("input_snooper", input_snooper.detect),
|
||||
Detector("credential_access", credential_access.detect),
|
||||
Detector("stealth_network", stealth_network.detect),
|
||||
Detector("memory_obfuscation", memory_obfuscation.detect),
|
||||
Detector("egress", egress.detect),
|
||||
Detector("new_listener", new_listener.detect, needs_baseline=True),
|
||||
Detector("persistence", persistence.detect, needs_baseline=True),
|
||||
|
|
|
|||
100
enodia_sentinel/detectors/memory_obfuscation.py
Normal file
100
enodia_sentinel/detectors/memory_obfuscation.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""memory_obfuscation — memory-map indicators of hiding or encrypted 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.
|
||||
"""
|
||||
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
|
||||
|
||||
_HIDE_LIBRARY_HINTS = frozenset({
|
||||
"libhide", "libprocesshide", "process_hide", "proc_hide", "rootkit_hide",
|
||||
})
|
||||
|
||||
|
||||
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 _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 ("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 = (SID_PROCESS_HIDING_LIBRARY if signature == "process_hiding_library"
|
||||
else 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" if signature == "process_hiding_library"
|
||||
else "memory-obfuscation"),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue