# SPDX-License-Identifier: GPL-3.0-or-later """Safe, offline drills that emit non-event built-in SIDs.""" from __future__ import annotations import os import tempfile from collections.abc import Callable from dataclasses import dataclass, field from pathlib import Path from enodia_sentinel import fim, pkgdb, posture, rootcheck from enodia_sentinel.alert import Alert from enodia_sentinel.config import Config from enodia_sentinel.detectors import ( credential_access, deleted_exe, egress, input_snooper, ld_preload, memory_obfuscation, new_listener, new_suid, persistence, reverse_shell, stealth_network, ) from enodia_sentinel.system import MemoryMap, Socket, SystemState @dataclass class FakeProc: pid: int comm: str = "bash" cmdline: str = "" exe: str = "" environ: dict = field(default_factory=dict) _stdio_inode: int | None = None ppid: int = 1 uid: int = 0 cwd: str = "/" fd_targets: dict = field(default_factory=dict) memory_maps: list = field(default_factory=list) def stdio_socket_inode(self): return self._stdio_inode def _cfg() -> Config: return Config() def _rootcheck_alerts(**views) -> list[Alert]: saved = { name: getattr(rootcheck, name) for name in ( "proc_pids", "alive_pids", "proc_modules", "sys_live_modules", "procnet_listen_ports", "procnet_udp_ports", "procnet_raw_protocols", "procnet_protocol_kinds", "ps_pids", "proc_pid_exists", "promiscuous_interfaces", "module_taints", "kernel_taint", ) } try: rootcheck.proc_pids = lambda: views.get("proc_pids", {1}) rootcheck.alive_pids = lambda cap: views.get("alive_pids", {1}) rootcheck.proc_modules = lambda: views.get("proc_modules", {"ext4"}) rootcheck.sys_live_modules = lambda: views.get("sys_live_modules", {"ext4"}) rootcheck.procnet_listen_ports = lambda: views.get("procnet_ports", set()) rootcheck.procnet_udp_ports = lambda: views.get("procnet_udp_ports", set()) rootcheck.procnet_raw_protocols = lambda: views.get( "procnet_raw_protocols", set()) rootcheck.procnet_protocol_kinds = lambda: views.get( "procnet_protocol_kinds", set()) rootcheck.ps_pids = lambda: views.get("ps_pids", {1}) rootcheck.proc_pid_exists = lambda pid: pid in views.get( "existing_pids", {1}) rootcheck.promiscuous_interfaces = lambda: views.get("promisc", []) rootcheck.module_taints = lambda: views.get("module_taints", {}) rootcheck.kernel_taint = lambda: views.get("kernel_taint", 0) return list(rootcheck.run(_cfg(), views.get("state", SystemState()))) finally: for name, fn in saved.items(): setattr(rootcheck, name, fn) def _fim_diff_alerts() -> list[Alert]: return list(fim.diff_alerts({ "modified": [("/etc/ssh/sshd_config", ["sha256"])], "added": ["/etc/systemd/system/evil.service"], "removed": ["/etc/profile.d/hardening.sh"], })) def _pkgdb_tamper() -> list[Alert]: with tempfile.TemporaryDirectory() as d: cfg = _cfg() cfg.log_dir = Path(d) pkgdb.check(cfg, _fingerprint=lambda: "FP1", _last_tx=lambda: None) alert = pkgdb.check(cfg, _fingerprint=lambda: "FP2", _last_tx=lambda: None) return [alert] if alert else [] def _siglevel() -> list[Alert]: with tempfile.NamedTemporaryFile("w", delete=False) as fh: fh.write("[options]\nSigLevel = Never\n") path = fh.name try: alert = pkgdb.siglevel_alert(path) return [alert] if alert else [] finally: os.unlink(path) def _pkgverify() -> list[Alert]: cfg = _cfg() saved = pkgdb.siglevel_alert pkgdb.siglevel_alert = lambda *a, **k: None try: alerts = pkgdb.verify_alerts( cfg, _installed=lambda: [("openssh", "9.0")], _verify=lambda name, version: { "pkg": f"{name}-{version}", "cached": "/cache/pkg.tar.zst", "checked": 1, "mismatched": [("/usr/bin/ssh", "dead", "beef")], }, ) return [a for a in alerts if a.sid == pkgdb.SID_PKGVERIFY] finally: pkgdb.siglevel_alert = saved def _fim_pkg() -> list[Alert]: saved = fim.pacman_verify fim.pacman_verify = lambda timeout=600: [ ("openssh", "/usr/bin/ssh", "SHA256 checksum mismatch"), ] try: return list(fim.pacman_verify_alerts()) finally: fim.pacman_verify = saved def _persistence() -> list[Alert]: with tempfile.TemporaryDirectory() as d: path = Path(d) / "evil.service" path.write_text("[Service]\nExecStart=/tmp/payload\n") cfg = _cfg() cfg.watch_persistence = (str(path),) state = SystemState(persist_since=path.stat().st_mtime - 1) return list(persistence.detect(state, cfg)) def _hidden_proc() -> list[Alert]: pid = os.getpid() return _rootcheck_alerts(proc_pids={1}, alive_pids={1, pid}, existing_pids={1}, ps_pids={1}) def _ps_hidden_proc() -> list[Alert]: return _rootcheck_alerts(proc_pids={1, 4242}, alive_pids={1, 4242}, existing_pids={1, 4242}, ps_pids={1}) DRILLS: dict[int, Callable[[], list[Alert]]] = { 100010: lambda: list(reverse_shell.detect( SystemState( processes=[FakeProc(pid=100, comm="bash", _stdio_inode=999, cmdline="bash -i")], sockets=[Socket("ESTAB", "127.0.0.1:55", "9.9.9.9:443", 999, "bash", 100)], ), _cfg(), )), 100011: lambda: list(ld_preload.detect( SystemState(processes=[ FakeProc(pid=200, comm="sleep", environ={"LD_PRELOAD": "/tmp/evil.so"}), ]), _cfg(), )), 100012: lambda: list(deleted_exe.detect( SystemState(processes=[ FakeProc(pid=300, comm="x", exe="/tmp/x (deleted)"), ]), _cfg(), )), 100013: lambda: list(new_listener.detect( SystemState( sockets=[Socket("LISTEN", "0.0.0.0:31337", "0.0.0.0:0", 1, "hoxha", 340)], listener_baseline=set(), ), _cfg(), )), 100014: lambda: list(new_suid.detect( SystemState(suid_binaries=["/tmp/evil", "/usr/bin/sudo"], suid_baseline={"/usr/bin/sudo"}), _cfg(), )), 100015: _persistence, 100016: lambda: list(egress.detect( SystemState(sockets=[ Socket("ESTAB", "10.0.0.2:5000", "8.8.8.8:443", 1, "python3", 400), ]), _cfg(), )), 100017: _fim_diff_alerts, 100018: _fim_diff_alerts, 100019: _fim_diff_alerts, 100020: _fim_pkg, 100021: _pkgdb_tamper, 100022: _hidden_proc, 100023: lambda: _rootcheck_alerts( proc_modules={"ext4"}, sys_live_modules={"ext4", "evil_mod"}), 100024: lambda: _rootcheck_alerts( procnet_ports={22, 31337}, state=SystemState(sockets=[ Socket("LISTEN", "0.0.0.0:22", "0.0.0.0:0", 10, "sshd", 1), ])), 100025: lambda: _rootcheck_alerts(promisc=["eth0"]), 100026: _siglevel, 100027: _pkgverify, 100028: lambda: _rootcheck_alerts( proc_modules={"ext4"}, sys_live_modules={"ext4", "diamorphine"}), 100029: lambda: _rootcheck_alerts( proc_modules={"vendor_gpu"}, sys_live_modules={"vendor_gpu"}, module_taints={"vendor_gpu": "OE"}), 100030: lambda: _rootcheck_alerts(kernel_taint=(1 << 12) | (1 << 13)), 100031: lambda: _rootcheck_alerts( procnet_udp_ports={53, 4444}, state=SystemState(sockets=[ Socket("UNCONN", "0.0.0.0:53", "0.0.0.0:0", 11, "dnsmasq", 2), ])), 100032: lambda: list(input_snooper.detect( SystemState(processes=[ FakeProc(pid=320, comm="hoxha", fd_targets={"7": "/dev/input/event3"}), ]), _cfg(), )), 100033: lambda: list(credential_access.detect( SystemState(processes=[ FakeProc(pid=330, comm="hoxha", fd_targets={"4": "/etc/shadow"}), ]), _cfg(), )), 100034: lambda: _rootcheck_alerts( procnet_raw_protocols={1, 58}, state=SystemState(sockets=[ Socket("UNCONN", "0.0.0.0:ipv6-icmp", "0.0.0.0:*", 12, "monitor", 3, "raw"), ])), 100035: lambda: _rootcheck_alerts(procnet_raw_protocols={1}), 100036: lambda: list(stealth_network.detect( SystemState(sockets=[ Socket("ESTAB", "10.0.0.2:5000", "8.8.8.8:4443", 1, "hoxha", 340, "sctp"), ]), _cfg(), )), 100037: lambda: _rootcheck_alerts( procnet_protocol_kinds={"sctp", "packet"}, state=SystemState(sockets=[ Socket("UNCONN", "*:eth0", "*", 13, "tcpdump", 4, "packet"), ])), 100038: _ps_hidden_proc, 100039: lambda: list(memory_obfuscation.detect( SystemState(processes=[ FakeProc(pid=351, comm="packer", memory_maps=[MemoryMap(0x3000, 0x4000, "rwxp", "")]), ]), _cfg(), )), 100040: lambda: list(posture.sshd_findings({"permitrootlogin": "yes"})), 100041: lambda: list(posture.sshd_findings({})), 100042: lambda: list(posture.sshd_findings({ "permitemptypasswords": "yes", "passwordauthentication": "no"})), 100043: lambda: list(posture.sudoers_findings([ ("/etc/sudoers", "alice ALL=(ALL) NOPASSWD: ALL\n", 0o440), ])), 100044: lambda: list(posture.sudoers_findings([ ("/etc/sudoers", "Defaults !authenticate\n", 0o440), ])), 100045: lambda: list(posture.sudoers_findings([ ("/etc/sudoers.d/bad", "root ALL=(ALL) ALL\n", 0o646), ])), 100046: lambda: list(posture.path_findings([ ("/usr/local/bin", 0o40777, 0), ])), 100047: lambda: list(posture.sensitive_file_findings([ ("/etc/shadow", 0o100644), ])), 100048: lambda: list(memory_obfuscation.detect( SystemState(processes=[ FakeProc(pid=352, comm="bash", memory_maps=[ MemoryMap(0x5000, 0x6000, "r-xp", "/usr/lib/libhide.so"), ]), ]), _cfg(), )), 100050: lambda: list(posture.systemd_findings([ posture.UnitFact("foo.service", "/etc/systemd/system/foo.service", 0o100664, True, ("/usr/bin/foo",)), ])), 100051: lambda: list(posture.systemd_findings([ posture.UnitFact("evil.service", "/etc/systemd/system/evil.service", 0o100644, True, ("/tmp/payload",)), ])), }