314 lines
11 KiB
Python
314 lines
11 KiB
Python
# 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",)),
|
|
])),
|
|
}
|