Running a Full Purple Team Exercise End to End
This is Chapter 4 of the Purple Team notebook, and it puts the whole thing together. Chapter 1 defined purple teaming as a transparent, collaborative loop; Chapter 2 built the offensive engine (Atomic Red Team and CALDERA); Chapter 3 built the defensive engine (detection validation, tuning, coverage). Each of those is a capability. This chapter is the event that uses all of them — a planned, scoped, time-boxed exercise that starts with an objective and ends with a prioritised, owned program of remediation work with retest dates on the calendar.
The discipline of the earlier chapters carries here in full. A purple exercise executes real adversary behaviour — credential dumping, lateral movement, exfiltration — and every technique in it requires explicit, written authorization against systems the organisation owns or is contractually permitted to test. The difference from a covert red team is transparency, not permission: everyone knows the exercise is running, but it is still real attacker tradecraft against real infrastructure, and running any of it outside an authorized window is indistinguishable from an intrusion.
Why This Matters
A purple team capability that never runs a structured exercise degenerates into ad-hoc "hey, can you test this rule?" requests — useful, but unmeasured and unaccountable. The structured exercise is what converts scattered testing into a repeatable instrument you can point at a question ("can we detect the actor most likely to target us?") and get a defensible, trended answer.
The exercise is also where the two halves of the previous chapters actually meet in the same room, in real time. Emulation without a defender watching is just red teaming; detection engineering without live technique execution is theory. The purple exercise is the deliberate collision of the two, with a feedback loop measured in minutes — the red operator fires a technique, the blue analyst checks whether it alerted, and if it did not, they diagnose why and often fix it before the exercise ends. That same-session fix is the signature of a mature purple program and the reason the format exists.
Done well, the output is not a report that gets filed. It is a set of tuned detections deployed during the exercise, a ranked list of gaps each with a named owner and a retest date, and a set of numbers — coverage, MTTD, close rate — that you can compare against the last exercise to prove the program is improving. This chapter is about producing exactly that.
flowchart LR
A[Objective +<br/>threat intel] --> B[Plan:<br/>technique list, RoE]
B --> C[Prepare:<br/>environment, safety, roles]
C --> D[Execute:<br/>fire - observe - collaborate]
D --> E[Score:<br/>outcome, MTTD, robustness]
E --> F[Report:<br/>owned gaps + retest dates]
F --> G[Remediate +<br/>deploy detections]
G --> H[Retest]
H -.->|trend across exercises| A
style D fill:#553c9a,color:#fff
style F fill:#22543d,color:#fff
Part 1: The Exercise Operating Model
A purple exercise is a small, structured operation with defined roles. Even when one person wears several hats (Part 12), the roles remain distinct because they represent distinct responsibilities, and blurring them is how exercises lose rigour.
1.1 Cells and roles
flowchart TD
ED[Exercise Director<br/>owns objective, scope, go/no-go, abort]
subgraph White["White Cell - control"]
ED
SCRIBE[Scribe<br/>timeline, evidence, decisions]
end
subgraph Red["Red Cell - offense"]
RL[Red Lead]
RO[Operators<br/>fire techniques]
end
subgraph Blue["Blue Cell - defense"]
BL[Blue Lead]
AN[Analysts / detection engineers]
end
ED --> RL
ED --> BL
RL <-->|transparent, real-time| BL
SCRIBE -.captures.-> Red
SCRIBE -.captures.-> Blue
style White fill:#2c5282,color:#fff
| Role | Owns | Key responsibility during the run |
|---|---|---|
| Exercise Director | Objective, scope, RoE, go/no-go, the abort call | Keeps the exercise on-objective; makes the stop decision if blast radius is exceeded |
| White Cell | Neutral control and deconfliction | Confirms each technique is in-scope before it fires; holds the deconfliction channel to real SOC |
| Scribe | The record | Timestamps every technique, captures evidence, logs every decision and collaboration point |
| Red Lead / Operators | Technique execution | Fire techniques per the plan, at agreed pace, capturing exact commands and output |
| Blue Lead / Analysts | Detection and response | Watch telemetry, record the outcome per technique, tune and re-test in real time |
The role that gets skipped and should never be is the Scribe. Without a dedicated record-keeper, the exercise generates findings that nobody can reconstruct afterward — "did T1055 alert? I think so?" is not a finding. The scribe's timeline is the raw material of the after-action report, and capturing it in real time is far cheaper than reconstructing it from memory and chat logs later.
1.2 The transparency principle
The defining feature is the live, two-way channel between red and blue. This is not a red team that blue is trying to catch; it is two teams jointly characterising the defensive posture. Concretely, during the run:
- Red announces each technique before or as it fires ("firing T1003.001 via comsvcs, now").
- Blue reports what they see, in real time ("EID 10 present, no alert — logged-only").
- When there is a gap, both sides diagnose it together and, where possible, fix it in-session.
That openness is what makes the feedback loop minutes long instead of weeks. It also changes the emotional dynamic: there is no "gotcha," no defender embarrassment, no red-team victory lap — the shared goal is more covered techniques at the end of the day than at the start.
Part 2: Choosing the Exercise Format
Not every purple exercise looks the same. Three formats sit on a spectrum from cheapest/least-realistic to most-expensive/most-realistic, and choosing the wrong one wastes the day.
| Format | What happens | Best for | Cost |
|---|---|---|---|
| Tabletop | Walk through an attack scenario verbally; "if the actor did X, would we see it?" | Testing process, roles, and decision-making; onboarding; when tooling is not ready | Low |
| Technique-driven | Fire an ordered list of individual ATT&CK techniques (Atomic-style), validate each | Building and measuring detection coverage breadth | Medium |
| Adversary-emulation | Emulate a specific named actor's full chain end to end (CALDERA/CTID plan) | Testing whether detections hold up when techniques are chained under realistic timing | High |
The most common mistake is jumping to full adversary emulation before the technique-driven groundwork exists. If individual techniques are not yet reliably detected, chaining them adds nothing but noise — you cannot attribute a missed detection in a 40-technique chain without first knowing each technique's baseline. Technique-driven exercises build the coverage; emulation exercises stress-test it. Do them in that order.
A mature program cycles through all three: tabletop to validate a new playbook or team, technique-driven to expand and measure coverage, emulation to validate that coverage against the actors that actually matter — then back to technique-driven to close the gaps emulation exposed.
Part 3: Scoping and Rules of Engagement
Scope is what separates a purple exercise from an incident. Before anything fires, the boundaries must be written, signed, and understood by everyone in every cell.
3.1 The Rules of Engagement template
PURPLE TEAM EXERCISE - RULES OF ENGAGEMENT
1. AUTHORIZATION
Authorizing authority: <name, title, signature, date>
Systems in scope: <explicit host list / CIDR / cloud account IDs>
Systems explicitly OUT: <production DBs, safety systems, third-party assets>
Authorization window: <start datetime> to <end datetime>, <timezone>
2. OBJECTIVE
Primary question: e.g. "Can we detect APT29-style credential access
and lateral movement within our corp domain?"
Success criteria: <measurable, e.g. detection outcome recorded for each
of the N planned techniques; >=X% score-3 coverage>
3. TECHNIQUES
Planned technique list: <reference the technique plan, Part 4>
Explicitly forbidden: e.g. destructive actions, real data exfiltration,
DoS, anything touching customer PII, ransomware
encryption even in simulation
4. SAFETY & BLAST RADIUS
Environment: <lab / prod-with-limits>
Snapshot/rollback: <required before start; who owns it>
Data handling: synthetic data only; no real credentials harvested
Blast-radius limits: <e.g. lateral movement max 2 hops; no domain-wide>
5. DECONFLICTION
Real SOC informed?: <yes/no - see Part 3.3>
Deconfliction contact: <name, phone, always-reachable during window>
Exercise marker: <how activity is tagged, e.g. a header, a known
source host, a filename convention>
6. ABORT
Abort authority: Exercise Director AND either lead
Abort triggers: real incident detected, blast radius exceeded,
production impact, safety concern
Abort procedure: <stop firing, announce on channel, snapshot state,
begin cleanup - see Part 5.4>
7. COMMUNICATIONS
Exercise channel: <dedicated, logged>
Reporting: <who gets the after-action report, by when>
Signatures: Exercise Director ___ Red Lead ___ Blue Lead ___ System Owner ___
The two fields most often left vague and most important are "Systems explicitly OUT" and "Abort triggers." Naming what is out-of-scope is what prevents a curious operator from pivoting into a production database because it was reachable; naming abort triggers in advance is what lets anyone stop the exercise without a debate when something goes wrong.
3.2 Blast-radius discipline
Purple techniques are real, and some have real consequences if run without limits. Encode the limits explicitly:
| Technique class | Uncontrolled risk | Blast-radius control |
|---|---|---|
| Credential dumping | Real credentials harvested and now in a dump file | Synthetic accounts only; secure-delete dumps; rotate any real creds touched |
| Lateral movement | Uncontrolled spread across the domain | Cap hops (e.g. 2); named target hosts only |
| Persistence | Artifacts left running after the exercise | Track every persistence artifact in the scribe log; verify removal in cleanup |
| Defense evasion (disable logging) | Blind spot left open post-exercise | Re-enable and verify before close; never leave logging off |
| Exfiltration | Real data leaves the environment | Synthetic marker files only; never real data, even encrypted |
3.3 To deconflict with the real SOC, or not
A genuine design decision with two valid answers:
- Inform the SOC (deconflicted): the SOC knows the exercise is running and can distinguish it from a real incident. Use this when you are testing detection content — you do not want a real IR mobilisation, and you want analysts free to collaborate.
- Do not inform the SOC (blind / "double-blind"): the SOC does not know, so you also test whether the human response process works. Use this when the SOC's ability to detect-and-respond under realistic conditions is itself the thing under test — but keep the White Cell deconfliction contact reachable so a real escalation can be halted before it wastes an on-call night or triggers external notifications.
Most technique-driven purple exercises are deconflicted, because the objective is detection coverage and collaboration. Reserve blind runs for when response-process realism is the explicit goal, and even then, only the front-line SOC is blind — the White Cell always knows.
Part 4: Threat-Intelligence-Driven Planning
The technique list is the spine of the exercise, and the best technique lists come from intelligence about who actually targets you, not from a generic top-10.
4.1 From actor to ordered plan
flowchart LR
A[Who targets our sector?<br/>threat intel] --> B[Pick a representative actor<br/>e.g. APT29 / FIN7]
B --> C[Extract their TTPs<br/>ATT&CK profile + reporting]
C --> D[Filter to techniques<br/>relevant to our environment]
D --> E[Order by kill-chain phase]
E --> F[Annotate each: data source,<br/>expected detection, robustness target]
F --> G[The technique plan]
style G fill:#22543d,color:#fff
The filtering step matters: an actor's full profile may include techniques irrelevant to your estate (a Linux-only technique when you are Windows-domain, a cloud technique when you are on-prem). Emulate what is applicable, and note the rest as out-of-scope-this-round rather than pretending you covered them.
4.2 A worked technique plan
An exercise plan is a table, ordered by kill-chain phase, with the detection expectation stated before the run so the result can be scored against a prediction:
| # | Phase | Technique | Procedure | Data source | Expected outcome | Robustness target |
|---|---|---|---|---|---|---|
| 1 | Discovery | T1087.002 Account Discovery: Domain | net group /domain |
4688 cmdline | Logged-only (predict gap) | L2 |
| 2 | Discovery | T1018 Remote System Discovery | nltest /dclist |
4688 cmdline | Logged-only | L2 |
| 3 | Cred Access | T1003.001 LSASS dump | comsvcs MiniDump | Sysmon EID 10 | Alerted (Ch.3 rule) | L3 |
| 4 | Cred Access | T1558.003 Kerberoasting | Rubeus | Security 4769 | Alerted (Ch.3 rule) | L3 |
| 5 | Lateral | T1021.002 SMB/Admin Shares | psexec |
4688 + 5140 | Predict gap | L2 |
| 6 | Lateral | T1550.002 Pass-the-Hash | Mimikatz sekurlsa::pth | 4624 type 9 | Predict gap | L2 |
| 7 | Collection | T1560.001 Archive via utility | 7z a |
4688 cmdline | Logged-only | L1 |
| 8 | Exfil | T1048 Exfil over alt protocol | DNS tunnel (marker) | Sysmon EID 22 | Predict gap | L2 |
Stating the expected outcome before the run is a discipline worth insisting on: it converts each technique from "let's see what happens" into a testable prediction, and a surprise (predicted alerted, got nothing) is often more valuable than a confirmed gap because it means a detection you believed in is broken.
Part 5: Environment and Safety
5.1 Snapshots first
Nothing fires until there is a known-good rollback point. Every target host and the domain controller are snapshotted, and the snapshot owner is named in the RoE. This is non-negotiable for two reasons: it lets you cleanly remove persistence and artifacts, and it lets you re-run the exercise from an identical baseline, which is what makes results comparable across runs.
# Example: snapshot the lab before the exercise (hypervisor-specific;
# shown for a libvirt/KVM lab)
for vm in dc01 win10-workstation win-server-app; do
virsh snapshot-create-as "$vm" "purple-baseline-$(date +%Y%m%d)" \
--description "pre-exercise clean state" --atomic
done
virsh snapshot-list dc01 Name Creation Time State
------------------------------------------------------------------
purple-baseline-20270513 2027-05-13 08:41:07 +0000 shutoff
5.2 The exercise marker
So that exercise activity can be told apart from real activity (and cleaned up), tag it. A dedicated source host, a filename convention, a benign but distinctive string in command lines — any consistent marker that the scribe records and the blue cell can filter on:
Exercise marker conventions (recorded in RoE):
- all offensive activity originates from 10.0.14.55 (red-op host)
- dropped files named PURPLE-<technique>-<timestamp>.<ext>
- synthetic accounts prefixed svc-purple-*
- DNS exfil marker domain purple-exercise.lab.internal
5.3 The abort protocol
Rehearsed, one-line, executable by anyone with abort authority:
stateDiagram-v2
[*] --> Running
Running --> AbortCalled: real incident / blast radius / prod impact / safety
AbortCalled --> StopFiring: red ceases immediately
StopFiring --> Announce: "EXERCISE ABORT" on channel
Announce --> Freeze: scribe snapshots current state + timeline
Freeze --> Deconflict: white cell checks with real SOC
Deconflict --> Cleanup: remove artifacts, restore, rotate creds
Cleanup --> [*]
The abort must be practised, not just documented. An abort that has never been executed is a hypothesis, and the moment you need it is the worst possible time to discover the rollback does not work.
5.4 Cleanup and close-out
Every exercise ends with a verified return to clean state, tracked against the scribe's artifact log:
| Cleanup item | Verification |
|---|---|
| Persistence artifacts removed | Re-query for each artifact logged during the run; expect none |
| Dropped files deleted | Marker-convention sweep returns nothing |
| Synthetic accounts disabled/removed | Directory query for svc-purple-* |
| Logging re-enabled | Confirm any evasion-disabled source is back and forwarding |
| Real credentials rotated | Any real account whose creds were exposed is rotated |
| Snapshots restored (lab) | Hosts reverted to baseline; or artifacts individually confirmed gone (prod) |
Part 6: The Live Run — Cadence and the Collaboration Loop
With plan, RoE, roles, and environment ready, the exercise runs. The rhythm is a tight per-technique loop, repeated down the plan.
6.1 The per-technique loop
sequenceDiagram
participant R as Red operator
participant W as White cell / scribe
participant B as Blue analyst
R->>W: "Firing T1003.001 comsvcs MiniDump, now"
W->>W: confirm in-scope, timestamp T0
R->>R: execute technique, capture exact cmd + output
R->>B: "Fired. Source 10.0.14.55, target lsass on win-server-app"
B->>B: check telemetry: logged? alerted? MTTD?
B->>W: "EID 10 present at T0+3s. No alert. LOGGED-ONLY."
W->>W: record outcome, MTTD, evidence link
Note over R,B: If gap: diagnose together. If fixable now, blue<br/>deploys/tunes rule, red RE-FIRES to validate.
B->>W: "Rule deployed. Re-fire?"
R->>R: re-fire same technique
B->>W: "ALERTED at T0'+4s. Now covered L3."
W->>W: record the in-session fix
The in-session fix — the dashed part of the loop — is the single most valuable thing that happens in a purple exercise. A logged-only result diagnosed and turned into a validated alert before the technique moves on is coverage bought in minutes. Not every gap is fixable in-session (a not-logged gap usually needs a data-source project), but every logged-only gap is a candidate, and working them live is what distinguishes a purple exercise from sequential red-then-blue.
6.2 What the scribe captures per technique
The record for each technique is fixed and complete, because it becomes a row in the scorecard and the report:
technique_id: T1003.001
procedure: comsvcs.dll MiniDump
timestamp_fired: 2027-05-13T10:14:03Z
source_host: 10.0.14.55
target: win-server-app / lsass.exe
exact_command: rundll32 comsvcs.dll, MiniDump <pid> C:\temp\PURPLE-...dmp full
telemetry_seen: Sysmon EID 10, GrantedAccess 0x1fffff, T+3s
outcome: logged-only -> (in-session) alerted
mttd_seconds: 4 # after in-session fix
robustness: L3
detection_ref: rules/credential_access/T1003.001_lsass_access.yml
notes: default rule missing; deployed Ch.3 rule during exercise
evidence: splunk-link, screenshot
6.3 Pace
Resist the urge to rush the plan. A purple exercise that fires 40 techniques with no time to diagnose gaps produces a long list and no fixes — which is a red team wearing a purple badge. Better to plan 12-15 techniques with room to stop, diagnose, and fix on the logged-only results. Depth of collaboration beats breadth of technique count; the coverage you build during the exercise matters more than the coverage you merely measure.
Part 7: Scoring the Exercise
A purple exercise without a scoring model produces anecdotes. The scoring model turns each technique's result into comparable numbers.
7.1 The scoring dimensions
| Dimension | Values | Measures |
|---|---|---|
| Detection outcome | not-logged / logged-only / alerted / prevented | The coverage state (Chapter 3, Part 3) |
| MTTD | seconds/minutes | Speed of detection, where detection exists |
| Robustness | L1-L4 (Chapter 3, Part 7) | Durability of the detection |
| Response quality | none / manual / playbook / automated | What happened after the alert |
| In-session fixed? | yes/no | Whether the gap was closed during the run |
Scoring on all five, rather than a single pass/fail, is what makes the result actionable: "logged-only, L-n/a, no response" and "alerted, L1, manual response" are both "not great" on a binary scale but need completely different fixes.
7.2 A worked scorecard
The plan from Part 4.2, after the run:
| # | Technique | Outcome (start → end) | MTTD | Robustness | Response | In-session fix |
|---|---|---|---|---|---|---|
| 1 | T1087.002 Account Discovery | logged-only → alerted | 6s | L2 | manual | Yes |
| 2 | T1018 Remote System Discovery | logged-only → alerted | 5s | L2 | manual | Yes |
| 3 | T1003.001 LSASS dump | logged-only → alerted | 4s | L3 | manual | Yes |
| 4 | T1558.003 Kerberoasting | alerted | 47s | L3 | playbook | — |
| 5 | T1021.002 SMB lateral | not logged | — | — | none | No (needs 5140 onboarding) |
| 6 | T1550.002 Pass-the-Hash | logged-only | — | L2 | none | No (rule drafted, needs tuning) |
| 7 | T1560.001 Archive | logged-only → alerted | 8s | L1 | manual | Yes |
| 8 | T1048 DNS exfil | not logged | — | — | none | No (needs Sysmon EID 22) |
7.3 Reading the scorecard
Turn the table into the three numbers that go on the summary slide:
#!/usr/bin/env python3
"""score_exercise.py - summarise a purple exercise scorecard."""
results = [
("T1087.002","alerted",6,"L2",True), ("T1018","alerted",5,"L2",True),
("T1003.001","alerted",4,"L3",True), ("T1558.003","alerted",47,"L3",False),
("T1021.002","not-logged",None,None,False),
("T1550.002","logged-only",None,"L2",False),
("T1560.001","alerted",8,"L1",True), ("T1048","not-logged",None,None,False),
]
n = len(results)
alerted = [r for r in results if r[1] == "alerted"]
logged_only = [r for r in results if r[1] == "logged-only"]
not_logged = [r for r in results if r[1] == "not-logged"]
robust = [r for r in alerted if r[3] in ("L3","L4")]
fixed = [r for r in results if r[4]]
mttds = [r[2] for r in alerted if r[2] is not None]
print(f"Techniques tested: {n}")
print(f"Alerted (any robustness): {len(alerted)}/{n} ({100*len(alerted)//n}%)")
print(f"Covered (alerted, L3+): {len(robust)}/{n} ({100*len(robust)//n}%)")
print(f"Logged-only (cheap gaps): {len(logged_only)}")
print(f"Not logged (visibility): {len(not_logged)}")
print(f"Closed in-session: {len(fixed)}")
print(f"Median MTTD (alerted): {sorted(mttds)[len(mttds)//2]}s")Techniques tested: 8
Alerted (any robustness): 5/8 (62%)
Covered (alerted, L3+): 2/8 (25%)
Logged-only (cheap gaps): 1
Not logged (visibility): 2
Closed in-session: 5
Two honest headlines fall out: the exercise raised alerted coverage from 1/8 (only Kerberoasting was pre-covered) to 5/8 during the run, and yet only 25% is covered at high robustness — a candid picture that a raw "62% detected" would have flattered. The five in-session fixes are the exercise's immediate ROI; the three remaining gaps (one logged-only needing tuning, two not-logged needing telemetry projects) are the backlog. The gap between "alerted" and "covered at L3+" is the number that keeps a program honest, and it is exactly the technique-vs-procedure and robustness distinction from Chapter 3 showing up at the program level.
Part 8: Live Execution Walk-Through
To make the loop concrete, here is a chained sequence — discovery into credential access into lateral movement — as it actually plays out across all three cells. Everything runs in the authorized, snapshotted lab from Chapter 2, from the red-op host 10.0.14.55.
8.1 Discovery (T1087.002 / T1018)
Red fires:
C:\> net group "Domain Admins" /domain
C:\> nltest /dclist:lab.internalGroup name Domain Admins
Members
-------------------------------------------------------------------------------
Administrator svc-backup svc-sql
The command completed successfully.
Get list of DCs in domain 'lab.internal' from '\\DC01'
dc01.lab.internal [PDC] [DS] Site: Default-First-Site-Name
The command completed successfully.
Blue observes (Splunk, filtering on the exercise source host):
`sysmon` OR (`wineventlog` EventCode=4688) host_ip=10.0.14.55
| search CommandLine="*net group*" OR CommandLine="*nltest*"
| table _time Image CommandLine
_time Image CommandLine
2027-05-13 10:02:11 C:\Windows\...\net.exe net group "Domain Admins" /domain
2027-05-13 10:02:44 C:\Windows\...\nltest.exe nltest /dclist:lab.internal
Collaboration dialogue (scribe log):
10:02 RED: fired T1087.002 (net group) + T1018 (nltest)
10:02 BLUE: both in 4688 with cmdline. No alert. LOGGED-ONLY x2.
10:04 BLUE: deploying discovery rule (net/nltest/whoami recon burst from single host)
10:07 RED: re-firing both
10:07 BLUE: ALERTED, MTTD 6s / 5s. Now L2 (matches tool cmdline, evadable by LOLBins).
Flagged for a follow-up L3 behavioural: recon-burst-by-count regardless of tool.
That last line is the discipline from Chapter 3's robustness ladder in action: the in-session fix is a real L2 detection, and the team records that it is only L2 and what the L3 version would be, rather than declaring victory.
8.2 Credential access (T1003.001)
Red fires (comsvcs, deliberately avoiding procdump to test robustness):
C:\> for /f "tokens=2" %i in ('tasklist ^| findstr lsass') do set PID=%i
C:\> rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump %PID% C:\temp\PURPLE-lsass.dmp full (no output; C:\temp\PURPLE-lsass.dmp written, ~45 MB)
Blue observes:
`sysmon` EventCode=10 TargetImage="*lsass.exe" host="win-server-app"
| table _time SourceImage GrantedAccess CallTrace
_time SourceImage GrantedAccess CallTrace
2027-05-13 10:14:03 C:\Windows\...\rundll32.exe 0x1fffff ...comsvcs.dll+...
Collaboration:
10:14 RED: fired T1003.001 via comsvcs (NOT procdump - testing robustness)
10:14 BLUE: EID 10 present, SourceImage=rundll32. No alert. LOGGED-ONLY.
10:16 BLUE: deploying Ch.3 rule (TargetImage=lsass + GrantedAccess mask,
source-agnostic -> catches comsvcs too)
10:18 RED: re-fire
10:18 BLUE: ALERTED, MTTD 4s, L3 (technique-invariant, source-agnostic).
10:19 WHITE: note - recommend RunAsPPL as prevention (Ch.3 Part 10). Backlog item.
8.3 Lateral movement (T1021.002) — the gap that cannot be closed in-session
Red fires:
C:\> psexec.exe \\win-server-app -u lab\svc-purple -p <synthetic> cmd.exe /c whoamilab\svc-purple
Blue observes — and finds nothing:
`wineventlog` EventCode=5140 host="win-server-app" -> 0 results
`sysmon` EventCode=3 host_ip=10.0.14.55 DestinationPort=445 -> present, but no share-access detail
Collaboration:
10:31 RED: fired T1021.002 (psexec to win-server-app)
10:31 BLUE: network connection to 445 seen, but NO 5140 (file share access) events.
Object Access auditing for file shares is not enabled on that host.
10:33 WHITE: this is NOT-LOGGED, not logged-only. Cannot fix in-session.
BACKLOG: enable "Audit Detailed File Share" (5145) + File Share (5140),
owner = platform team, retest date = +2 weeks.
This is the honest counterpoint to the in-session fixes: some gaps are visibility projects, not rule projects (Chapter 3, Part 10). The exercise's job here is not to fix it live — it is to diagnose it precisely (not-logged, specific audit policy missing, named owner, retest date) so the after-action report carries a fix, not just a finding.
Part 9: Running It With Limited People and Tooling
Full white/red/blue cells are a luxury. Most teams run purple with two or three people and a modest SIEM. The exercise still works if you preserve the roles even when one person wears several hats.
9.1 The two-person purple exercise
| Full model | Two-person compression |
|---|---|
| Exercise Director + White Cell + Scribe | Person A also directs and scribes (use a shared doc as the live timeline) |
| Red Lead + Operators | Person A fires techniques |
| Blue Lead + Analysts | Person B watches telemetry and tunes |
The one role you must not collapse into the others is the record. With two people, the timeline lives in a shared document that both edit in real time — every technique gets a row as it fires. It is tempting to "reconstruct it later"; later never comes, and the exercise's value evaporates with the memory of it.
9.2 Minimal tooling stack
You can run a complete, valuable purple exercise with entirely free tools:
Emulation: Invoke-AtomicRedTeam (per-technique) + a manual command list
Telemetry: Sysmon (sysmon-modular config) + Windows Security auditing
SIEM: a free-tier Splunk, Elastic, or even Windows Event Forwarding + queries
Authoring: Sigma + sigma-cli
Record: a shared spreadsheet / markdown doc (the scribe timeline)
Tracking: the scorecard script (Part 7.3) + an ATT&CK Navigator layer
The absence of a commercial breach-and-attack-simulation platform is not a blocker. Atomic Red Team plus Sysmon plus a query interface is enough to run the entire loop in Part 6, and many mature programs never use anything more.
9.3 Time-boxing for small teams
A two-person team cannot sustain a full-day, 40-technique exercise. Run short, frequent, focused sessions instead: a two-hour exercise covering 5-6 techniques in one ATT&CK tactic (say, Credential Access), fully diagnosed and fixed, every fortnight. Frequency beats scale for a small team — six focused two-hour sessions build more real coverage than one exhausting all-day sprint that measures a lot and fixes nothing.
Part 10: The After-Action Report
The report is the deliverable that converts a day of testing into a program of work. A findings list with no owners and no dates is a document that gets filed; a report structured around owned, dated remediation is one that changes posture.
10.1 Report structure
PURPLE TEAM EXERCISE - AFTER-ACTION REPORT
1. EXECUTIVE SUMMARY (half a page, for leadership)
- Objective and the question answered
- Headline numbers: techniques tested, alerted %, covered-at-L3+ %,
in-session fixes, MTTD median
- The single most important gap and its business relevance
- Trend vs previous exercise (Part 11)
2. SCOPE & METHOD
- RoE summary, format, actor emulated, dates, participants
3. RESULTS (the scorecard, Part 7.2)
- Per-technique table: outcome, MTTD, robustness, response, fixed?
- ATT&CK Navigator coverage layer (before/after)
4. FINDINGS & REMEDIATION (the heart of the report)
For each gap:
- Technique + procedure + evidence
- Gap type (not-logged / logged-only / low-robustness / no-response)
- Recommended fix (onboard source / write rule / tune / prevent)
- OWNER (named person/team) <- mandatory
- PRIORITY (from risk, not ease)
- RETEST DATE <- mandatory
5. WHAT WORKED
- Detections that fired well; keep and regression-test them
6. APPENDICES
- Full scribe timeline, exact commands, raw evidence links
10.2 The remediation table is the point
Every gap becomes a row that a program manager can track:
| Finding | Gap type | Fix | Owner | Priority | Retest |
|---|---|---|---|---|---|
| T1021.002 SMB lateral not detected | not-logged | Enable 5140/5145 file-share auditing on servers | platform-team | High | +2 wk |
| T1048 DNS exfil not detected | not-logged | Onboard Sysmon EID 22 (DNS query) | soc-eng | High | +3 wk |
| T1550.002 PtH logged-only | logged-only | Tune drafted 4624-type-9 rule to acceptable precision | detection-eng | Med | +1 wk |
| T1560.001 Archive detection is L1 | low-robustness | Add behavioural archive-of-sensitive-paths rule | detection-eng | Low | +4 wk |
| T1003.001 no prevention | prevention gap | Deploy RunAsPPL fleet-wide | endpoint-team | Med | +4 wk |
Two mandatory fields make or break the report: OWNER and RETEST DATE. A finding with no owner is nobody's job; a fix with no retest date is never confirmed to actually work. The retest is not optional politeness — Chapter 3's whole argument is that an unvalidated detection is a hypothesis, so a remediation is not "done" until a retest fires the technique and confirms the fix.
10.3 Closing the loop with retests
Retests are mini-exercises: on the retest date, re-fire exactly the technique that exposed the gap, against the same target, and record the new outcome. Only a technique that moves from its gap state to alerted at the target robustness is closed. This is where the snapshot discipline (Part 5.1) pays off — an identical baseline makes the retest a clean before/after.
Part 11: Metrics and Trending Across Exercises
A single exercise is a snapshot. The program's value shows in the trend across exercises, and a small set of stable metrics makes that trend legible.
| Metric | Per-exercise | Trend meaning |
|---|---|---|
| Alerted coverage % | alerted / tested | Breadth of detection; should rise |
| Covered-at-L3+ % | robust / tested | Real coverage; the honest number |
| Median MTTD | across alerted techniques | Detection speed; should fall |
| In-session fix rate | fixed / gaps found | Team's live detection-engineering capability |
| Remediation close rate | retested-closed / findings | Whether findings actually get fixed |
| Regression count | previously-covered techniques that broke | Rot; should stay near zero |
The two most diagnostic trend metrics are covered-at-L3+ % and remediation close rate. The first resists the flattery of "we detected it" by demanding robust, procedure-aware coverage (Chapter 3). The second measures the thing that actually determines whether the program improves posture: a program that finds 20 gaps per exercise and closes 3 is running in place; one that finds 8 and closes 8 is compounding. Close rate, not find rate, is the number that predicts whether next year's exercise scores better.
flowchart LR
E1[Exercise 1<br/>covered 15%] --> E2[Exercise 2<br/>covered 25%]
E2 --> E3[Exercise 3<br/>covered 38%]
R1[retests close gaps] -.-> E2
R2[retests close gaps] -.-> E3
REG[regression testing<br/>keeps prior gains] -.-> E3
style E3 fill:#22543d,color:#fff
Part 12: Continuous and Automated Purple Teaming
The periodic exercise is the foundation; the maturity endpoint is making the loop continuous. Two complementary directions:
- Scheduled automated emulation. Tools like CALDERA (Chapter 2) and automated Atomic runners can fire a technique set on a schedule — nightly or weekly — and report which detections still fire. This is regression testing at the program level (Chapter 3, Part 1): it catches the log-source migration or EDR upgrade that silently broke twelve rules, days after it happens instead of at the next quarterly exercise.
- Detection-as-code integration. When a new detection is merged (Chapter 3, Part 8), a CI job fires the corresponding technique in a lab and confirms the rule alerts before it reaches production. The purple loop becomes part of the deployment pipeline rather than a calendar event.
flowchart TD
subgraph Periodic["Periodic (quarterly)"]
EX[Full purple exercise<br/>new actor, new techniques]
end
subgraph Continuous["Continuous (nightly/weekly)"]
AUTO[Automated emulation of<br/>the covered technique set]
AUTO --> CHK{Still alerting?}
CHK -->|no| REG[Regression ticket<br/>auto-filed]
CHK -->|yes| OK[Coverage confirmed]
end
subgraph Pipeline["On every detection merge"]
CI[CI fires technique<br/>-> confirms alert]
end
EX -->|adds techniques to| AUTO
REG -->|feeds| EX
style EX fill:#553c9a,color:#fff
style AUTO fill:#2c5282,color:#fff
The important boundary: automation regression-tests known coverage; it does not discover new gaps. Finding what you cannot yet detect still needs the creativity of a human-run exercise against a fresh actor profile. The mature program runs both — continuous automation to hold the line, periodic human exercises to advance it.
A note on where this notebook has been going: continuous purple teaming is the operational expression of everything across these four chapters. Chapter 1's collaborative loop, Chapter 2's emulation engines, Chapter 3's validated detections, and this chapter's structured exercise all converge on a single steady-state capability — an organisation that continuously proves, in evidence rather than belief, which adversary behaviours it can see and stop.
Part 13: Common Pitfalls and Myths
13.1 Pitfalls
| # | Pitfall | Consequence | Fix |
|---|---|---|---|
| 1 | No scribe / no real-time record | Findings that cannot be reconstructed | Dedicated record; timeline written as it happens (Part 1.1) |
| 2 | Jumping to full emulation before technique coverage exists | Missed detections in a chain, unattributable | Technique-driven first, emulation to stress-test (Part 2) |
| 3 | Vague scope ("the corp domain") | Operator pivots into something out of bounds | Explicit in/out host lists in the RoE (Part 3.1) |
| 4 | No abort triggers defined in advance | Debate while a real problem grows | Named triggers + rehearsed abort (Part 5.3) |
| 5 | Firing 40 techniques with no time to fix | A long list, zero coverage gained | 12-15 techniques with room to diagnose (Part 6.3) |
| 6 | No snapshot before start | Cannot cleanly remove artifacts or re-run | Snapshot everything first (Part 5.1) |
| 7 | Leaving logging disabled after evasion tests | A blind spot left open post-exercise | Cleanup checklist verifies re-enablement (Part 5.4) |
| 8 | Reporting "62% detected" without robustness | Overstated posture | Report covered-at-L3+ separately (Part 7.3) |
| 9 | Findings with no owner or retest date | Nothing gets fixed or confirmed | Owner + retest mandatory in the report (Part 10.2) |
| 10 | Treating the report as the deliverable | Filed and forgotten | The deliverable is closed retests, not a PDF |
| 11 | Measuring find rate, not close rate | Program runs in place | Trend close rate across exercises (Part 11) |
| 12 | Automation mistaken for the whole program | New gaps never discovered | Automation holds the line; humans advance it (Part 12) |
| 13 | Prevention validated but not its alert | Blind block, no campaign signal | Confirm prevented techniques also alert (Ch.3 Part 3) |
| 14 | Blind run with no White Cell contact | Real IR mobilised, external notifications fire | Front-line SOC blind, White Cell always reachable (Part 3.3) |
13.2 Myths
"Purple teaming is just a red team that shares its report." The defining feature is the live, two-way, minute-scale loop and the in-session fixes (Part 6.1). A red team that hands over a report at the end is not a purple exercise; the collaboration during execution is the point.
"You need a commercial breach-and-attack-simulation platform." You need Atomic Red Team, Sysmon, a query interface, and Sigma — all free (Part 9.2). The platform is a convenience, not a prerequisite.
"More techniques per exercise is better." Depth of collaboration beats breadth of count. Twelve techniques fully diagnosed and half of them fixed in-session beats forty measured and none fixed (Part 6.3).
"A high detection percentage means we're covered." Only if it is robust, procedure-aware coverage. The gap between "alerted" and "covered at L3+" is where the honesty lives (Part 7.3).
"The exercise is over when the report is written." The exercise is over when the retests close. An unretested remediation is an unvalidated detection — a hypothesis (Part 10.3).
"We ran one last year, so we do purple teaming." A single exercise is a snapshot. The value is the trend across exercises and the continuous automation that holds coverage between them (Parts 11-12).
Final Revision / Summary
The exercise is the event that uses every capability. Chapter 1's loop, Chapter 2's emulation, and Chapter 3's detection engineering converge in a planned, scoped, time-boxed exercise that starts with an objective and ends with owned, dated remediation and a trend line.
Roles stay distinct even when people don't. Exercise Director (scope, go/no-go, abort), White Cell (deconfliction), Scribe (the record), Red (fire techniques), Blue (detect, tune, re-test). The role never to collapse is the Scribe — the timeline is the raw material of the report.
Transparency is the defining feature. Red announces, blue reports what they see, both diagnose gaps together, and logged-only gaps get fixed in-session. That minute-scale loop, not a shared report, is what makes it purple.
Choose the format deliberately. Tabletop (process), technique-driven (build coverage), adversary-emulation (stress-test coverage against a real actor). Do them in that order; emulation before technique coverage exists just adds unattributable noise.
Scope in writing, safety by design. A signed RoE with explicit in/out host lists, forbidden actions, blast-radius limits, deconfliction, and pre-named abort triggers. Snapshot before anything fires; mark exercise activity; verify a clean return to baseline including re-enabled logging.
Plan from intelligence. Turn the actor most likely to target you into an ordered, kill-chain-phased technique list, each row annotated with the data source and the predicted outcome — so a surprise (predicted alerted, got nothing) surfaces a broken detection.
Score on five dimensions. Detection outcome, MTTD, robustness, response quality, and in-session-fixed — not a single pass/fail. Report alerted % and covered-at-L3+ % together; the gap between them is the honest picture.
The report's job is owned, dated remediation. Every gap: type, fix, named owner, priority by risk, retest date. A remediation is not done until a retest re-fires the technique and confirms the fix at the target robustness.
Trend beats snapshot. Across exercises, watch covered-at-L3+ % (real coverage) and remediation close rate (whether the program compounds). Close rate, not find rate, predicts next year's score.
Mature = continuous. Automated emulation regression-tests known coverage nightly and catches silent rot; detection-as-code fires the technique on every rule merge. Automation holds the line; human exercises against fresh actors advance it.
You can start small. Two people, free tools, a shared timeline doc, and a two-hour focused session every fortnight builds more real coverage than one exhausting all-day sprint that measures everything and fixes nothing.
Cheat Sheet / Quick Reference
Roles
Exercise Director objective, scope, go/no-go, ABORT
White Cell deconfliction, in-scope confirmation
Scribe the timeline (never skip)
Red Lead/Operators fire techniques, capture exact cmd+output
Blue Lead/Analysts detect, record outcome, tune + re-test live
Format choice
Tabletop test process/roles/decisions (low cost)
Technique-driven build + measure coverage breadth (medium)
Adversary-emul stress-test coverage vs a real actor (high)
Order: technique-driven FIRST, emulation to stress-test.
RoE must-haves
[ ] authorizing signature + window [ ] in-scope AND explicitly-out host lists
[ ] objective + measurable success [ ] forbidden actions (destructive/PII/DoS)
[ ] snapshot owner [ ] blast-radius limits (hop cap, synthetic data)
[ ] deconfliction contact (reachable) [ ] named abort triggers + procedure
Per-technique loop
RED announce -> WHITE confirm in-scope + timestamp -> RED fire (capture cmd+output)
-> BLUE check telemetry (logged? alerted? MTTD?) -> record outcome
-> if logged-only gap: diagnose together, deploy/tune rule, RED RE-FIRE, validate
Scribe record per technique
technique_id | procedure | timestamp | source_host | target | exact_command
telemetry_seen | outcome(not-logged/logged-only/alerted/prevented) | MTTD
robustness(L1-4) | detection_ref | in-session_fix? | evidence
Scoring dimensions
outcome not-logged / logged-only / alerted / prevented
MTTD seconds to alert
robustness L1 string -> L2 tool -> L3 technique-invariant -> L4 behavioural
response none / manual / playbook / automated
fixed? in-session yes/no
Report: alerted % AND covered-at-L3+ % (never one alone)
Gap type -> fix
not-logged -> onboard data source / enable audit policy (cannot fix in-session)
logged-only -> write/tune a rule (often fixable in-session)
low-robustness-> climb the ladder to L3+
no-response -> playbook + routing
prevention -> deploy control AND confirm it alerts
After-action remediation row (owner + retest MANDATORY)
finding | gap_type | fix | OWNER | priority(by risk) | RETEST_DATE
closed = retest re-fires the technique and confirms alert at target robustness
Program trend metrics
alerted % | covered-at-L3+ % | median MTTD | in-session fix rate
remediation CLOSE rate (predicts improvement) | regression count (rot)
Glossary
| Term | Meaning |
|---|---|
| White Cell | Neutral control/deconfliction group overseeing the exercise |
| Scribe | Role owning the real-time timeline and evidence record |
| RoE | Rules of Engagement: signed scope, limits, abort, deconfliction |
| Deconfliction | Keeping the real SOC able to tell exercise from real incident |
| Blast radius | The bounded extent of an exercise's real-world effect |
| In-session fix | A gap diagnosed and turned into a validated alert during the run |
| Technique-driven exercise | Firing individual ATT&CK techniques to build/measure coverage |
| Adversary emulation | Emulating a specific actor's chained TTPs end to end |
| MTTD / MTTR | Mean time to detect / respond |
| Covered-at-L3+ | Alerted at technique-invariant or behavioural robustness |
| Retest | Re-firing a technique on the retest date to confirm a fix |
| Close rate | Retested-closed findings / total findings |
| Continuous purple teaming | Scheduled automated emulation + detection-as-code CI |
Practice Labs & Resources
- Run a full two-hour technique-driven exercise in DetectionLab. Take one ATT&CK tactic (Credential Access), build an 8-technique plan with predicted outcomes (Part 4.2), fire each with Atomic Red Team, record the scorecard live in a shared doc, fix every logged-only gap in-session, and write the one-page after-action report with owners and retest dates. Then actually do the retests two weeks later.
- Write and sign a real RoE (Part 3.1) for your own lab before the exercise above. Practising the scope-and-safety discipline on a lab is how it becomes muscle memory for a production engagement.
- MITRE CTID Adversary Emulation Library. Take a published plan (APT29, FIN6, Carbanak+FIN7), extract its applicable techniques, and run an emulation exercise. Compare the chained result against the technique-by-technique baseline you built earlier — this is the "technique-driven first, emulation to stress-test" progression from Part 2 made real.
- Build the scorecard + Navigator trend. Use the Part 7.3 script and Chapter 3's coverage-layer generator to produce before/after Navigator layers for one exercise, then run a second exercise a month later and diff them. Seeing coverage move is the single most motivating artifact for a program.
- Practise the abort. In the lab, deliberately trigger an abort mid-exercise (simulate "blast radius exceeded"), execute the Part 5.3 protocol, and time the return to clean state. An abort you have rehearsed is one you can trust.
- Two-person purple drill. With one colleague, run the compressed model from Part 9.1: you fire, they detect, both edit the timeline. Prove to yourselves that a valuable exercise needs no cell structure and no commercial platform.
- Stand up continuous regression. Schedule an automated Atomic/CALDERA run of your covered technique set (Part 12), wire the "still alerting?" check to file a ticket on failure, and deliberately break a rule to confirm the regression fires. This is the program-level version of Chapter 3's detection-as-code CI.
- VECTR end to end. Log two or three exercises in VECTR (community edition) to build the trend metrics from Part 11 — covered-at-L3+ %, MTTD, and close rate over time — and use it to drive the next exercise's plan.
- TryHackMe — "Purple Team", "Red Team Fundamentals", "Intro to Detection Engineering" and HackTheBox purple/blue tracks for guided practice combining execution, detection, and reporting in one flow.
- Read published purple/exercise write-ups and IR retrospectives, and reverse-engineer a technique plan from a real incident: "if this actor did exactly what the report says, which of my detections would fire?" That question is the seed of your next exercise.
This completes the Purple Team notebook. Across four chapters it moved from the collaborative philosophy, through the offensive emulation engines and the defensive detection-engineering discipline, to the structured exercise that fuses them into a measured, trended, continuously improving capability. The next notebook steps up from operating defences to governing them — the frameworks, risk management, and architecture that decide what a security program is trying to achieve in the first place.