Research report — campaign detection-loop-2026-10-11
_Narrative report, drafted 2026-10-13 from ledger item led-b9bd61b99b62, the run bundles, lab/config/wazuh/rules/local_rules.xml and lab/config/wazuh/sigma-mapping.md. Proposal for human review; nothing here is deployed by this document. Not a deterministic projection: hand-written, every claim cites a ledger event, run id or rule._
At a glance
- The full benign Windows emulation plan was measured on 2026-10-11. Five entries graded partially-detected. Triage showed they were not five rule gaps: they sorted into three buckets (A: telemetry present but no rule matched; B: a latent rule-shadowing defect; C: not SIEM gaps at all).
- Both bucket A gaps are closed and proven firing:
lateral-movement-via-remote-services(T1021.001 Disable NLA) via new rule 100213 (run-2026-10-12-002, graded detected; re-confirmed in run-2026-10-14-001), andcookie-theft(T1539, Chrome v127+ CDP theft) via new rule 100203 (fired on a realmsedge --remote-debugging-portlaunch). - Bucket B is only load/reachability-proven. Rules 100220/100222 were re-anchored and FP-hardened, but neither has been seen to fire. Bucket C is parked, not solved. One rule (100212) is known not to fire in-lab and needs a field fix.
- Ledger status of
led-b9bd61b99b62is stillin-progress(gate G2, approval granted for deploys so far); this report does not close it.
What was measured
The measurement was the full benign Windows plan run on 2026-10-11 (lab source lab-2026-10-11). Five runs graded partially-detected and were fed into the ledger item:
| Run | Plan entry / attack | Initial reading |
|---|---|---|
| run-2026-10-11-001 | t1021.001-atomic, lateral-movement-via-remote-services | partial |
| run-2026-10-11-002 | t1539-atomic, cookie-theft | partial |
| run-2026-10-11-003 | pass-the-hash (t1550.002) | partial |
| run-2026-10-11-004 | pass-the-ticket (t1550.003) | partial |
| run-2026-10-11-006 | modbus-dnp3-s7-protocol-abuse (t1571) | partial |
The engineer then read each bundle against the exact Atomic command rather than the technique label, which is what exposed the three buckets.
Three-bucket triage
- A. Telemetry present, no rule matched (real detection gaps): run-001 and run-002.
- B. Latent shadow (rules exist but cannot be reached): 100220/100222 chained
if_sid 60000, sibling of the bundled Security-channel catch-all 60001. - C. Not SIEM gaps (the lab cannot generate the telemetry, or the probe was invalid): t1550.002/.003 and t1571.
Bucket A, gap 1: lateral-movement-via-remote-services (T1021.001 "Disable NLA")
- Finding. The plan had predicted the tscon rule 100210 for this entry. That was a misprediction: the Atomic is "Disable NLA", a registry edit (
reg add ...\RDP-Tcp /v UserAuthentication /d 0), not atsconsession hijack. - A wrong premise, corrected. The first fix (rule 100212, Sysmon EID13 RegistryValueSet) assumed EID13 was present in the run-001 telemetry. That was unverified and wrong. The bundle for run-2026-10-12-001 (graded partially-detected, expected 100210/100212, no match) shows the reg edit only as an EID1 process-create command line, caught generically by bundled rules 92041/92052. No EID13 appears in the window (event IDs captured: 9100, 4104, 4624, 4672, 4634, 11, 1).
- Fix. Rule 100213 (level 10,
if_group sysmon_event1, EID1 command line settingUserAuthenticationorfDenyTSConnectionsto 0 under a Terminal Server path). The human chose to ship the EID1 arm now and keep 100212 as the evasion-resistant arm. - Proof. After deploy (21/21 rules live, single file), the re-detonation run-2026-10-12-002 (
t1021.001-atomic) graded detected, matched 100213, 3 s detection latency, with the 100100 run marker present. run-2026-10-14-001, after EID13 auditing was enabled on win-victim, again graded detected via 100213 (4 s latency). - 100212 did not fire, even with EID13 on. Follow-up B (enable Sysmon EID13 for the Terminal Server keys on win-victim,
lab/provision/apply-sysmon-rdp-registry.ps1) is half-closed: telemetry was confirmed live, but in run-2026-10-14-001 the alert set carried no eventID 13 and the reg set surfaced only as EID1 (92041 + 100213). The ledger reads this as a rule-field mismatch in 100212, not a telemetry gap (its value-0 DWORD regex likely does not fit the real event rendering). This is inferred, not confirmed: the indexer holds alerts only (logall_jsonoff), so the raw EID13 was not inspected.
Bucket A, gap 2: cookie-theft (T1539, Chrome v127+ remote-debugging theft)
- Finding. The v127+ app-bound-encryption bypass drives
Network.getAllCookiesover a DevTools socket launched with--remote-debugging-port. The existing rules 100200-100202 key on file-copy / command-line / memory-read of the cookie store, so they cannot see it. - Fix. Rule 100203 (level 10,
if_group sysmon_event1): Chromium image (chrome|msedge|brave|chrome_proxy) plus--remote-debugging-(port|pipe)in the command line, with the parent image negated for browsers andexplorer, so a script/LOLBin launch fires but a user shortcut does not. - Proof. Chrome is not installed on win-victim, so the proof used the Edge variant (the image regex already covers
msedge). A realmsedge --remote-debugging-portlaunch emitted EID1 and 100203 fired to wazuh-alerts at 2026-10-12 (agent 003), verified by the engineer via the read-only indexer. Caveat: this proof is a ledger-recorded alert, not a graded run bundle inlab/results/; the ledger names it "run-edge-proof".
Bucket B: 100220/100222 shadowed behind the bundled Security base
- Diagnosis (analyst, read-only Manager API plus committed bundles, Wazuh v4.14.7). Across the Windows runs, 20 each of 4624/4672/4634 alerts were all bundled (4624 -> 67022, 4672 -> 67028, 4634 -> 67023) and none were 100220/100222. Live chain: 60000 -> 60001 (level 0,
^Security$, matches every Security event) -> 60103 -> 60106 ("Windows Logon Success", eventID 528|540|673|4624|4769) -> children including 67022 (logonType not 3|5). 100220 and 100222 both chainedif_sid 60000, siblings of 60001, so analysisd descended through 60001 and never evaluated them. Same defect class as 100100 and 100300/100301. 100221 (if_sid 100220) and 100240 (if_matched_sid 100220) were unreachable transitively. - Re-anchor (engineer, 2026-10-12): 100220 ->
if_sid 60106,67022(type 3 terminates at 60106; type 9/10 are claimed by 67022 first, so both anchors are needed); 100222 ->if_sid 60106. Levels, field guards, groups and MITRE tags unchanged.build_plan.pyplatform mapping updated; plan regenerated unchanged; lab test suite green (95/95). - Validator verdict (advisory, not a G2 grant): plausible. The live Manager API was unreachable from the validator host, so 60106/67022 were not re-confirmed against the live tree. Top named risk: the bundled once-per-user
if_ftsrule 60119 might pre-empt the new-source logons that 100220/100240 depend on. Secondary: the anchor hard-codes bundled numeric ids, which an upstream renumber would silently break. - FP hardening (engineer, 2026-10-13, draft, not deployed): 100222 negates machine-account service names (
serviceNameending$); 100221 negatesANONYMOUS LOGONand machine-account ($) target users. Two validator suggestions were deliberately not taken: theticketOptions 0x40810000guard (it would narrow 100222 from the broader PtT/RC4-downgrade indicator to Kerberoasting) and a blanket system subject-SID exclusion (S-1-5-18/19/20), which would blind a SYSTEM-context overpass-the-hash. These are field negates only; anchors are unchanged. - Status: load/static-reachability-proven only. No firing proof exists for 100220, 100221 or 100222. No run to date emitted a type 3/9/10 4624 (all lab 4624s were local 67022 logons), and 100222 needs 4769 from a KDC that the isolated non-domain lab does not have. The ledger is explicit that logtest cannot prove eventchannel firing (see Methods note below).
Bucket C: not SIEM gaps
- t1550.002 / t1550.003 (
pass-the-hash,pass-the-ticket): the isolated single-victim non-domain lab cannot generate type-9 / 4769 telemetry, so the partial grades there reflect the lab, not missing rules. Coverage decision is open (see below). - t1571 (
modbus-dnp3-s7-protocol-abuse, rule 100300): the probe connected to itself (loopback), so Sysmon EID3 (established connections only) was never produced. The two later re-detonations, run-2026-10-12-003 and -004 (t1571-custom-windows, graded partially-detected, 0 expected alerts matched), were invalid measurements of 100300: the lin-victim sink was not listening, so no established connect and no EID3. They are parked inlab/results-excluded/and do not count as evidence about 100300 either way (per threat-huntled-edfd59466851).
Public-Sigma-backed vs lab-original (per sigma-mapping.md)
All rules below are translations of public logic; none is claimed as novel detection content.
| Rule | Status | Public source it mirrors |
|---|---|---|
| 100203 | public-backed (adapted) | SigmaHQ Browser Started with Remote Debugging (proc_creation_win_susp_web_browser_debugging); lab adds a parent-image negate |
| 100213 | public-backed (adapted) | SigmaHQ command-line variants of disable-NLA / enable-RDP (proc_creation_win_reg_disable_nla) |
| 100212 | public-backed (adapted) | SigmaHQ registry_set_disable_nla, registry_set_rdp_enabled, RDP Sensitive Settings Changed |
| 100210 | public-backed, 1:1 | SigmaHQ proc_creation_win_rdp_hijack_tscon |
| 100221 | public-backed | SigmaHQ win_security_susp_logon_type_9_seclogo; lab FP negates are its own |
| 100222 | public-backed | SigmaHQ win_security_susp_kerberos_rc4; machine-account exclusion matches SigmaHQ/TrustedSec; lab omits the 0x40810000 guard on purpose |
| 100220 | lab-original infrastructure | no Sigma; a feeder providing a stable if_matched_sid anchor for 100240 |
| 100240 | lab-original | composite E1 session-usage correlation; no single public Sigma |
Note that the validator's prior-art check found 100221 and 100222 are well-covered public idioms, so they add little beyond translation. The re-anchoring work (bucket B) and the telemetry-vs-rule diagnosis are the lab's contribution here, not the rule logic. One divergence: 100212/100213 sigma-mapping text still says 100212 is unreachable because "win-victim Sysmon does not audit RegistryValueSet" (measured 2026-10-12); this predates the 2026-10-14 EID13 enablement, so that row is stale and the accurate current reading is "telemetry on, rule field mismatch".
Proven firing vs load-proven only
| Rule | State | Evidence |
|---|---|---|
| 100213 | proven firing | run-2026-10-12-002 and run-2026-10-14-001, both graded detected |
| 100203 | proven firing | real msedge --remote-debugging-port launch, alert 2026-10-12 (ledger; no graded bundle) |
| 100212 | loaded, known not firing in-lab | EID13 telemetry on, no EID13 alert in run-2026-10-14-001; field fix pending |
| 100220 | re-anchored, load/static only | no type 3/9/10 4624 yet; 60119 if_fts pre-emption unchecked |
| 100221 | re-anchored transitively + FP-hardened, load/static only | needs 100220 firing on a type-9 4624 |
| 100222 | re-anchored + FP-hardened, load/static only | needs a KDC/4769 the lab lacks |
| 100210 | pre-existing, not exercised by this Atomic | mispredicted for it |
Methods note: wazuh-logtest cannot prove any Sysmon/eventchannel/Security rule fires. A pasted {"win":...} event decodes with the json decoder into the syslog tree (rule 1), never reaching rule 60000. wazuh-analysisd -t proves load only. Firing needs a real agent -> manager -> indexer round-trip.
Operator / still open
1. 100212 field fix (G2). Briefly enable logall_json, write one Disable-NLA reg set, read the raw EID13 from wazuh-archives, correct the targetObject/details match to the actual rendering, deploy via sed -i on the single local_rules.xml (never a second rules file), then re-detonate to prove firing. Turn logall_json off afterwards. Until then 100213 covers Disable-NLA, so this is an evasion-resistance refinement, not an open coverage gap. Note the validator's warning that the patch self-verify cannot show 100212 fires (it writes the existing value back, not 0). 2. 100220 fire-test (G2). Re-confirm 60106/67022 on the live manager (ids can drift), deploy the re-anchor, check the 60119 if_fts field set, then run a benign type-3 logon (e.g. loopback SMB) and a type-9 logon (runas /netonly) round-trip. 100221 is tested by the type-9 case. 3. 100222 fire-test (G2). Needs domain/KDC 4769 telemetry. It stays a load-proven proposal and must not be called a validated detection until then. Deploy of the 2026-10-13 FP-hardening is also still G2 and not applied. 4. Do not call the E1 chain covered until 100220/100240 are seen firing. 5. Bucket C decisions. (a) Re-detonate t1571 (t1571-custom-windows) against a listening lin-victim sink so an established connection produces EID3, then regrade 100300. (b) Decide whether to extend the lab with domain/KDC telemetry to measure t1550.002/.003, or accept those entries as out-of-scope for the isolated lab. 6. Residual FP exposure. 100203, 100212 and 100213 have no admin allow-list. Legitimate RDP-enable via System Properties will set fDenyTSConnections=0. 100213's command-line regex is order-dependent and misses reg import of a .reg file. runas /netonly admin activity remains a measured FP for 100221. 7. Housekeeping. Refresh the stale 100212 row in sigma-mapping.md (see above); close ledger item led-b9bd61b99b62 and record this report as a ledger report item with the PR as artifact once reviewed.
Evidence index
- Ledger:
analysis/ledger/led-b9bd61b99b62.yaml - Rules:
lab/config/wazuh/rules/local_rules.xml(100203, 100210, 100212, 100213, 100220, 100221, 100222) - Mapping:
lab/config/wazuh/sigma-mapping.md - Bundles:
lab/results/run-2026-10-12-001/run.json(partial),lab/results/run-2026-10-12-002/run.json(detected, 100213),lab/results/run-2026-10-14-001/run.json(detected, 100213); excluded:lab/results-excluded/run-2026-10-12-003/run.json,lab/results-excluded/run-2026-10-12-004/run.json - Attacks cited:
lateral-movement-via-remote-services,cookie-theft,pass-the-hash,pass-the-ticket,modbus-dnp3-s7-protocol-abuse