Threat detection

Beyond inventory drift, Delta reports observations that commonly accompany a compromise: contact with infrastructure the operator has listed as known-bad, rootkit and persistence indicators, and changed DNS resolvers. Every finding carries a severity and, where it maps cleanly, a MITRE ATT&CK technique id; the findings also feed the aggregate risk score.

What this is and is not. These are comparisons — against a policy, a baseline, or a list of indicators the operator supplies — reported as structured findings. Delta ships no signature database, runs no heuristics or classifier, fetches nothing from the network, and never concludes on its own that something is malicious. It reports what it saw and how that differs from what was declared normal; whether that constitutes an incident is the operator’s call, and any action on the host is a separate, explicitly commanded step (see Shipping reports to a server). A build that cannot act on the host at all is one CMake switch away — see Report-only builds.

All sources are path-based, so these checks apply equally to a live host and to an offline firmware image under --root.

Threat intelligence (IOC feed)

--ioc-feed <file> matches what Delta observes against a local Indicator-of-Compromise feed — the threat-intelligence counterpart to the CVE (vulnerability) feed. The feed is a file you write and distribute; Delta neither ships nor updates one. Matching is literal — exact IP equality, IPv4 CIDR containment, case-insensitive hash equality — so this is inverted policy (“these must not appear”) rather than malware analysis. It is matched against:

  • remote connection peers — the socket table (/proc/net/{tcp,udp}{,6}), so an outbound session to a flagged host is caught (C2 — T1071);

  • ARP neighbors;

  • file fingerprints — a known-bad file hash on disk (T1204).

Feed schema:

{
  "ips":    ["203.0.113.5"],
  "cidrs":  ["198.51.100.0/24"],
  "hashes": ["d41d8cd98f00b204e9800998ecf8427e"]
}

ips are exact matches, cidrs are IPv4 network matches, hashes match file checksums case-insensitively. No network access is made — the feed is a local file you distribute. A match produces a violation in the report and nothing else: the file is not touched, the process is not stopped, the peer is not blocked.

delta --netif --ioc-feed ioc.json --json

Running executable images

The fingerprint scan (--fingerprint) only sees files under the directory it is pointed at. To catch malware that runs from elsewhere — /tmp, a user’s home, or entirely from memory — Delta resolves and MD5s each running process’s executable image (/proc/<pid>/exe) when --processes is collected. This feeds two detections (Linux live host only):

  • the image hash is matched against the IOC feed, so a running known-bad binary is caught even if it was never on a scanned path (T1204). The exe symlink opens the real inode even after unlinking, so a self-deleting dropper is still hashed;

  • a process whose image was unlinked while running (the kernel resolves exe to "… (deleted)") is flagged outright — a self-deleting dropper or a binary replaced under a live service (T1070.004, high).

In Continuous monitoring the image hash also joins the drift keyset (procexe:<name>=<hash>), so a binary swapped under a running service name surfaces as drift with no feed at all.

System / runtime integrity

Always evaluated (the heuristics need no policy):

  • Loaded kernel modules — captured in a baseline; an unexpected module on re-check is flagged (rootkit vector — T1547.006).

  • /etc/ld.so.preload — a non-empty preload file is a library-injection persistence mechanism (T1574.006, critical).

  • World-writable executable directories — a writable /usr/bin &c. lets any user trojan a system binary (T1574).

Kernel memory (SLAB/SLUB)

/proc/meminfo and the slab allocator are collected on every run. One check needs no policy:

  • Slab caches hidden from /proc/slabinfo — the slab memory meminfo accounts for is cross-checked against the sum of the caches slabinfo lists. A gross shortfall means caches are not being reported, which is what a rootkit filtering the file looks like (T1014, high). The full comparison is published as a consistency block, so a smaller divergence shows up in data_consistent without failing the run.

The rest is enforced only when the memory norm block is configured (see Configuration), and splits into exhaustion and exploitation surface:

  • Available memory / swap headroom below the configured floor — a device that has run out of usable memory has stopped doing its job, and memory exhaustion is a denial-of-service primitive an attacker can drive (T1499);

  • Slab and unreclaimable-slab share of RAM above the configured ceiling. SUnreclaim is the telling one: reclaimable slab is cache the kernel gives back under pressure, while unreclaimable slab only grows if kernel objects are not being freed (T1499);

  • A single slab cache past max_cache_mb — a leak (or a deliberate spray) concentrates in one cache long before it registers as total exhaustion (T1499);

  • /proc/slabinfo readable beyond root — the file is a live map of kernel heap layout and object counts, which is exactly the reconnaissance a slab-spray exploit needs to aim (T1082);

  • Freelist hardening off — a kernel built without CONFIG_SLAB_FREELIST_HARDENED leaves slab freelist pointers unobfuscated, and without CONFIG_SLAB_FREELIST_RANDOM the allocation order is predictable; both make heap corruption far easier to turn into control (T1068).

In Continuous monitoring the volume side also feeds the behavioral model (mem_available_pct, slab_unreclaim_kb), so a slow leak surfaces as a trend anomaly rather than waiting to cross a static threshold.

Accounts & SSH persistence

From /etc/passwd and each user’s ~/.ssh/authorized_keys:

  • Extra UID-0 account — any account other than root holding UID 0 is a root-equivalent backdoor (T1136.001, critical). A baseline records the observed UID-0 set; the allow-list can also be pinned explicitly with uid0_accounts.

  • authorized_keys — key counts per user are tracked, so an added key (a common SSH persistence trick) shows up as drift in Continuous monitoring.

Persistence surface

The classic Unix persistence locations are fingerprinted on every collection — no configuration needed:

  • cron — /etc/crontab, /etc/anacrontab, /etc/cron.d and the cron.{hourly,daily,weekly,monthly} directories, plus per-user crontabs under /var/spool/cron (T1053.003);

  • systemd units — /etc/systemd/system including the *.wants enablement level (symlinks are followed, so enabling a unit is visible) (T1543.002);

  • init scripts — /etc/init.d, /etc/rc.local (T1037);

  • shell profiles — /etc/profile, /etc/profile.d, /etc/bash.bashrc, and the .bashrc / .profile / .bash_profile / .zshrc of every account with an interactive shell (T1546.004);

  • /etc/hosts — silent redirection of any name the device resolves (T1565.001);

  • trusted CA bundle — /etc/ssl/certs/ca-certificates.crt; an added root certificate enables TLS interception (T1553.004).

The files join the regular fingerprint set, so a baseline pins them: a later edit, addition or removal fails the file check and shows as drift in Continuous monitoring. Two hygiene findings need no baseline at all: a world-writable persistence file (critical) and a system persistence file not owned by root (high; live host only — extraction ownership makes it meaningless under --root).

DNS resolvers

The nameserver entries of /etc/resolv.conf are recorded. A baseline pins them; an unexpected resolver on re-check is flagged as a possible hijack (T1557). A silently-changed resolver redirects every lookup on the device.

Device identity

Delta emits a stable device identity — OS id/version (/etc/os-release), kernel release and machine-id — as a device block in the JSON report. The collector uses it to group a fleet; a change in OS or kernel between observations is itself a meaningful event (an unplanned firmware/OS swap), and is reported as drift in Continuous monitoring.

Risk score & ATT&CK

The JSON report includes an aggregate posture:

"risk": { "score": 80, "grade": "E" }

score (0–100) is a severity-weighted sum of the findings; grade is a single letter A (clean) … F (worst). Each violation may also carry an "attack" field with its MITRE ATT&CK technique id, so a SIEM can pivot on the technique directly.

Syscall behavior

Each check above reports what the host contains. Syscall behavior tracing reports what its software does. Delta samples a syscall profile for each cycle and puts each observation into a behavior class: module, ptrace, bpf, kexec, memexec, network and others. --trace-args also decodes the arguments of the selected syscalls: the file that a process opened, the peer that it contacted, the page that it made executable.

This covers the case that an inventory cannot cover: a legitimate binary that keeps its name, its path and its checksum, and changes its behavior. The syscalls norm block makes this a policy. The findings have the same severities and the same ATT&CK ids as the other findings on this page.

Configuration keys

The relevant norm keys (all optional; a generated baseline fills them in):

"norm": {
  "kernel_modules": ["ext4", "i915"], "other_modules_present": false,
  "resolvers": ["1.1.1.1"],           "other_resolvers_present": false,
  "uid0_accounts": ["root"],
  "memory": { "min_available_pct": 10, "max_unreclaim_pct": 25,
              "require_slabinfo_restricted": true },
  "syscalls": { "forbidden": ["init_module", "bpf"],
                "deny_classes": ["module", "kexec"],
                "forbidden_args": ["path=/etc/shadow", "prot=rwx"] }
}

The full memory key set is in Configuration; a baseline does not fill it in, since the thresholds are a deployment decision rather than an observed state.