Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ GitHub Release; `sync-fanout.yml` then opens the Kali sync PR.

## [Unreleased]

### Added

- **Command & Control + Impact corpus (14 new red↔blue pairs, +28 entries).** Fills
the two tactics that had **zero** coverage. **`TA0011` Command & Control** (8 pairs):
HTTPS beacon sleep+jitter, DNS tunneling, domain fronting, mutual-TLS/JA3, ICMP
tunneling, web-service C2 (Telegram/Slack/Gist), DGA rendezvous, and reverse
tunnels (chisel/ligolo) — each attack paired with the network/host detection that
survives its evasion (inter-arrival regularity, Sysmon-22 query shape, SNI/Host
mismatch, JA3 fingerprints, NXDOMAIN entropy). **`TA0040` Impact** (6 pairs):
recovery inhibition (`vssadmin`/`wbadmin`/`bcdedit`), mass file encryption, pre-
encryption service kills, cloud data destruction (CloudTrail delete burst),
cryptojacking (Stratum), and account access removal (4724/4725/4726). Corpus-only
(no flat-view markers); every new entry carries a valid, non-deprecated ATT&CK
technique ID.

## [v2.2.0] - 2026-07-09

### Added
Expand Down
27 changes: 27 additions & 0 deletions entries/blue/account-removal-4725.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
id: account-removal-4725
title: Detect account access removal (admin disable/delete/reset + group removal)
detection: splunk-spl
event_ids: [4724, 4725, 4726, 4729, 4733]
attack:
tactic: TA0040
techniques: [T1531]
source: Windows account-management auditing (4724/4725/4726)
pair: account-lockout-defenders
---

Locking defenders out leaves a clean audit trail: 4724 (password reset attempt),
4725 (account disabled), 4726 (account deleted), and 4733/4729 (removal from a
privileged group). Individually these are ordinary help-desk actions; the impact
pattern is a *burst* targeting privileged or break-glass accounts, by an actor who
doesn't normally administer identity, close in time to other impact signals. Alert
on multiple such events against admin accounts in a short window, and protect
break-glass accounts with alerting on any change to them at all.

```spl
index=wineventlog EventCode IN (4724,4725,4726,4729,4733)
| bucket _time span=10m
| stats count, dc(Target_Account_Name) as targets, values(Target_Account_Name) as who by _time, Subject_Account_Name, EventCode
| where targets>3
| sort - count
```
27 changes: 27 additions & 0 deletions entries/blue/cloud-destroy-cloudtrail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
id: cloud-destroy-cloudtrail
title: Detect cloud data destruction (CloudTrail delete burst)
detection: splunk-cloudtrail
event_ids: []
attack:
tactic: TA0040
techniques: [T1485]
source: CloudTrail management events; data-destruction analytics
pair: cloud-snapshot-destroy
---

Destruction is a rare, high-impact verb set on the control plane: `DeleteSnapshot`,
`DeleteDBClusterSnapshot`, `DeleteBucket` / `DeleteObject*` on a versioned bucket,
`DeleteTable`. A single delete may be housekeeping; a *burst* of them across storage
services from one `userIdentity` in a short window — especially from a new IP/role
or with `errorCode` mixed in as it probes — is the pattern. Alert on the aggregate,
and defend structurally with S3 Object Lock, MFA-delete, and cross-account backup
copies the compromised principal can't reach.

```spl
index=aws sourcetype=aws:cloudtrail (eventName IN ("DeleteSnapshot","DeleteDBClusterSnapshot","DeleteDBSnapshot","DeleteBucket","DeleteObject","DeleteObjects","DeleteTable"))
| bucket _time span=5m
| stats count, dc(eventName) as verbs, values(eventName) as which by _time, userIdentity.arn, sourceIPAddress
| where count>10
| sort - count
```
28 changes: 28 additions & 0 deletions entries/blue/cryptomine-pool-detect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
id: cryptomine-pool-detect
title: Detect cryptojacking (Stratum pool connections + CPU peg)
detection: splunk-spl
event_ids: []
attack:
tactic: TA0040
techniques: [T1496]
source: Zeek conn.log / EDR process telemetry; Stratum pool indicators
pair: resource-hijack-xmrig
---

Two independent tells converge: a process pegged near 100% CPU for a sustained
period, and an outbound connection speaking Stratum to a mining pool — often on
3333/5555/7777/14444 or to a known pool domain, sometimes wrapped in TLS. Alert on
outbound connections to pool-domain/port indicators from server or workstation
fleets, and corroborate with EDR process telemetry for the CPU peg and miner
command-line flags (`--cpu-max-threads-hint`, `stratum+tcp://`). Either signal alone
is worth a look; together they're high fidelity. Keep the base query port-based
(`conn.log` has no hostname field), then enrich the survivors with `dns.log`/`ssl.log`
to resolve `id.resp_h` and match known pool domains (`*xmr*`, `*minexmr*`, `*nanopool*`).

```spl
index=zeek sourcetype=zeek:conn id.resp_p IN (3333,5555,7777,14444)
| stats count, sum(orig_bytes) as bytes_out, max(duration) as longest by id.orig_h, id.resp_h, id.resp_p
| where count>5
| sort - longest
```
29 changes: 29 additions & 0 deletions entries/blue/dga-nxdomain-entropy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
id: dga-nxdomain-entropy
title: Detect DGA beacons (NXDOMAIN burst + label entropy)
detection: splunk-spl
event_ids: [22]
attack:
tactic: TA0011
techniques: [T1568.002]
source: Sysmon DnsQuery (Event ID 22) + DNS resolver NXDOMAIN logs
pair: dga-c2-domains
---

A DGA host generates far more domains than register, so it leaves a trail of failed
resolutions to names no human would type: high character randomness, no dictionary
words, unusual TLD spread. Alert when a single `Image`/host produces a burst of
distinct NXDOMAIN (or QueryStatus≠0) results whose labels are long and vowel-poor
in a short window — a self-contained proxy for the entropy a `dns_entropy` macro
would score if you have one. Sysmon 22 gives the process; the resolver's NXDOMAIN
log gives the failures — either alone works, together they're high fidelity.
Baseline out telemetry/antivirus clients that probe many names.

```spl
index=sysmon EventCode=22 QueryStatus!=0
| eval label=mvindex(split(QueryName,"."),0), llen=len(label)
| eval vowels=llen-len(replace(lower(label),"[aeiou]","")), vowel_ratio=vowels/llen
| stats count as nxdomains, dc(QueryName) as uniq, avg(llen) as avg_len, avg(vowel_ratio) as avg_vowel by Image, host
| where nxdomains>50 AND avg_len>12 AND avg_vowel<0.3
| sort - nxdomains
```
27 changes: 27 additions & 0 deletions entries/blue/dns-tunnel-sysmon-22.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
id: dns-tunnel-sysmon-22
title: Detect DNS tunneling (Sysmon 22 query volume + label length)
detection: splunk-spl
event_ids: [22]
attack:
tactic: TA0011
techniques: [T1071.004]
source: Sysmon DnsQuery (Event ID 22) telemetry
pair: dns-tunnel-c2
---

Tunneled DNS looks nothing like resolution: one process emits hundreds of unique
queries under a single registrable domain, with long high-entropy leftmost labels
(the encoded payload) and TXT/NULL answers. Sysmon Event ID 22 attributes each
query to its `Image`, so group by process and parent domain and alert when a single
non-browser image drives a high count of distinct, long subdomains to one zone.
Baseline out CDNs and telemetry endpoints that legitimately fan out subdomains.

```spl
index=sysmon EventCode=22
| eval label=mvindex(split(QueryName,"."),0), qlen=len(label)
| rex field=QueryName "(?<parent_domain>[^.]+\.[^.]+)$"
| stats count, dc(QueryName) as uniq, avg(qlen) as avg_label_len by Image, host, parent_domain
| where uniq>100 AND avg_label_len>25
| sort - uniq
```
27 changes: 27 additions & 0 deletions entries/blue/domain-fronting-sni-mismatch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
id: domain-fronting-sni-mismatch
title: Detect domain fronting (TLS SNI vs HTTP Host mismatch)
detection: splunk-spl
event_ids: []
attack:
tactic: TA0011
techniques: [T1090.004]
source: TLS SNI / HTTP Host correlation (Zeek ssl.log + http.log)
pair: domain-fronting-cdn
---

Fronting only works because the SNI and the inner Host differ; where you can see
both, the mismatch is the invariant. With a TLS-inspecting proxy (or Zeek pairing
`ssl.log` SNI to the decrypted `http.log` Host), alert when the SNI's registrable
domain differs from the Host header for the same connection — the CDN case that
isn't a legitimate multi-tenant vhost. Without decryption, fall back to
non-browser processes making TLS to CDN edges with rare/young inner destinations.

```spl
index=zeek (sourcetype=zeek:ssl OR sourcetype=zeek:http)
| transaction uid maxspan=30s
| eval sni_dom=replace(server_name,"^.*\.([^.]+\.[^.]+)$","\1")
| eval host_dom=replace(host,"^.*\.([^.]+\.[^.]+)$","\1")
| where isnotnull(server_name) AND isnotnull(host) AND sni_dom!=host_dom
| table _time, id.orig_h, server_name, host, uri
```
31 changes: 31 additions & 0 deletions entries/blue/https-beacon-jitter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
id: https-beacon-jitter
title: Detect HTTPS beaconing by inter-arrival regularity (proxy logs)
detection: splunk-spl
event_ids: []
attack:
tactic: TA0011
techniques: [T1071.001]
source: MITRE ATT&CK TA0011; beacon inter-arrival analysis
pair: https-beacon-sliver
---

Jitter randomizes each interval but not the *distribution*: a beacon's callbacks
still cluster around a mean period with a bounded spread, while human browsing does
not. Compute the deltas between successive connections per `src→dest`, and alert
when a destination is contacted many times with a low coefficient of variation
(stdev small relative to the mean) — especially to a rare/uncategorized host from a
single stable user-agent. Enrich with domain age + category to cut noise; a fixed
User-Agent talking to a young, uncategorized domain on a near-metronomic cadence is
the signal jitter can't hide.

```spl
index=proxy action=allowed
| sort 0 src_ip, dest, _time
| streamstats current=f last(_time) as prev by src_ip, dest
| eval delta=_time-prev
| stats count, avg(delta) as period, stdev(delta) as spread by src_ip, dest, http_user_agent
| eval cv=spread/period
| where count>20 AND period>30 AND cv<0.35
| sort cv
```
25 changes: 25 additions & 0 deletions entries/blue/icmp-c2-volume.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
id: icmp-c2-volume
title: Detect ICMP tunneling (echo volume + payload size)
detection: splunk-spl
event_ids: []
attack:
tactic: TA0011
techniques: [T1095]
source: Zeek conn.log / NetFlow ICMP volumetrics
pair: icmp-tunnel-c2
---

Legitimate ICMP echo is sparse and small — fixed-size OS pings, a few per host.
Tunneled ICMP is the opposite: sustained echo request/reply to a single external
host, high packet counts, and large or variable payload bytes carrying the encoded
session. From Zeek `conn.log` (or NetFlow) filtered to ICMP, sum bytes and packets
per internal→external pair over a window and alert on the outliers, especially to a
rare destination. Baseline out monitoring pollers that legitimately ping a lot.

```spl
index=zeek sourcetype=zeek:conn proto=icmp
| stats sum(orig_bytes) as bytes_out, count as pkts, avg(orig_bytes) as avg_payload by id.orig_h, id.resp_h
| where pkts>500 AND avg_payload>64
| sort - bytes_out
```
27 changes: 27 additions & 0 deletions entries/blue/inhibit-recovery-4688.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
id: inhibit-recovery-4688
title: Detect recovery inhibition (4688 vssadmin / wbadmin / bcdedit)
detection: splunk-spl
event_ids: [4688]
attack:
tactic: TA0040
techniques: [T1490]
source: TrustedSec "Actionable Purple Teaming" (BH USA 2023)
pair: inhibit-recovery-vssadmin
---

Shadow-copy and backup deletion runs through a small set of signed built-ins with
unmistakable arguments — `vssadmin delete shadows`, `wmic shadowcopy delete`,
`wbadmin delete catalog`, `bcdedit ... recoveryenabled no`. Alert on 4688 process
creation (or Sysmon 1) matching those image+command-line shapes; the delete/disable
verbs are what separate them from legitimate backup administration. This fires
*before* mass encryption, so treat a hit as an in-progress incident, not a report.

```spl
index=main EventCode=4688 (New_Process_Name IN ("*\\vssadmin.exe","*\\wbadmin.exe","*\\bcdedit.exe","*\\wmic.exe"))
| where (like(Process_Command_Line,"%delete shadows%")
OR like(Process_Command_Line,"%shadowcopy delete%")
OR like(Process_Command_Line,"%delete catalog%")
OR like(Process_Command_Line,"%recoveryenabled no%"))
| table _time, host, Account_Name, New_Process_Name, Process_Command_Line
```
28 changes: 28 additions & 0 deletions entries/blue/mass-encrypt-4663.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
id: mass-encrypt-4663
title: Detect mass encryption (4663 file-write burst + note drop)
detection: splunk-spl
event_ids: [4663]
attack:
tactic: TA0040
techniques: [T1486]
source: Windows object-access auditing (Event ID 4663) burst analysis
pair: ransomware-encrypt-files
---

Encryption is a write storm: a single process issuing `WriteData` (4663, with
SACLs enabled) or Sysmon 11 FileCreate against hundreds of files across many
directories in seconds — far above any normal application's file-touch rate. Alert
when one `Process_Name`/host exceeds a high threshold of distinct files written in a
short window, and corroborate with a same-name note file (`readme`, `how_to_decrypt`,
`*.<campaign-ext>`) appearing in many folders. Tune the threshold per environment;
back it with canary files for a low-false-positive trip.

```spl
index=wineventlog EventCode=4663 Accesses="*WriteData*"
| bucket _time span=1m
| rex field=Object_Name "(?<dir>.+)\\[^\\]+$"
| stats dc(Object_Name) as files, dc(dir) as dirs by _time, host, Process_Name
| where files>200
| sort - files
```
27 changes: 27 additions & 0 deletions entries/blue/mtls-c2-ja3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
id: mtls-c2-ja3
title: Detect encrypted C2 by TLS fingerprint (JA3 / JA3S)
detection: splunk-spl
event_ids: []
attack:
tactic: TA0011
techniques: [T1573.002]
source: Zeek ssl.log JA3/JA3S fingerprinting
pair: mtls-c2-sliver
---

Encryption hides the payload, not the handshake. Each implant's TLS ClientHello
(cipher list + extension order) hashes to a stable JA3, and the C2 server's
ServerHello to a JA3S; frameworks reuse these across builds, so a known-implant
JA3/JA3S pair is a high-fidelity match regardless of destination or sleep. Maintain
a blocklist of framework fingerprints (Zeek + community JA3 sets) and alert on any
hit; then hunt self-signed / very-short-chain certs to a rare destination as the
unknown-implant fallback.

```spl
index=zeek sourcetype=zeek:ssl
| lookup ja3_c2_implants ja3 OUTPUT framework AS ja3_hit
| lookup ja3s_c2_servers ja3s OUTPUT framework AS ja3s_hit
| where isnotnull(ja3_hit) OR isnotnull(ja3s_hit)
| table _time, id.orig_h, id.resp_h, server_name, ja3, ja3s, ja3_hit, ja3s_hit
```
26 changes: 26 additions & 0 deletions entries/blue/reverse-tunnel-detect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
id: reverse-tunnel-detect
title: Detect reverse tunnels (long-lived outbound session + JA3)
detection: splunk-spl
event_ids: []
attack:
tactic: TA0011
techniques: [T1572]
source: Zeek conn.log duration/volume + JA3 tunneling fingerprints
pair: reverse-tunnel-chisel
---

A tunnel collapses many sessions into one, so it shows up as a single outbound
connection that lives far longer and moves far more bytes — bidirectionally — than
normal client traffic, often to a raw IP or a young domain on an odd port. From Zeek
`conn.log`, alert on long-duration external connections with high byte counts in
both directions from a server/workstation that shouldn't hold persistent outbound
sessions; enrich with chisel/ligolo JA3 fingerprints and destination reputation.

```spl
index=zeek sourcetype=zeek:conn
| where NOT (cidrmatch("10.0.0.0/8",id.resp_h) OR cidrmatch("172.16.0.0/12",id.resp_h) OR cidrmatch("192.168.0.0/16",id.resp_h))
| where duration>1800 AND orig_bytes>1000000 AND resp_bytes>1000000
| table _time, id.orig_h, id.resp_h, id.resp_p, duration, orig_bytes, resp_bytes, ja3
| sort - duration
```
28 changes: 28 additions & 0 deletions entries/blue/service-stop-7036.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
id: service-stop-7036
title: Detect pre-encryption service kills (7036 stop burst)
detection: splunk-spl
event_ids: [7036]
attack:
tactic: TA0040
techniques: [T1489]
source: Service Control Manager stop events (Event ID 7036)
pair: service-stop-preransom
---

One service stopping is routine; a *burst* of business-critical services entering
the stopped state within seconds is not. Correlate SCM Event ID 7036 ("entered the
stopped state") across many distinct services per host in a short window; the query
below is 7036-only. Corroborate out-of-band with process-creation (4688) for the
`net.exe stop` / `sc.exe stop` / `taskkill.exe` command lines against DB/mail/backup/AV
names. Weight the alert when the stopped set includes backup or endpoint-protection
services — the combination immediately preceding, or interleaved with, a file-write
storm is an active-encryption indicator.

```spl
index=wineventlog EventCode=7036 "entered the stopped state"
| bucket _time span=2m
| stats dc(Service_Name) as services_stopped, values(Service_Name) as which by _time, host
| where services_stopped>5
| sort - services_stopped
```
Loading