SKILL: Week 4: Crash Analysis and Exploitability Assessment
Metadata
- Skill Name: crash-analysis
- Folder: offensive-crash-analysis
- Source: https://github.com/SnailSploit/offensive-checklist/blob/main/4-crash-analysis.md
Description
Week 4 exploit development curriculum. Crash triage and analysis methodology: WinDbg/GDB analysis, ASAN/MSAN output interpretation, exploitability assessment, register/stack trace reading, root cause identification. Use when analyzing crash dumps, assessing exploitability, or understanding fuzzer-generated crashes.
Trigger Phrases
Use this skill when the conversation involves any of:
crash analysis, crash triage, WinDbg, GDB, ASAN, MSAN, exploitability, stack trace, register dump, segfault, null deref, access violation, week 4
Instructions for Claude
When this skill is active:
- Load and apply the full methodology below as your operational checklist
- Follow steps in order unless the user specifies otherwise
- For each technique, consider applicability to the current target/context
- Track which checklist items have been completed
- Suggest next steps based on findings
Full Methodology
Week 4: Crash Analysis and Exploitability Assessment
Overview
created by AnotherOne from @Pwn3rzs Telegram channel.
After finding potential vulnerabilities through fuzzing (Week 2) or patch diffing (Week 3), the next critical step is analyzing crashes to determine if they're exploitable. This week focuses on crash triage, debugger mastery, and techniques for identifying how to reach vulnerable code paths from attacker-controlled input.
Once you've confirmed a crash is exploitable and built a PoC, you'll be ready for Basic Exploitation in Week 5.
Prerequisites
Before starting this week, ensure you have:
- A Windows VM (for WinDbg labs) and a Linux VM (for GDB/ASAN/CASR labs).
- Completed Week 2 fuzzing labs, including running AFL++ or libFuzzer against at least one C/C++ target
- Completed (or skimmed) Week 3 patch diffing labs:
- Familiar with Ghidriff/Diaphora diff reports and how to interpret changed functions
- Understand how to extract Windows updates and Linux kernel patches
- Reviewed at least one case study (CVE-2022-34718 EvilESP, CVE-2024-1086 nf_tables, or 7-Zip symlink bugs)
- Comfortable understanding from Week 1 of basic vulnerability classes (buffer overflow, UAF, integer bugs, info leaks) and their exploit primitives
Crash Analysis Decision Tree
Use this decision tree to select the appropriate tools and workflow for any crash you encounter:
┌─────────────────────────────────────────────────────────────────────┐
│ CRASH RECEIVED │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────┐
│ Source code available?│
└───────────────────────┘
│ │
Yes No
│ │
▼ ▼
┌─────────────────────┐ ┌──────────────────────────┐
│ Recompile with │ │ What platform? │
│ ASAN + UBSAN │ └──────────────────────────┘
│ (Day 2) │ │ │ │
└─────────────────────┘ │ │ │
│ Windows Linux Mobile
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────┐ ┌───────┐ ┌───────┐ ┌───────────┐
│ Run crash input │ │WinDbg │ │Pwndbg │ │ Tombstone │
│ Get detailed report │ │+ TTD │ │+ rr │ │ + Frida │
└─────────────────────┘ │(Day 1)│ │(Day 1)│ │ (Future) │
│ └───────┘ └───────┘ └───────────┘
│ │ │ │
└─────────────┴────┬────┴─────────┘
│
▼
┌─────────────────────────────────────┐
│ Crash requires special environment? │
└─────────────────────────────────────┘
│ │
Yes No
│ │
▼ │
┌─────────────────────────────┐ │
│ Setup reproduction env: │ │
│ - Network (tcpdump, proxy) │ │
│ - Files (strace, procmon) │ │
│ - Services (docker, VM) │ │
└─────────────────────────────┘ │
│ │
└──────────────┬───────────────┘
│
▼
┌─────────────────────┐
│ Crash type known? │
└─────────────────────┘
│ │
Yes No
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Run CASR for │ │ Manual analysis: │
│ classification │ │ - Examine registers │
│ (Day 3) │ │ - Check memory │
└─────────────────────┘ │ - Disassemble │
│ │ (Day 3) │
│ └─────────────────────┘
│ │
└────────┬────────┘
│
▼
┌─────────────────────────┐
│ EXPLOITABILITY ASSESS │
│ - Check mitigations │
│ - Control analysis │
│ - Reachability (Day 4) │
└─────────────────────────┘
│
▼
┌─────────────────────────┐
│ Multiple crashes? │
└─────────────────────────┘
│ │
Yes No
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Deduplicate (Day 5) │ │ Minimize (Day 5) │
│ - CASR cluster │ │ - afl-tmin │
│ - Stack hash │ │ - Manual reduction │
└─────────────────────┘ └─────────────────────┘
│ │
└────────┬───────────┘
│
▼
┌─────────────────────────┐
│ Create PoC (Day 6) │
│ - Python + pwntools │
│ - Verify reliability │
│ - Document findings │
└─────────────────────────┘
Quick Reference - Tool Selection by Scenario:
| Scenario | Primary Tool | Secondary Tool | Sanitizer |
|---|---|---|---|
| Linux binary, have source | GDB + Pwndbg | rr | ASAN + UBSAN |
| Linux binary, no source | GDB + Pwndbg | Ghidra | N/A |
| Windows binary, have source | WinDbg + TTD | Visual Studio | ASAN |
| Windows binary, no source | WinDbg + TTD | IDA/Ghidra | N/A |
| Fuzzer crash corpus | CASR | afl-tmin | ASAN |
| Non-deterministic crash | rr (Linux) / TTD (Windows) | Chaos mode | TSAN |
| Kernel crash (Linux) | crash utility | GDB + KASAN | KASAN |
| Kernel crash (Windows) | WinDbg kernel | Driver Verifier | N/A |
| Android app crash | Tombstone + ndk-stack | Frida | HWASan |
| Rust/Go crash | Native debugger | Sanitizer output | Built-in |
Day 1: Debugger Fundamentals and Crash Dump Analysis
- Goal: Learn Windows Debugger (WinDbg) and Linux debugger (GDB + Pwndbg) for analyzing application crashes.
- Activities:
- Reading:
- "Practical Malware Analysis" by Michael Sikorski - Chapter 9 and 10
- WinDbg Official Documentation
- Pwndbg Documentation
- Online Resources:
- Tool Setup:
- Windows: Install WinDbg Preview from Microsoft Store
- Linux: Install GDB with Pwndbg enhancement
- Install Windows SDK for symbol support
- Exercise:
- Analyze 5 pre-generated crash dumps (Windows and Linux)
- Identify crash type and root cause for each
- Reading:
Reproduction Fidelity
[!IMPORTANT] Before any crash analysis, ensure you can reproduce the crash reliably. A crash that only happens "sometimes" or "on the fuzzer's machine" is nearly impossible to analyze or exploit. This section establishes the mandatory checklist for achieving reproduction fidelity.
Reproduction Fidelity Checklist
Before analyzing any crash, verify these match between discovery and analysis environments:
┌─────────────────────────────────────────────────────────────────┐
│ REPRODUCTION FIDELITY CHECKLIST │
├─────────────────────────────────────────────────────────────────┤
│ System Environment │
│ [ ] OS/Kernel version : ________________________________ │
│ [ ] libc version : ________________________________ │
│ [ ] CPU architecture : [ ] x86 [ ] x86_64 [ ] ARM64 │
│ [ ] Container/VM : [ ] Native [ ] Docker [ ] VM │
│ [ ] ASLR state : [ ] Enabled [ ] Disabled │
├─────────────────────────────────────────────────────────────────┤
│ Process Environment │
│ [ ] argv (command-line) : ________________________________ │
│ [ ] Environment variables : ________________________________ │
│ [ ] Working directory : ________________________________ │
│ [ ] Locale (LC_ALL, LANG) : ________________________________ │
│ [ ] umask / permissions : ________________________________ │
├─────────────────────────────────────────────────────────────────┤
│ Input Path │
│ [ ] Input source : [ ] stdin [ ] file [ ] network │
│ [ ] Input file path : ________________________________ │
│ [ ] Network port/protocol : ________________________________ │
├─────────────────────────────────────────────────────────────────┤
│ Build Configuration │
│ [ ] Compiler version : ________________________________ │
│ [ ] Optimization level : [ ] -O0 [ ] -O1 [ ] -O2 [ ] -O3 │
│ [ ] Sanitizers : [ ] ASAN [ ] UBSAN [ ] TSAN [ ] None│
│ [ ] Debug symbols : [ ] Yes [ ] No │
│ [ ] Mitigations : [ ] PIE [ ] Canary [ ] RELRO │
└─────────────────────────────────────────────────────────────────┘
Essential Environment Knobs
ASAN/UBSAN Options (Linux/macOS):
# Full ASAN options for crash analysis
export ASAN_OPTIONS="\
abort_on_error=1:\
symbolize=1:\
detect_leaks=1:\
disable_coredump=0:\
halt_on_error=1:\
print_stats=1:\
check_initialization_order=1:\
detect_stack_use_after_return=1:\
quarantine_size_mb=256"
# UBSAN options
export UBSAN_OPTIONS="\
print_stacktrace=1:\
halt_on_error=1:\
suppressions=ubsan_suppressions.txt"
# Symbolizer path (required for readable stack traces)
export ASAN_SYMBOLIZER_PATH=$(command -v llvm-symbolizer)
glibc Allocator Tuning (Linux):
# Enable glibc heap consistency checks (catch corruption early)
export MALLOC_CHECK_=3
# Modern glibc tunable interface (glibc 2.26+)
export GLIBC_TUNABLES="\
glibc.malloc.check=3:\
glibc.malloc.perturb=165"
# What these do:
# MALLOC_CHECK_=3: Abort on heap corruption detection
# glibc.malloc.perturb=165: Fill freed memory with 0xA5 (helps detect UAF)
Core Dump Configuration (Linux):
# Enable unlimited core dumps
ulimit -c unlimited
# Verify core pattern (where dumps go)
cat /proc/sys/kernel/core_pattern
# For local dumps in CWD (temporary, affects system):
# echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_pattern
ASLR Control (Linux - for deterministic analysis):
# Check current ASLR state
cat /proc/sys/kernel/randomize_va_space
# 0 = disabled, 1 = conservative, 2 = full
# Disable ASLR for current shell (temporary, per-process)
setarch $(uname -m) -R ./target < crash_input
# Or system-wide (DANGEROUS - only for isolated VMs):
# echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
Input Path Matching
The crash may behave differently depending on HOW input reaches the target:
# If fuzzer used stdin:
./target < crash_input
# If fuzzer used file argument:
./target crash_input
# If fuzzer used network:
cat crash_input | nc localhost 8080
# WRONG: Mixing input paths can change behavior!
# Fuzzer: ./target @@ (file)
# You: ./target < crash (stdin) # May not reproduce!
Example: stdin vs file difference:
// Some programs behave differently:
// - stdin may be line-buffered
// - File may be memory-mapped
// - Network may have different read chunk sizes
// This can affect:
// - Buffer contents at crash time
// - Heap layout (different allocation patterns)
// - Race conditions (timing changes)
Quick Reproduction Test Script
#!/bin/bash
# repro_test.sh - Verify crash reproduction
CRASH_INPUT="$1"
TARGET="$2"
EXPECTED_SIGNAL="${3:-11}" # Default: SIGSEGV (11)
echo "[*] Testing reproduction of $(basename $CRASH_INPUT)"
echo "[*] Target: $TARGET"
echo "[*] Expected signal: $EXPECTED_SIGNAL"
# Set up environment
ulimit -c unlimited
export ASAN_OPTIONS="abort_on_error=1:symbolize=1"
# Run 10 times
CRASHES=0
for i in {1..10}; do
timeout 5s $TARGET < "$CRASH_INPUT" 2>/dev/null
EXIT_CODE=$?
# Check for crash signal (128 + signal number)
if [ $EXIT_CODE -gt 128 ]; then
SIGNAL=$((EXIT_CODE - 128))
if [ $SIGNAL -eq $EXPECTED_SIGNAL ] || [ $SIGNAL -eq 6 ]; then
((CRASHES++))
fi
fi
done
echo "[*] Crash rate: $CRASHES/10"
if [ $CRASHES -ge 9 ]; then
echo "[+] Reproduction: RELIABLE"
elif [ $CRASHES -ge 5 ]; then
echo "[!] Reproduction: FLAKY - investigate environment"
else
echo "[-] Reproduction: FAILED - check environment checklist"
fi
Installing WinDbg and Symbol Support
WinDbg Preview (recommended - modern UI):
winget install Microsoft.WinDbg
Windows SDK Debugging Tools (includes cdb.exe for command-line/batch analysis):
# Option 1: Install via winget (Windows SDK)
winget install --source winget --exact --id Microsoft.WindowsSDK.10.0.26100
# Option 2: Download from Microsoft
# https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/
# During installation, select "Debugging Tools for Windows"
# After installation, cdb.exe is located at:
# C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe
# Add to PATH for convenience (run as Administrator):
setx PATH "%PATH%;C:\Program Files (x86)\Windows Kits\10\Debuggers\x64" /M
# Or use full path in scripts:
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe" -z dump.dmp -c "!analyze -v; q"
Configure Symbol Path:
# In WinDbg Settings -> Default Symbol Path, or:
# In WinDbg command window:
.sympath SRV*C:\Symbols*https://msdl.microsoft.com/download/symbols
# Or set environment variable permanently (recommended):
setx _NT_SYMBOL_PATH "SRV*C:\Symbols*https://msdl.microsoft.com/download/symbols"
# Create symbols cache directory
mkdir C:\Symbols
# Reload symbols (in debugger)
.reload /f
Linux Crash Dump Generation and Pwndbg Setup
[!HINT] While Windows uses WinDbg, Linux crash analysis uses GDB enhanced with Pwndbg. This section covers parallel Linux setup.
Installing Pwndbg:
# Install GDB
sudo apt install gdb
# Install Pwndbg (recommended for crash analysis)
cd ~/tools
git clone --depth 1 https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
# Verify installation
gdb -q -ex "quit" 2>&1 | grep -q "pwndbg" && echo "pwndbg installed successfully"
[!WARNING] Pwndbg is installed per-user in
~/.gdbinit. If you runsudo gdb, it uses root's home directory and won't find your pwndbg config. Solutions: For crash analysis of your own compiled test programs, you typically don't need sudo. Only use sudo when attaching to system processes or analyzing setuid binaries.
# Option 1: Use gdb as regular user (recommended for most analysis)
cd ~/crash_analysis_lab
gdb ./vuln_no_protect -c core.dump
# Option 2: If you MUST use sudo (e.g., attaching to privileged process)
sudo -E gdb ./program # -E preserves your environment including HOME
# Option 3: Install pwndbg for root as well
sudo su -
cd /root
git clone https://github.com/pwndbg/pwndbg
cd pwndbg && ./setup.sh
exit
# Option 4: Explicitly source pwndbg in sudo gdb session
sudo gdb -ex "source /home/<YOUR_USER>/tools/pwndbg/gdbinit.py" ./program
Configuring Core Dumps on Linux:
# Check current core dump configuration
cat /proc/sys/kernel/core_pattern
# Enable core dumps for current shell (recommended for learning)
ulimit -c unlimited
[!TIP] For the exercises in this course, you typically only need:
ulimit -c unlimited # In your current shellOn modern Ubuntu/Debian with systemd, cores are handled by
systemd-coredumpeven if you setulimit. Usecoredumpctlto list and debug them.
[!WARNING] Optional: Local core files in CWD (modifies system-wide settings)
If you specifically need core files in your working directory instead of systemd-coredump:
# This is SYSTEM-WIDE and may interfere with other tooling echo 'core.%e.%p' | sudo tee /proc/sys/kernel/core_patternAdditional kernel settings that affect core dumps:
kernel.core_uses_pid: Append PID to core filenamefs.suid_dumpable: Controls dumps for setuid binaries (0=disabled, 1=enabled, 2=suidsafe)
Building a Vulnerable Test Suite for Linux
Create these vulnerable C programs to generate real crashes:
# Create a directory for crash analysis practice
mkdir -p ~/crash_analysis_lab/{src,crashes,cores}
cd ~/crash_analysis_lab/src
vulnerable_suite.c - Save this file for testing multiple vulnerability types:
// ~/crash_analysis_lab/src/vulnerable_suite.c - Compile with different flags for different exercises
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 1. Stack Buffer Overflow
void stack_overflow(char *input) {
char buffer[64];
printf("[*] Copying input to 64-byte buffer...\n");
strcpy(buffer, input); // No bounds check!
printf("[*] Buffer: %s\n", buffer);
}
// 2. Heap Buffer Overflow
void heap_overflow(char *input) {
char *buf = malloc(32);
printf("[*] Allocated 32 bytes at %p\n", buf);
strcpy(buf, input); // Overflow heap buffer
printf("[*] Buffer: %s\n", buf);
free(buf);
}
// 3. Use-After-Free
void use_after_free() {
char *ptr = malloc(64);
strcpy(ptr, "Hello, World!");
printf("[*] Allocated at %p: %s\n", ptr, ptr);
free(ptr);
printf("[*] Freed, now accessing...\n");
printf("[*] UAF read: %s\n", ptr); // UAF read - may print stale data
ptr[0] = 'X'; // UAF write - may corrupt allocator state
}
// 4. Double Free
void double_free() {
char *ptr = malloc(64);
printf("[*] Allocated at %p\n", ptr);
free(ptr);
printf("[*] First free done\n");
free(ptr); // Double free!
}
// 5. NULL Pointer Dereference
void null_deref(int trigger) {
char *ptr = trigger ? malloc(10) : NULL;
printf("[*] ptr = %p\n", ptr);
*ptr = 'A'; // NULL deref if trigger is 0
}
void print_usage(char *prog) {
printf("Usage: %s <test_num> [input]\n", prog);
printf("Tests:\n");
printf(" 1 <input> - Stack overflow (need ~100+ chars)\n");
printf(" 2 <input> - Heap overflow (need ~50+ chars)\n");
printf(" 3 - Use-after-free\n");
printf(" 4 - Double free\n");
printf(" 5 <0|1> - NULL deref (0=crash)\n");
printf("\nExample: %s 1 $(python3 -c \"print('A'*100)\")\n", prog);
}
int main(int argc, char **argv) {
if (argc < 2) { print_usage(argv[0]); return 1; }
int test = atoi(argv[1]);
switch(test) {
case 1: if (argc<3) return 1; stack_overflow(argv[2]); break;
case 2: if (argc<3) return 1; heap_overflow(argv[2]); break;
case 3: use_after_free(); break;
case 4: double_free(); break;
case 5: if (argc<3) return 1; null_deref(atoi(argv[2])); break;
default: print_usage(argv[0]); return 1;
}
return 0;
}
Build the test suite:
cd ~/crash_analysis_lab/src
# 1. Build WITHOUT mitigations (for basic crash analysis)
gcc -g -fno-stack-protector -no-pie -z execstack \
vulnerable_suite.c -o ../vuln_no_protect
# 2. Build WITH ASAN (for detailed memory error reports)
gcc -g -O1 -fsanitize=address -fno-omit-frame-pointer \
vulnerable_suite.c -o ../vuln_asan
# 3. Build with standard protections (see how mitigations affect crashes)
gcc -g vulnerable_suite.c -o ../vuln_protected
Generate your first crashes:
cd ~/crash_analysis_lab
# Enable core dumps
ulimit -c unlimited
# Test 1: Stack overflow - generates a core dump
./vuln_no_protect 1 $(python3 -c "print('A'*200)")
# You should see: Segmentation fault (core dumped)
# Check for core file: ls -la core* (if core_pattern writes to CWD) or use coredumpctl (systemd systems) or look at output of `cat /proc/sys/kernel/core_pattern`
# Test 2: Stack overflow with ASAN - detailed report
./vuln_asan 1 $(python3 -c "print('A'*200)") 2>&1 | tee crashes/stack_asan.txt
# ASAN prints detailed overflow information
# Test 3: Use-after-free with ASAN
./vuln_asan 3 2>&1 | tee crashes/uaf_asan.txt
# Test 4: NULL dereference - generates core dump
./vuln_no_protect 5 0
Using coredumpctl (systemd systems):
sudo apt install systemd-coredump
# List recent core dumps
coredumpctl list
# Show details of most recent crash
coredumpctl info
# Debug most recent crash with GDB
coredumpctl debug
# Debug specific crash by PID
coredumpctl debug 12345
# Extract core dump to file for offline analysis
coredumpctl dump -o crash.core
# View where cores are stored
cat /etc/systemd/coredump.conf
# [Coredump]
# Storage=external # 'external' = /var/lib/systemd/coredump/
# Compress=yes
# MaxUse=1G # Max disk space for cores
Configuring systemd-coredump (/etc/systemd/coredump.conf):
[Coredump]
# Where to store cores: external (disk), journal, or none
Storage=external
# Compress with zstd/lz4
Compress=yes
# Maximum size for stored cores
ProcessSizeMax=2G
# Maximum total disk usage
MaxUse=5G
# Keep cores for this long
KeepFree=1G
After editing, reload: sudo systemctl daemon-reload
ASAN and Core Dumps
[!NOTE] ASAN often exits via SIGABRT, not SIGSEGV. This can be confusing when trying to capture core dumps.
# ASAN default: aborts on error (SIGABRT = signal 6)
# Core dumps may not be generated by default for SIGABRT
# Method 1: Configure ASAN to allow core dumps
export ASAN_OPTIONS="abort_on_error=1:disable_coredump=0"
# Method 2: Check that coredumpctl captures SIGABRT
# coredumpctl list
# Should show crashes with signal=6 (SIGABRT)
# Method 3: Use gdb to catch ASAN abort
echo "1 $(python3 -c "print('A'*200)")" > crash_input
gdb ./vuln_asan
(gdb) run < crash_input
# ASAN prints report, then GDB catches SIGABRT
(gdb) bt full # Get full backtrace
# What "success" looks like with ASAN + core dump:
# 1. ASAN prints detailed error report (allocation/free stacks)
# 2. Program aborts with SIGABRT
# 3. coredumpctl captures the core
# 4. coredumpctl debug lets you examine state at abort
Building Vulnerable Test Suite for Windows
Prerequisites:
- Visual Studio 2022 (Community edition is free) or Build Tools for Visual Studio
- Open "x64 Native Tools Command Prompt for VS 2022" for compilation
vulnerable_suite_win.c - Save this file for Windows crash analysis practice:
// C:\CrashAnalysisLab\src\vulnerable_suite_win.c
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void stack_overflow(char *input) {
char buffer[64];
printf("[*] Copying input to 64-byte buffer...\n");
strcpy(buffer, input);
printf("[*] Buffer: %s\n", buffer);
}
void heap_overflow(char *input) {
char *buf = (char*)HeapAlloc(GetProcessHeap(), 0, 32);
printf("[*] Allocated 32 bytes at %p\n", buf);
strcpy(buf, input);
printf("[*] Buffer: %s\n", buf);
HeapFree(GetProcessHeap(), 0, buf);
}
void use_after_free() {
char *ptr = (char*)HeapAlloc(GetProcessHeap(), 0, 64);
strcpy(ptr, "Hello, World!");
printf("[*] Allocated at %p: %s\n", ptr, ptr);
HeapFree(GetProcessHeap(), 0, ptr);
printf("[*] Freed, now accessing...\n");
printf("[*] UAF read: %s\n", ptr);
ptr[0] = 'X';
}
void double_free() {
char *ptr = (char*)HeapAlloc(GetProcessHeap(), 0, 64);
printf("[*] Allocated at %p\n", ptr);
HeapFree(GetProcessHeap(), 0, ptr);
printf("[*] First free done\n");
HeapFree(GetProcessHeap(), 0, ptr);
}
void null_deref(int trigger) {
char *ptr = trigger ? (char*)HeapAlloc(GetProcessHeap(), 0, 10) : NULL;
printf("[*] ptr = %p\n", ptr);
*ptr = 'A';
}
void integer_overflow(unsigned int size) {
unsigned int alloc_size = size + 16;
if (alloc_size < size) {
printf("[*] Integer overflow detected! alloc_size=%u\n", alloc_size);
}
char *buf = (char*)HeapAlloc(GetProcessHeap(), 0, alloc_size);
printf("[*] Allocated %u bytes at %p\n", alloc_size, buf);
memset(buf, 'A', size);
HeapFree(GetProcessHeap(), 0, buf);
}
void print_usage(char *prog) {
printf("Windows Vulnerable Test Suite\n");
printf("==============================\n");
printf("Usage: %s <test_num> [input]\n\n", prog);
printf("Tests:\n");
printf(" 1 <input> - Stack overflow (need ~100+ chars)\n");
printf(" 2 <input> - Heap overflow (need ~50+ chars)\n");
printf(" 3 - Use-after-free\n");
printf(" 4 - Double free\n");
printf(" 5 <0|1> - NULL deref (0=crash)\n");
printf(" 6 <size> - Integer overflow (try 4294967280)\n");
printf("\nExamples:\n");
printf(" %s 1 ", prog);
printf("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n");
printf(" %s 5 0\n", prog);
}
int main(int argc, char **argv) {
if (argc < 2) { print_usage(argv[0]); return 1; }
int test = atoi(argv[1]);
switch(test) {
case 1: if (argc<3) return 1; stack_overflow(argv[2]); break;
case 2: if (argc<3) return 1; heap_overflow(argv[2]); break;
case 3: use_after_free(); break;
case 4: double_free(); break;
case 5: if (argc<3) return 1; null_deref(atoi(argv[2])); break;
case 6: if (argc<3) return 1; integer_overflow((unsigned int)strtoul(argv[2], NULL, 10)); break;
default: print_usage(argv[0]); return 1;
}
printf("[*] Test completed without crash\n");
return 0;
}
Build the Windows test suite:
# install visual studio community
# Open "x64 Native Tools Command Prompt for VS 2022"
# Create lab directory
mkdir C:\CrashAnalysisLab\src
mkdir C:\CrashAnalysisLab\dumps
cd C:\CrashAnalysisLab\src
# Save the source code above as vulnerable_suite_win.c, then:
# 1. Build WITHOUT mitigations (for basic crash analysis)
# /GS- disables stack cookies, /DYNAMICBASE:NO disables ASLR
cl /Zi /Od /GS- vulnerable_suite_win.c /Fe:..\vuln_win.exe /link /DYNAMICBASE:NO /NXCOMPAT:NO
# 2. Build WITH ASAN (Visual Studio 2019 16.9+ or VS 2022)
cl /Zi /Od /fsanitize=address vulnerable_suite_win.c /Fe:..\vuln_asan.exe
# 3. Build with standard protections (default mitigations)
cl /Zi /Od vulnerable_suite_win.c /Fe:..\vuln_protected.exe
Generate your first Windows crashes:
cd C:\CrashAnalysisLab
# Test 1: Stack overflow
vuln_win.exe 1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# Should crash with access violation
# crash will be at C:\CrashDumps\
# Test 2: Stack overflow with ASAN - detailed report
vuln_asan.exe 1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# ASAN prints detailed overflow information
# Test 3: Use-after-free with ASAN
vuln_asan.exe 3
# Test 4: NULL dereference
vuln_win.exe 5 0
# Test 5: Double free (may not crash immediately without PageHeap)
vuln_win.exe 4
# Directory of C:\CrashDumps
# 01/05/2026 03:11 PM <DIR> .
# 01/05/2026 03:10 PM 9,879,181 vuln_win.exe.7452.dmp
# 01/05/2026 03:11 PM 9,866,817 vuln_win.exe.7756.dmp
# 01/05/2026 03:09 PM 10,543,599 vuln_win.exe.984.dmp
Using PowerShell to generate long strings:
# PowerShell equivalent of Python one-liners
cd C:\CrashAnalysisLab
# Generate 200 'A' characters
$payload = "A" * 200
# Test stack overflow
.\vuln_win.exe 1 $payload
# Test with ASAN
.\vuln_asan.exe 1 $payload 2>&1 | Tee-Object -FilePath C:\CrashDumps\stack_asan.txt
# Test UAF with ASAN
.\vuln_asan.exe 3 2>&1 | Tee-Object -FilePath C:\CrashDumps\uaf_asan.txt
Verify crashes are captured:
# If WER LocalDumps is configured (see next section), check:
dir C:\CrashDumps\
# Or use Event Viewer:
# Windows Logs -> Application -> Look for "Application Error" events
WER/ProcDump Dump Collection
Windows Error Reporting (WER) LocalDumps
WER is Windows' built-in crash reporting. Configure it to save dumps locally:
Enable LocalDumps via Registry:
# Create LocalDumps key for ALL applications
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpFolder /t REG_EXPAND_SZ /d "C:\CrashDumps" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpType /t REG_DWORD /d 2 /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpCount /t REG_DWORD /d 10 /f
# DumpType values:
# 0 = Custom (use CustomDumpFlags)
# 1 = Mini dump
# 2 = Full dump (recommended for crash analysis)
# Create dump directory
mkdir C:\CrashDumps
Per-Application LocalDumps (configure for our test binary):
# Configure for our vulnerable test binary
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\vuln_win.exe" /v DumpFolder /t REG_EXPAND_SZ /d "C:\CrashAnalysisLab\dumps" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\vuln_win.exe" /v DumpType /t REG_DWORD /d 2 /f
# Or for any application
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\target.exe" /v DumpFolder /t REG_EXPAND_SZ /d "C:\CrashDumps\target" /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\target.exe" /v DumpType /t REG_DWORD /d 2 /f
Verify WER is Enabled:
# Check WER service status
Get-Service WerSvc
# Check LocalDumps configuration
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps"
Sysinternals ProcDump
ProcDump provides more control than WER and catches crashes in real-time:
Basic Crash Capture (using our test binary):
winget install Microsoft.Sysinternals.Suite
# First, ensure you've built the test suite (see "Building a Windows Vulnerable Test Suite" above)
cd C:\CrashAnalysisLab
# Options:
# -ma : Full memory dump (recommended)
# -e : Write dump on unhandled exception
# -x : Launch and monitor (below)
# Launch and monitor for crashes
procdump -ma -e -x dumps vuln_win.exe 1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# Monitor already-running process
procdump -ma -e -p <PID>
Advanced ProcDump Usage:
cd C:\CrashAnalysisLab
# Capture on first-chance exceptions (catches more bugs)
procdump -ma -e 1 -x dumps vuln_win.exe 1 AAAA...
# Capture on specific exception codes
procdump -ma -e 1 -f C0000005 -x dumps vuln_win.exe 5 0 # Access violation (NULL deref)
# Capture multiple dumps (for intermittent crashes)
procdump -ma -e -n 5 -x dumps vuln_win.exe 3 # UAF - capture up to 5 dumps
# Monitor service (generic example)
# procdump -ma -e -x C:\Dumps -w ServiceName.exe
ProcDump + Fuzzing Integration:
# Monitor fuzzing target (generic example)
# procdump -ma -e -x C:\FuzzDumps -accepteula target.exe @@
# Batch process dumps from fuzzing run
# for %d in (C:\CrashAnalysisLab\dumps\*.dmp) do cdb -z "%d" -c "!analyze -v; q" >> analysis.txt
Batch Dump Triage with CDB
Analyze multiple dumps automatically:
# Set CDB path (adjust version number as needed)
set CDB="C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe"
# Single dump analysis (use actual dump from ProcDump)
%CDB% -z C:\CrashAnalysisLab\dumps\vuln_win.exe_XXXXXX.dmp
# Or if cdb is in PATH:
cdb -z C:\CrashAnalysisLab\dumps\vuln_win.exe_XXXXXX.dmp -c "!analyze -v; q"
Batch triage script (batch_triage.cmd):
@echo off
# Set path to cdb.exe (adjust if needed)
set CDB="C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe"
for %%f in (C:\CrashAnalysisLab\dumps\*.dmp) do (
echo ======================================== >> triage_report.txt
echo Analyzing: %%f >> triage_report.txt
echo ======================================== >> triage_report.txt
%CDB% -z "%%f" -c ".symfix; .reload; !analyze -v; q" >> triage_report.txt 2>&1
)
echo Done! Results in triage_report.txt
PowerShell Batch Analysis:
# batch_analyze.ps1
# Path to cdb.exe - adjust if your Windows SDK version differs
$cdb = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe"
# Verify cdb exists
if (-not (Test-Path $cdb)) {
Write-Error "cdb.exe not found at $cdb. Install Windows SDK Debugging Tools."
exit 1
}
$dumps = Get-ChildItem "C:\CrashAnalysisLab\dumps\*.dmp"
$results = @()
foreach ($dump in $dumps) {
Write-Host "Analyzing $($dump.Name)..."
$output = & $cdb -z $dump.FullName -c "!analyze -v; !exploitable; q" 2>&1 | Out-String
# Extract key info
$exploitable = if ($output -match "Exploitability Classification: (\w+)") { $Matches[1] } else { "Unknown" }
$bugcheck = if ($output -match "EXCEPTION_CODE: \(NTSTATUS\) (0x[0-9a-f]+)") { $Matches[1] } else { "Unknown" }
$results += [PSCustomObject]@{
DumpFile = $dump.Name
Exploitability = $exploitable
ExceptionCode = $bugcheck
}
}
$results | Export-Csv "triage_results.csv" -NoTypeInformation
$results | Format-Table -AutoSize
Symbols and Symbolization (Linux Quick Reference)
Meaningful backtraces (GDB, CASR, ASAN reports) require symbols.
1. Build with debug info (preferred for labs):
cd ~/crash_analysis_lab/src
sudo apt install -y clang-18 clang-18-dbgsym
clang -g -O1 -fno-omit-frame-pointer vulnerable_suite.c -o ../target
2. Install debug symbols for system libraries (real-world targets):
# Ubuntu/Debian: prefer -dbg packages when available (example: libc6-dbg).
# Some packages ship -dbgsym via Ubuntu's ddebs repository.
# Fedora/RHEL:
# sudo dnf debuginfo-install glibc
3. Use debuginfod for "fetch symbols on demand" (when local symbols unavailable):
# Set URL for your distribution (GDB/LLDB will auto-fetch symbols)
# Ubuntu:
export DEBUGINFOD_URLS="https://debuginfod.ubuntu.com"
# Fedora:
# export DEBUGINFOD_URLS="https://debuginfod.fedoraproject.org"
# Generic fallback:
# export DEBUGINFOD_URLS="https://debuginfod.elfutils.org/"
# Note: If you install -dbgsym packages locally (recommended),
# GDB uses those directly without needing debuginfod.
4. Symbolize raw addresses when you only have PCs:
sudo apt install -y elfutils binutils
cd ~/crash_analysis_lab
# IMPORTANT: Full source info requires debug symbols (-g flag at compile time)
# Verify with: file ./target (look for "with debug_info, not stripped")
# Find function addresses in your binary
nm ./target | grep -E " T " | head -5
# Example output:
# 00000000000012b0 T double_free
# 0000000000001624 T _fini
# 00000000000011e0 T heap_overflow
# 0000000000001000 T _init
# 00000000000013c0 T main
# Symbolize using an address from nm output or a crash backtrace
# (PIE binaries show low addresses; add the runtime base for live processes)
addr2line -e ./target -f -C 0x12b0
# With debug info (-g at compile time):
# double_free
# /home/dev/crash_analysis_lab/src/vulnerable_suite.c:37
#
# Without debug info, you only get the function name:
# double_free
# ??:0
# NOTE: eu-addr2line (from elfutils) may show ??:0 even with debug info
# due to DWARF5 compatibility issues. Prefer addr2line (from binutils).
# eu-addr2line -e ./target -f -C 0x12b0 # May not resolve line numbers
# Dynamic lookup example:
addr2line -e ./target -f -C $(nm ./target | grep " T main" | awk '{print $1}')
Symbol Hygiene Best Practices
- Symbols make or break crash analysis.
- Without them, you're staring at hex addresses instead of function names.
- This section provides best practices for both Windows and Linux.
Linux Symbol Management
1. debuginfod (Automatic Symbol Fetching):
debuginfod can automatically fetch debug symbols on-demand from public servers when you don't have them installed locally.
# Install debuginfod client
sudo apt install debuginfod
# Configure debuginfod URL for your distribution
# Ubuntu:
export DEBUGINFOD_URLS="https://debuginfod.ubuntu.com"
# Fedora:
# export DEBUGINFOD_URLS="https://debuginfod.fedoraproject.org"
# Arch:
# export DEBUGINFOD_URLS="https://debuginfod.archlinux.org"
# For GDB, enable automatic fetching
echo "set debuginfod enabled on" >> ~/.gdbinit
# For LLDB
export LLDB_DEBUGINFOD_URLS="https://debuginfod.e
…(truncated)