Skip to content

Linux support with automated detection validation#7

Open
Murat-Oruntak wants to merge 3 commits into
krdmnbrk:mainfrom
Murat-Oruntak:feature/linux-support
Open

Linux support with automated detection validation#7
Murat-Oruntak wants to merge 3 commits into
krdmnbrk:mainfrom
Murat-Oruntak:feature/linux-support

Conversation

@Murat-Oruntak

@Murat-Oruntak Murat-Oruntak commented Jul 3, 2026

Copy link
Copy Markdown

Summary

This PR adds Linux support to AttackRuleMap's detection pipeline. Atomic Red Team Linux
tests run on an Ubuntu 24.04 VM (VirtualBox), auditd forwards the logs to Splunk, and a rule
counts as validated only if it actually triggers on the log the real test produced — the
same execution-based approach the project already uses for Windows.

What we start from — the full Linux surface

Two separate sets define the space:

1. Atomic Red Team — the tests

  • 407 Linux atomic tests across 118 techniques (supported_platforms: linux).

Not all are runnable in a log-collection lab: some are manual (no command), some compile
code / kernel modules on the target, and some need a second machine (C2). These are noted
individually in the findings.

2. Sigma + ESCU — the rules

Sigma ships 210 Linux rules, but they split by the telemetry they require:

Sigma category Rules Telemetry required
process_creation 122 Sysmon-for-Linux
file_event 8 Sysmon-for-Linux
network_connection 5 Sysmon-for-Linux
auditd 53 auditd (native Linux)
builtin 22 auditd / syslog

135 of the 210 rules (~64%) require Sysmon-for-Linux (Image, CommandLine fields).
Sysmon-for-Linux is experimental and not widely deployed, so this PR uses auditd — the
native, standard Linux audit source. That leaves 75 non-Sysmon Sigma rules (auditd +
builtin), plus Splunk's 55 ESCU linux_auditd detections, as the usable rule surface.

So the two sides are 407 tests / 118 techniques and 75 Sigma + 55 ESCU rules.
Running the tests shows which of them actually overlap.

How the validation runs

  • Per-test isolation. Each test reverts the VM to a clean snapshot before running, so
    tests never contaminate each other. The Splunk search is strictly time-boxed to each
    test's execution window.
  • auditd → Splunk. auditd captures EXECVE / SYSCALL / PATH / CONFIG_CHANGE records; a
    Universal Forwarder ships them to Splunk, where both the Sigma-compiled SPL and the ESCU
    linux_auditd detections are evaluated.
  • No rule or test is modified. When a rule doesn't fire, we investigate why and report
    it — never rewrite the rule or test to force a match.

Results

  • 37 MITRE ATT&CK techniques detected and validated (aligned to ATT&CK v19).
  • 49 unique detection rules fired: 32 Sigma + 17 ESCU/Splunk.
  • 112 validated test executions (each an atomic test caught by at least one rule),
    making 130 test↔rule matches in total.

These count three different things and are not meant to add up: 37 = distinct ATT&CK
techniques, 49 = distinct rules that fired (one technique can be caught by several
rules), 112 = distinct tests that were caught.

Detected techniques (37):

T1003.008, T1016, T1027.001, T1030, T1033, T1036.003, T1046, T1053.002, T1053.003,
T1053.006, T1057, T1059.004, T1070.003, T1070.006, T1082, T1083, T1105, T1113, T1115,
T1136.001, T1140, T1201, T1222.002, T1485, T1489, T1529, T1543.002, T1546.004, T1548.001,
T1548.003, T1552.001, T1552.003, T1552.004, T1560.001, T1564.001, T1574.006, T1685

Note: T1685 (parent) detects; its sub-technique T1685.004 (disabling auditd) does not — see Finding 5.

What changed

Code (automation/):

  • config.py — platform-aware configuration (PLATFORM, VM_SSH_PORT, USE_PROXMOX,
    VM_INTERFACE, ESCU_AGG_WINDOW_SECONDS).
  • execution_handler.py — SSH/bash executor for Linux (sudo, prereq handling, stdin
    execution for multiline/quoted commands, dependency staging to the VM).
  • vm_handler.py — VirtualBox (VBoxManage) support alongside the existing Proxmox path.
  • dynamic_generator.py — Linux Sigma-rule filtering, CIM-mapping skip for auditd, and
    correct ESCU rule links for the Linux ruleset.
  • sigma_handler.py — Linux-only Sigma-rule filtering.
  • report_handler.py — platform-specific MITRE Navigator layers and Linux coverage stats.
  • dependency_handler.py — dependency staging path for Linux tests.

Config / helpers:

  • .env.example — new environment variables documented.
  • .gitignore — ignore local run artifacts.
  • add_splunk_macros.py — helper to install the ESCU filter macros in Splunk.

Outputs (dist/):

  • attack_rule_map_linux.json — the Linux detection map (37 techniques, 112 entries).
  • mitre_layer_{sigma,splunk,combined}_linux.json — MITRE ATT&CK Navigator layers.

Key design decisions

  • Windows pipeline untouched. Every Linux-specific change is behind
    if config.PLATFORM == "linux"; the Windows path is unchanged (I could not test Windows,
    so each such spot has a comment saying so).
  • Proxmox pipeline untouched. VirtualBox support is behind a USE_PROXMOX flag.
  • auditd, not Sysmon-for-Linux — native, production-standard telemetry (Sysmon-for-Linux
    could be added later to unlock the 135 Sysmon Sigma rules).
  • Validation purity — no detection rule or Atomic test is ever modified; only the lab is.

VM / deployment setup

  • Ubuntu 24.04 on VirtualBox (NAT + port-forward 2222→22).
  • Splunk Universal Forwarder shipping auditd logs.
  • auditd rule set (execve, file watches, syscall monitoring).
  • Packages some tests need (e.g. at, xclip, tcpdump, steghide, imagemagick).
  • Splunk props/transforms to rebuild execve_command and decode proctitle when auditd
    is ingested as raw audit.log (sourcetype=linux_audit) — exact config in the Deployment
    notes
    of the write-up below.

Limitations & future work

  • Sysmon-for-Linux rules are not covered. This PR validates against auditd only, so the
    135 Sigma rules that rely on Sysmon-for-Linux fields are out of scope. Adding that telemetry
    later would let them be validated too.
  • Some ESCU rules need the CIM / Endpoint datamodel, which isn't populated in this lab;
    those cases are noted in the findings.
  • A few "Linux" Atomic tests can't be run in this lab — some are mislabeled FreeBSD, some
    need a compiler/kernel toolchain, and some need a second machine. These are listed
    per-technique in the findings.
  • Windows was not tested. Linux-specific changes are guarded so the Windows path stays as
    it was, but I could not run the Windows pipeline to confirm.

Findings

Execution surfaces techniques a paper mapping calls "covered" but which do not detect when
run. Each was investigated and resolved into a real rule/ecosystem gap (reported, not
patched) or could not be validated on this lab (with an evidenced reason). Highlights:

  • Rules that pin an exact a0 short name while auditd logs the full path (Finding 1).
  • Rules that assume a fixed argument position while the test orders arguments differently
    (Finding 3).
  • A rule hard-coding RHEL-only PAM paths on a Debian host (Finding 4).
  • A rule querying Sysmon fields for an action auditd captures natively in CONFIG_CHANGE
    (Finding 5).
  • Tests that need a compiler/kernel toolchain, tests whose procedure doesn't overlap the
    rule's procedure, and rules that require the (unpopulated) CIM Endpoint datamodel.

The full write-up — with end-to-end evidence for every non-detected technique, plus the exact
Splunk onboarding config — is in the collapsible section below.


Full findings write-up — end-to-end evidence for every non-detected technique (click to expand)

This document records what surfaced while validating Linux detection coverage (Atomic Red
Team tests → auditd → Splunk → Sigma/ESCU rules). It falls into a few kinds: real gaps in
the detection rules
themselves; data-onboarding / deployment lessons about how auditd
has to reach Splunk before existing rules can fire; and engineering notes on detections
that only fire once the lab runs the test faithfully. Each item says clearly which one it is.

Scope and method

Sigma's Linux rule set (210 rules total) splits by required telemetry:

Category Rules Telemetry
process_creation 122 Sysmon-for-Linux
file_event 8 Sysmon-for-Linux
network_connection 5 Sysmon-for-Linux
auditd 53 auditd (native Linux)
builtin 22 auditd / syslog

135 of 210 rules (~64%) are written against Sysmon-for-Linux fields (Image,
CommandLine, ...). Sysmon-for-Linux is not production-mature and not widely deployed,
so this lab uses auditd — the native, standard Linux audit source. That leaves the
75 non-Sysmon Sigma rules (auditd + builtin), plus Splunk's 55 ESCU linux_auditd
detections
, as the realistic detection surface. On the test side, Atomic Red Team ships
407 Linux tests across 118 techniques.

Method. We ran every test and checked which rules actually fired. When a technique had a
usable (auditd/ESCU) rule and a runnable Linux test but still didn't detect on the first
pass, we dug into it one by one. A paper mapping of ID-to-ID would call these "covered";
running them shows they are not. Each one ended up in exactly one of three buckets:
detected after a lab/onboarding fix, a real rule/ecosystem gap (reported, not
patched), or could not be validated on this lab (each with a concrete reason).

Result: 37 ATT&CK techniques detected (aligned to ATT&CK v19), entirely through lab and
data-onboarding correctness, without altering any rule or test.


Detections that required lab/onboarding correctness

Every fix here follows the same rule: run the real test, watch the real detection rule
fire, and change neither of them.
We only fix (a) how the pipeline runs the test, and
(b) how the auditd logs are loaded into Splunk, so the data looks the way the (unchanged)
rules already expect.

A. Test-execution fixes (the pipeline now runs the test as written)

  • Multiline / quoted commands (stdin execution). Commands with inner double quotes
    (e.g. if [ "$(uname)" = 'FreeBSD' ]; then cmd="netstat -ant"; fi) broke when wrapped in
    sh -c "…". The Linux path now feeds such scripts over stdin (sh -s). Unblocked
    T1016 and is a prerequisite for many multiline tests.
  • Dependency staging path — enabled T1574.006 (helper files now land on the VM).
  • Conflicting snapshot auditd rule removed — enabled T1046.
  • Missing package pre-installed into the VM snapshot — some tests need a tool the
    Atomic get_prereq only echoes a reminder for; installing it and re-snapshotting
    enabled T1053.002 (at/atd).

B. Detection data-onboarding — reconciling an ingestion difference (not a rule gap)

Why they didn't fire. The ESCU linux_auditd rules expect auditd data in the exact
shape the official Splunk Add-on for *nix produces. That add-on ingests auditd through
a script (bin/rlog.sh) that runs ausearch -i, which decodes the hex-encoded
proctitle into readable text
, and its [auditd] stanza builds a joined execve_command
field — all under sourcetype=auditd. This lab does something simpler and very common
instead: a Universal Forwarder tails the raw /var/log/audit/audit.log
(sourcetype=linux_audit). In that setup proctitle stays hex-encoded and execve_command
is never built, so the ESCU rules silently do nothing — not because the rules are wrong,
but because the data reached Splunk in a different shape than they expect.
(Two signs this
is a known issue: the linux_auditd macro ships as a fill-in-yourself placeholder —
"Replace the macro definition with configurations for your Splunk Environment" — and ESCU
even shipped a now-deprecated linux_auditd_normalized_proctitle_process macro that
hex-decodes proctitle for exactly this situation.)

So this is a deployment issue, not a rule or ecosystem gap — but worth documenting,
because tailing the raw audit.log is a common setup and these rules fail silently under it.
We rebuilt the same fields the official ingestion produces, in Splunk props/transforms for
the linux_audit sourcetype (rules and tests untouched):

  • execve_command rebuilt — the joined command line ausearch -i / the Add-on's
    [auditd] stanza produce; enabled T1083, T1115, T1140, T1552.004, T1548.001.
  • proctitle hex-decoded — the same decode ausearch -i (and the deprecated ESCU
    macro) perform, done here in pure SPL (printf("%c", …)); enabled T1485, T1003.008,
    T1053.006, T1548.003
    .

Two adjacent fixes here are genuine (not ingestion-specific):

  • mitre_attack_id moved to top-level — a Splunk security_content schema change
    that broke ESCU technique matching (returned zero rules) for every consumer of the
    current repo; corrected the lookup.
  • ESCU aggregation window — many ESCU rules aggregate over time (bucket span=15m); the
    pipeline's per-test search window was widened so a test falls inside a full bucket.

C. Keeping the map current with ATT&CK

  • ATT&CK v19 re-map. Two techniques (T1562.001, T1562.006) had their tests
    promoted/moved to T1685 ("Disable or Modify Tools") in ATT&CK v19. The map was
    re-aligned to the current ID and re-validated by a fresh T1685 run (ASLR, Auditing,
    and Logging config-change tests all fired). Same detections, current taxonomy.

The rest of this document explains each residual finding: what the rule expected, what
actually happened in auditd/Splunk, and why detection did not fire — all verified
end-to-end.

Finding 1 — auditd a0 full-path vs. short-name mismatch

Many Sigma auditd rules match the EXECVE a0 field with an exact short name:

selection:
    type: EXECVE
    a0: 'iptables'
    a1|contains: 'DROP'

But a0 is simply argv[0] — the exact name the program was launched with. When a
tool is run through sudo or by its absolute path (which is how iptables and other
admin tools are normally invoked), a0 holds the full path, not the short name.

Evidence (Splunk, 7-day window):

  • a0="/usr/sbin/iptables" → 252 records
  • a0="iptables" (short name) → 0 records

Because the rule requires an exact match on a0: 'iptables', it never matches
the real records (/usr/sbin/iptables), producing a silent false negative
for these tools.

Assessment: The weakness is in the rule, not in our pipeline. A better rule would use
a0|endswith: 'iptables'. We deliberately do not rewrite a0 inside the pipeline: the
whole question we are answering is "does this rule fire as written?", and cleaning up the
data ourselves would hide the real answer. So we run the rule as-is and report the gap.


Finding 2 — Orphan rules (rule exists, but no Atomic test exercises it)

lnx_auditd_capabilities_discovery.yml (tagged attack.t1083 and
attack.t1548) matches:

selection:
    type: EXECVE
    a0: getcap
    a1: '-r'
    a2: '/'
  • We confirmed the rule itself is correct: running getcap -r / manually
    produced one EXECVE record and the rule matched it in Splunk (verified at
    15s/60s/120s after execution).
  • We confirmed that no Atomic test runs getcap — not under T1083, not under
    T1548, nowhere in the test set.

Conclusion: This getcap Sigma rule is sound but unverifiable with available
tests — no Atomic test runs getcap, so we do not claim it as validated (doing so would
require editing the rule or hand-crafting a command, both of which break the contract).

Update (post-recovery): the technique T1083 is now detected — via a different,
ESCU rule (Linux Auditd File and Directory Discovery) that fired once execve_command
was onboarded (see §B). So T1083 is covered; the observation above narrows to
"this one getcap Sigma rule remains an orphan," not "the technique is uncovered."


Finding 3 — auditd argument-position assumption (a1 vs a2)

Sigma auditd rules often pin a match to a specific positional argument field
(a1, a2, ...). But auditd records command arguments positionally, and the
position depends on the order the test author wrote them — which is not fixed.

Example: lnx_auditd_dd_delete_file.yml (T1485) compiles to this SPL:

type="EXECVE" a0="*dd*" a1 IN ("*if=/dev/null*", "*if=/dev/zero*")

The rule expects if=/dev/zero in a1. But the Atomic test runs:

dd of=/var/log/syslog if=/dev/zero count=... iflag=count_bytes

Because of= comes first, auditd records:

a0="dd"  a1="of=/var/log/syslog"  a2="if=/dev/zero"

Evidence (verified end-to-end):

  • Real pipeline-generated SPL searches a1 IN (...if=/dev/zero...)0 results.
  • Splunk field extraction confirms a1=of=/var/log/syslog, a2=if=/dev/zero.

So if=/dev/zero lands in a2, not a1, and the rule never fires — a silent
false negative
. The command genuinely ran and was logged and forwarded; only the
rule's positional assumption is wrong.

Variant: combined short flags (T1040)

The same brittleness appears with how flags are written. lnx_auditd_network_sniffing.yml
(T1040) requires:

selection_1: { a0: 'tcpdump', a1: '-c', a3|contains: '-i' }

The Atomic test runs tcpdump -c 5 -nnni enp0s3, which auditd records as:

a0="tcpdump"  a1="-c"  a2="5"  a3="-nnni"  a4="enp0s3"

a0/a1 match, but a3|contains: '-i' fails: the test combines the flags into
-nnni, so the substring -i (dash immediately followed by i) never appears.
The rule assumes -i is a separate token; the test bundles it. Verified: tcpdump ran
(listening on enp0s3), the record reached Splunk (a0=tcpdump), but the rule
returned 0.

Note: T1040 first failed for a different reason — the test's default interface
(ens33) does not exist on the VM (enp0s3). That part was a fixable pipeline
gap and is addressed by the optional VM_INTERFACE setting. Even after the interface
is corrected, the flag-format mismatch above still prevents detection.

Variant: arguments split across two commands (T1115)

lnx_auditd_clipboard_collection.yml (T1115) requires all four constraints in a
single EXECVE:

selection: { a0: xclip, a1: [-selection,-sel], a2: [clipboard,clip], a3: -o }

i.e. one invocation xclip -sel clip -o. But the Atomic test runs xclip twice:

history | tail -n 30 | xclip -sel clip   # write to clipboard  (a0=xclip a1=-sel a2=clip, no -o)
xclip -o > history.txt                    # read from clipboard (a0=xclip a1=-o, no -sel/clip)

auditd records two separate EXECVE events:

argc=3  a0=xclip a1=-sel a2=clip      # 3 of 4 constraints; missing a3=-o
argc=2  a0=xclip a1=-o                # only a0 matches

Evidence (verified end-to-end): running the rule SPL against each record
individually returns 0 and 0; combined 0. DEBUG confirms the pipeline really issued
the two separate xclip commands. xclip ran and was logged (not a headless/X11 issue) —
the rule's "all args in one command" assumption simply doesn't hold.

Side note: T1115 also maps to a third clipboard rule whose SPL is Sysmon-shaped
(Image="*xclip*" CommandLine="*-sel*" ...). Those fields don't exist in auditd data,
so that rule can never match on Linux regardless — a separate Windows-rule-on-Linux
mismatch.

Assessment: Same family as Finding 1 — these Sigma auditd rules are too strict about
where an argument sits (a1 vs a2), how a flag is written (-i vs -nnni), and whether
everything lands in one command. A more robust rule would match on the whole EXECVE content
instead of a fixed aN slot. As before, we leave the rule as-is and report the gap rather
than rewriting it to force a match.

Update (post-recovery): two of the three techniques above are now detected — not
by fixing the brittle Sigma rule, but because each also has an ESCU rule that keys on a
whole-command field
rather than a fixed aN, and that field is now onboarded:

  • T1485 — ESCU Linux Auditd Dd File Overwrite matches on proctitle (once
    hex-decoded, §B); the command line contains all needed tokens regardless of order. The
    Sigma a1-vs-a2 gap still stands for the Sigma rule.
  • T1115 — ESCU Linux Auditd Clipboard Data Copy matches on execve_command; each
    xclip invocation now matches on its own. The Sigma "all args in one command" gap still
    stands for the Sigma rule.
  • T1040 — remains uncovered: its rules still depend on the exact -i flag token,
    which the test bundles as -nnni. Genuine residual finding.

A useful takeaway: rules that match on the whole command line (proctitle /
execve_command) are not thrown off by argument order or flag spacing — the very things
that break Sigma rules pinned to a fixed aN slot.


Finding 4 — distro-family assumption (RHEL paths on a Debian-family VM)

Some Sigma rules hard-code file paths that only exist on one Linux distro family.

lnx_auditd_keylogging_with_pam_d.yml (T1056.001) matches:

selection_path_events: { type: PATH, name: ['/etc/pam.d/system-auth', '/etc/pam.d/password-auth'] }
selection_tty_events:  { type: ['TTY', 'USER_TTY'] }
condition: 1 of selection_*

password-auth and system-auth are RHEL-family files (RHEL, CentOS, Fedora,
Rocky, Alma). The lab VM is Debian-family (Ubuntu 24.04), which uses common-auth,
common-password, common-account instead — the RHEL files do not exist.

Evidence (verified directly on the VM):

  • test -f /etc/pam.d/password-authabsent; system-authabsent.
  • Only common-* PAM files exist (ls /etc/pam.d/).
  • Splunk over the run window: 0 records mentioning password-auth; 177 pam.d PATH
    records, all common-*; 0 TTY/USER_TTY records.

The Atomic test itself guards with if sudo test -f /etc/pam.d/password-auth; then ...,
so on Ubuntu it simply does nothing — no file is touched, the rule never fires.

Assessment: This is a real coverage blind spot, not a lab artifact. On Debian-family
hosts (very common in production) this rule silently misses PAM-based keylogging because
it does not include the Debian PAM paths (common-auth, etc.). Static mapping would
count T1056.001 as covered; validation shows it is not covered on this distro family.
Per project policy we report the gap rather than editing the rule.


Finding 5 — rule queries the wrong telemetry source (Sysmon fields for an auditd-native action)

Technique: T1685.004 (Disable or Modify Tools — disable/clear the Linux Audit System).
The two Atomic tests run and succeed:

auditctl -D       # delete all audit rules
auditctl -e 0     # disable auditing

auditd captures both perfectly, in its native CONFIG_CHANGE record:

type=CONFIG_CHANGE ... op=set audit_enabled=0 old=1 auid=1000   # ← from `auditctl -e 0`
type=CONFIG_CHANGE ... op=remove_rule key="mknod_monitoring"     # ← from `auditctl -D`

So the telemetry needed to detect this technique exists and is unambiguous. The problem is
the rules mapped to T1685.004:

  • "Audit Rules Deleted Via Auditctl" keys on Image="*/auditctl" | regex CommandLine="-D"
    — i.e. Sysmon process fields (Image, CommandLine). On an auditd host these are not
    populated, so the rule cannot fire even though the action was fully logged.
  • "Auditd Daemon Abort / Shutdown / Start" key on type=DAEMON_ABORT/END/START — but
    auditctl -D / -e 0 change configuration; they do not stop the daemon, so those
    lifecycle events never appear for this test.

Assessment: Not a "cannot validate" case (the test runs and the action is logged) and
not a missing-data case (the CONFIG_CHANGE event is right there). It is a rule/
telemetry-source mismatch
: the only auditctl-specific rule was written against
Sysmon-for-Linux telemetry, while the native auditd CONFIG_CHANGE event that perfectly
captures "disable/clear auditd" is queried by no mapped rule. Per project policy we
report the gap rather than authoring a new CONFIG_CHANGE-based rule.


Methodology note

These findings rely on a key infrastructure fact established during the
investigation: the lab chain (VM → auditd → Splunk Universal Forwarder → Splunk →
Sigma/ESCU) is sound. This was proven by re-running a previously-detected,
non-EXECVE technique (T1489, Magic SysRq) which detected correctly, and by
confirming EXECVE records forward into Splunk normally. The non-detections above
are therefore content/mapping issues, not lab failures.

Note: when verifying rule behavior manually, a forwarding buffer (≥15s) is
required before searching Splunk; querying immediately returns empty results
even though the event is later indexed.


Outcome summary

Every investigated technique was resolved into one of three buckets:

Detected — via pipeline/lab fixes, not by changing any rule:

  • T1574.006 (Dynamic Linker Hijacking) — fixed by the dependency-staging path bug
    (C source for the shared library was never staged to the VM, so the prereq gcc
    step failed; it now compiles and the rule fires).
  • T1046 (Network Service Scanning) — fixed by removing a conflicting auditd rule.
    Two rules watched the connect syscall with different keys: atomic.rules
    had -S connect -k network and sigma.rules had -S connect -k network_connect_4.
    auditd tags an event with the first matching rule's key only (it stops at the
    first match), and atomic.rules loads first (alphabetical), so every nmap connect
    was written to the log as key=network. The Sigma rule searches for
    key=network_connect_4, which therefore never appeared in the log — not because the
    search missed it, but because auditd never wrote that key. No Sigma rule uses
    key=network, so deleting that line is safe; afterwards connect events carry
    network_connect_4 and the rule fires (Count 11 and 75). This is a VM/auditd-config
    fix, not a code change — it lives in the lab snapshot, so reproducing it requires the
    same auditd rule set.
  • Data-onboarding — auditd telemetry reconciled to the shape the official
    ingestion produces (see §B):
    • execve_command rebuilt → T1083, T1115, T1140, T1552.004, T1548.001
    • proctitle hex-decoded → T1485, T1003.008, T1053.006, T1548.003
    • ESCU mitre_attack_id top-level + aggregation window + stdin execution → T1016
  • Execution / environment: stdin fix (T1016), package pre-install → T1053.002 (at).
  • ATT&CK v19 re-map + re-validation: T1685 (formerly T1562.001 / T1562.006; ASLR +
    Auditing + Logging config-change tests re-fired under the current ID).

Findings (rule/ecosystem gaps, reported not patched):

  • T1040 (Network Sniffing) — Finding 3 variant (combined flags -nnni); no whole-command
    rule to fall back on, so still uncovered.
  • T1056.001 (Input Capture / Keylogging) — Finding 4 (RHEL-only PAM paths).
  • T1686 (Disable/Modify System Firewall) — Finding 1 (a0=/usr/sbin/iptables).
  • T1685.004 (Disable Linux Audit System) — Finding 5 (auditctl rule is Sysmon-shaped; the
    native auditd CONFIG_CHANGE event that captures it is queried by no rule).

T1083, T1115, T1485 previously appeared here as findings; the techniques are now
detected via their ESCU counterparts (see the "Update" notes under Findings 2 and 3). The
brittle Sigma rules that first exposed them remain valid findings, but the techniques are
covered.

Could not be validated — the test cannot be exercised on this lab, each for a concrete
reason (not a rule gap):

  • T1547.006 (insmod), T1055.009 (Proc Memory), T1014 (Rootkit) — these tests compile
    native code (or a kernel module) on the target
    before running: a C src/ directory
    built with make (output into a sibling bin/ the Makefile assumes exists), and for
    T1014/T1547.006 an insmod of a freshly built module into the running kernel. Validating
    them needs a full build toolchain (compiler, kernel headers, the bin/ layout,
    module-loading) — beyond the auditd-telemetry validation this lab performs, so out of
    scope for this PR. (A staging fix — recursively uploading the src/ dir — was prototyped
    and confirmed necessary-but-not-sufficient; reverted to keep the pipeline minimal.)
  • T1048.003 (Exfiltration) — test/rule mismatch. Its Linux tests are manual executor
    (no command) or run python http.server, whose only rule is Sysmon-shaped
    (Image/CommandLine); the auditd rule looks for wget --post-file, which no test runs.
  • T1569.002 (Service Execution) — test/rule mismatch. The only Linux auditd rule matches
    systemctl … start/enable, but the only test runs psexec.py (Impacket SMB to
    127.0.0.1), which never issues systemctl start and has no SMB service to reach.
  • T1685.006 (Clear System Logs) — test/rule mismatch (procedure). All 9 tests clear logs
    by touching files (rm -rf, truncate, unlink, cat /dev/null >), while the only
    auditd rule matches a different procedure: SYSCALL=syslog a0 IN (4,5,6) = dmesg -c-style
    clearing of the kernel ring buffer. No test runs dmesg; the file-deletion rules are
    CIM. Independent ecosystems picked non-overlapping procedures of the same technique.
  • T1098.004 (SSH Authorized Keys) — CIM-only rules + wrong-file auditd rule. The
    authorized-keys rules are all Endpoint/CIM (not populated here); the one auditd rule
    watches the sshd config file, whereas the test modifies ~/.ssh/authorized_keys
    (a different file — its access was logged: 3 PATH events).
  • T1095 (Non-Application Layer Protocol) — manual test + CIM rules. Its single Linux test
    (ICMP Reverse Shell) uses the manual executor and needs a second machine (C2) plus
    downloaded binaries; the mapped rules are Network/CIM (not populated).
  • T1611 (Escape to Host) — no Linux-labelled test. Its tests are labelled
    supported_platforms: containers, not linux, so the pipeline's linux filter never runs
    them; they also need a Docker host with privileged containers, which is outside this VM lab.
    Its one Linux-tagged rule (Docker Root Directory Mount) is an Endpoint/CIM rule (not
    populated here).
  • T1562.004 / T1070.002 — renumbered in ATT&CK v19; Atomic Red Team moved their tests to
    the new IDs and removed the old folders, so the pipeline finds no Linux test under the
    old IDs (they only appeared via a stale ID reference in the coarse filter).

Deployment notes (reproducing the onboarding fixes)

Two of the fixes in §B (execve_command, proctitle) are Splunk-side onboarding config,
not pipeline code, so they must be present in any Splunk instance that ingests auditd as
raw audit.log / sourcetype=linux_audit (as this lab does) rather than through the
official Splunk_TA_nix scripted input (bin/rlog.shausearch -i, which already
decodes proctitle and joins the command line, landing as sourcetype=auditd). The config
below reproduces that transform for sourcetype=linux_audit so the unmodified ESCU rules
fire. Both were applied via the Splunk REST API and shared globally; the equivalent
props.conf / transforms.conf is:

# props.conf — [linux_audit]
[linux_audit]
# execve_command: join the EXECVE args into one command line (strip aN= prefixes + quotes)
EXTRACT-execve_command = type=EXECVE.*a0=(?<execve_command>.*)$
EVAL-execve_command    = replace(replace(execve_command, "a\d+=", ""), "\"", "")
# proctitle: decode the hex-encoded proctitle into readable text
REPORT-auditd_proctitle_pairs = auditd_proctitle_pairs
EVAL-proctitle = if(isnull(pp), null(), mvjoin(mvmap(pp, if(pp="00"," ", printf("%c", tonumber(pp,16)))), ""))

# transforms.conf — split the proctitle hex into 2-char byte pairs (pp, multivalue)
[auditd_proctitle_pairs]
REGEX  = (?:proctitle=|\G)([0-9A-Fa-f]{2})
FORMAT = pp::$1
MV_ADD = true

(Splunk eval has no native hex→ASCII, but printf("%c", n) acts as chr(), which is what
makes the pure-SPL proctitle decode possible.)

CIM / Endpoint datamodel (out of scope). Several ESCU rules (and the authorized-keys
rules under T1098.004, plus T1027, T1036.004, T1037.004, T1070.004, T1572, T1611) query
datamodel=Endpoint.* via tstats. That datamodel is not populated here. CIM is
source-agnostic (it can be fed from auditd), but doing so is a full data-engineering effort
— mapping auditd's split SYSCALL/EXECVE/PATH records into single CIM process/file events,
including parent_process, which auditd does not provide natively — i.e. re-implementing what
Sysmon-for-Linux gives for free. Left out of scope for this auditd-focused PR.

Murat-Oruntak and others added 3 commits March 22, 2026 17:03
- Align map to ATT&CK v19 (T1562.001/.006 -> T1685), re-validated
- Fix ESCU rule links for Linux (develop branch + forward-slash paths)
- ESCU mitre_attack_id top-level lookup for schema change (Linux-guarded)
- stdin execution for multiline/quoted bash commands
- Linux coverage stats count Sigma OR ESCU detections
- Guard platform-suffixed output filenames so Windows keeps original names
- Regenerate dist/ Linux map + MITRE Navigator layers

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant