Expand monitoring for credential theft and covert protocols

This commit is contained in:
Luna 2026-06-13 03:30:36 -07:00
parent 3e5f8fc3f7
commit cb334c0c94
17 changed files with 675 additions and 25 deletions

View file

@ -32,6 +32,9 @@ SID_KNOWN_ROOTKIT_MODULE = 100028
SID_TAINTED_MODULE = 100029
SID_KERNEL_TAINTED = 100030
SID_HIDDEN_UDP_PORT = 100031
SID_HIDDEN_RAW_SOCKET = 100034
SID_RAW_ICMP_SOCKET = 100035
SID_HIDDEN_PROTOCOL_SOCKET = 100037
IFF_PROMISC = 0x100
@ -62,6 +65,13 @@ TAINT_FLAGS = {
}
HIGH_RISK_TAINT_BITS = frozenset({1, 3, 7, 12, 13})
ICMP_RAW_PROTOCOLS = frozenset({1, 58}) # ICMP, IPv6-ICMP
RAW_PROTOCOL_NAMES = {
"icmp": 1,
"ipv6-icmp": 58,
"icmp6": 58,
"sctp": 132,
}
# --- pure diff cores (unit-tested without a live system) ------------------
@ -86,6 +96,18 @@ def find_hidden_udp_ports(procnet_ports: set[int], ss_ports: set[int]) -> set[in
return procnet_ports - ss_ports
def find_hidden_raw_protocols(procnet_protocols: set[int],
ss_protocols: set[int]) -> set[int]:
"""Raw socket protocols in /proc/net/raw that ss does not report."""
return procnet_protocols - ss_protocols
def find_hidden_protocol_kinds(procnet_kinds: set[str],
ss_kinds: set[str]) -> set[str]:
"""Special socket families visible in /proc/net but absent from ss."""
return procnet_kinds - ss_kinds
def _norm_module(name: str) -> str:
return name.lower().replace("-", "_")
@ -238,6 +260,60 @@ def procnet_udp_ports() -> set[int]:
return _procnet_udp_ports("/proc/net/udp") | _procnet_udp_ports("/proc/net/udp6")
def _procnet_raw_protocols(path: str) -> set[int]:
protocols: set[int] = set()
try:
with open(path) as fh:
next(fh, None) # header
for line in fh:
parts = line.split()
if len(parts) < 2:
continue
local = parts[1] # HEX_ADDR:HEX_PROTO
proto = int(local.rsplit(":", 1)[1], 16)
if proto:
protocols.add(proto)
except (OSError, ValueError):
pass
return protocols
def procnet_raw_protocols() -> set[int]:
return (_procnet_raw_protocols("/proc/net/raw")
| _procnet_raw_protocols("/proc/net/raw6"))
def _procnet_has_rows(path: str) -> bool:
try:
with open(path) as fh:
next(fh, None) # header
for line in fh:
if line.strip():
return True
except OSError:
pass
return False
def procnet_protocol_kinds() -> set[str]:
kinds: set[str] = set()
checks = {
"sctp": (
"/proc/net/sctp/eps",
"/proc/net/sctp/assocs",
"/proc/net/sctp/remaddr",
),
"dccp": ("/proc/net/dccp", "/proc/net/dccp6"),
"packet": ("/proc/net/packet",),
"tipc": ("/proc/net/tipc/socket",),
"xdp": ("/proc/net/xdp",),
}
for kind, paths in checks.items():
if any(_procnet_has_rows(path) for path in paths):
kinds.add(kind)
return kinds
def ss_listen_ports(state: SystemState) -> set[int]:
ports: set[int] = set()
for s in state.listening_sockets():
@ -258,6 +334,31 @@ def ss_udp_ports(state: SystemState) -> set[int]:
return ports
def _protocol_from_endpoint(endpoint: str) -> int | None:
tail = endpoint.rsplit(":", 1)
if len(tail) != 2:
return None
label = tail[1].strip("[]").lower()
if label.isdigit():
return int(label)
return RAW_PROTOCOL_NAMES.get(label)
def ss_raw_protocols(state: SystemState) -> set[int]:
protocols: set[int] = set()
for s in state.sockets:
if s.kind != "raw":
continue
proto = _protocol_from_endpoint(s.local)
if proto is not None:
protocols.add(proto)
return protocols
def ss_protocol_kinds(state: SystemState) -> set[str]:
return {s.kind for s in state.sockets if s.kind}
def promiscuous_interfaces() -> list[str]:
out: list[str] = []
base = "/sys/class/net"
@ -361,6 +462,37 @@ def run(cfg: Config, state: SystemState | None = None) -> Iterator[Alert]:
"(tool may be hooked): " + ", ".join(map(str, sorted(hidden_udp)))),
sid=SID_HIDDEN_UDP_PORT, classtype="rootkit-hidden-port")
raw_protocols = procnet_raw_protocols()
hidden_raw = find_hidden_raw_protocols(raw_protocols, ss_raw_protocols(state))
if hidden_raw:
yield Alert(
severity=Severity.HIGH, signature="rootkit_hidden_raw_socket",
key=f"rk:hidraw:{min(hidden_raw)}",
detail=("raw socket protocol(s) in /proc/net/raw not reported by ss "
"(tool may be hooked): " + ", ".join(map(str, sorted(hidden_raw)))),
sid=SID_HIDDEN_RAW_SOCKET, classtype="rootkit-hidden-socket")
raw_icmp = raw_protocols & ICMP_RAW_PROTOCOLS
if raw_icmp:
yield Alert(
severity=Severity.HIGH, signature="raw_icmp_socket",
key=f"rk:rawicmp:{min(raw_icmp)}",
detail=("persistent raw ICMP socket protocol(s) visible in /proc/net/raw: "
+ ", ".join(map(str, sorted(raw_icmp)))
+ " — validate authorized ping/monitoring tools or ICMP knockers"),
sid=SID_RAW_ICMP_SOCKET, classtype="raw-socket-backdoor")
hidden_protocols = find_hidden_protocol_kinds(
procnet_protocol_kinds(), ss_protocol_kinds(state))
if hidden_protocols:
yield Alert(
severity=Severity.HIGH, signature="rootkit_hidden_protocol_socket",
key=f"rk:hidproto:{sorted(hidden_protocols)[0]}",
detail=("special protocol socket family present in /proc/net but "
"absent from ss output (tool may be hooked): "
+ ", ".join(sorted(hidden_protocols))),
sid=SID_HIDDEN_PROTOCOL_SOCKET, classtype="rootkit-hidden-socket")
for iface in promiscuous_interfaces():
yield Alert(
severity=Severity.MEDIUM, signature="promiscuous_interface",