Rebuilding the lab around the feedback loop, not the tooling
Six months of a detection lab that was impressive to look at and useless to work in, and the four changes that fixed it.
The first version of this lab had more moving parts than the production environment it was supposed to model. Three hypervisor nodes, a full domain, two SIEMs "for comparison", and a network tap. It looked like the diagrams people post.
I used it about four times in six months, because every experiment cost forty minutes of setup before it produced a single log line.
Here is what actually made it useful.
1. Snapshot-first, rebuild-never
The original loop was: break the box, spend an evening rebuilding it, lose the thread. The fix is embarrassingly simple and I should have done it on day one.
#!/usr/bin/env bash
set -euo pipefail
STAMP="clean-$(date +%Y%m%d-%H%M)"
for vmid in 101 102 103 110 120; do
echo "snapshotting ${vmid} -> ${STAMP}"
qm snapshot "${vmid}" "${STAMP}" --description "pre-exercise clean state"
done
Rollback is thirty seconds. That single change is what turned "run the technique once, carefully" into "run it four ways and see which ones the rule misses" — which is the whole point of having a lab.
2. One SIEM, and it is the one you have at work
Running two SIEMs to compare them meant every finding needed reproducing twice and neither instance was ever properly tuned. Both got noisy, so I trusted neither.
Pick the one your detections have to ship to. Everything else is a distraction dressed up as rigour.
3. The collection config is version-controlled and it is the same file
The single highest-value change. The lab's Sysmon config is the same file as the one on the domain, in the same repository, with the same review history:
<RuleGroup name="RegistryPersistence" groupRelation="or">
<RegistryEvent onmatch="include">
<TargetObject condition="end with">\CurrentVersion\Run</TargetObject>
<TargetObject condition="contains">\CurrentVersion\Run\</TargetObject>
<TargetObject condition="contains">\CurrentVersion\RunOnce\</TargetObject>
<TargetObject condition="contains">\Winlogon\Shell</TargetObject>
</RegistryEvent>
</RuleGroup>
4. Every exercise ends with a written artifact
Not a report — a file. One markdown note per exercise, in the repo, containing:
- the exact command that was run
- the raw events it produced, pasted in
- what fired, what did not, and the guess about why
- the rule change, if any
## technique
rundll32.exe with an exported entry point
## command
rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";alert();
## produced
Sysmon EID 1 → yes, full command line
Sysmon EID 7 → yes, mshtml.dll ImageLoad
Defender → nothing
Zeek → nothing (no network stage in this variant)
## fired
none of the three existing rundll32 rules
## why
all three keyed on ".dll," in the command line; this variant has no .dll token
## change
sigma/proc_rundll32_no_dll_token.yml — new rule, level: medium
Ninety percent of the value of a home lab is the note, not the lab. The note is what you still have in six months; the VM is not.
What the lab is now
| was | is | | --- | --- | | 3 Proxmox nodes | 1 node, 6 VMs | | 2 SIEMs | 1, tuned | | rebuild after each run | snapshot rollback, 30s | | ad-hoc Sysmon config | shared config, version-controlled | | no written output | one note per exercise, in git |
Smaller, less photogenic, used roughly weekly instead of monthly. The constraint that mattered was never compute — it was how many minutes stood between having an idea and seeing a log line.
Discussion
GuidelinesSign in to comment. Corrections and additions are the point — this is a working document.