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
claude-red is a curated library of offensive security skills designed for the Claude skills system. Each skill is a structured SKILL.md file that primes Claude with expert-level methodology for a spec
The skill file stays in the publisher's repository.
Paste the copied command in a terminal. It installs this SKILL.md into that agent.
npx skills add SnailSploit/Claude-Red --skill offensive-crash-analysis -a cursor -y
claude-red is a curated library of offensive security skills designed for the Claude skills system. Each skill is a structured SKILL.md file that primes Claude with expert-level methodology for a specific attack surface — from SQLi to shellcode, EDR evasion to exploit development.
claude-red is a curated library of offensive security skills designed for the Claude skills system. Each skill is a structured SKILL.md file that primes Claude with expert-level methodology for a specific attack surface — from SQLi to shellcode, EDR evasion to exploit development.
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.
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
When this skill is active:
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.
Before starting this week, ensure you have:
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 |
[!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.
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 │
└─────────────────────────────────────────────────────────────────┘
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
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)
#!/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
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
[!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)
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
[!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
Prerequisites:
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 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"
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
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
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}')
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.elfutils.org/"
[!IMPORTANT] debuginfod vs local debug packages: debuginfod queries remote servers for symbols you don't have locally. If you install debug symbol packages (e.g.,
coreutils-dbgsym), the symbols are stored locally at/usr/lib/debug/and GDB uses them directly without needing debuginfod.
Verification: Don't use debuginfod-find to verify your setup—it only queries remote servers. Instead, verify GDB can find symbols:
# Install local debug symbols (recommended for common packages)
sudo apt install coreutils-dbgsym
# Verify GDB finds the symbols
gdb -q -ex "file /usr/bin/ls" -ex "info sources" -ex "quit" 2>&1 | head -5
# Expected output (with pwndbg you'll see its banner first, then):
# Reading symbols from /usr/bin/ls...
# Reading symbols from /usr/lib/debug/.build-id/xx/xxxxx.debug...
# ... followed by source file paths like ls.c, hash.c, etc.
When to use debuginfod: debuginfod is useful when you're analyzing crashes in binaries where you haven't installed the -dbgsym package.
GDB will automatically fetch symbols from the configured server.
2. Installing Debug Symbol Packages:
sudo apt install libc6-dbgsym # Common libraries
sudo apt install libssl3t64-dbgsym # OpenSSL (Ubuntu 24.04+)
sudo apt install zlib1g-dbgsym # zlib
# For -dbgsym packages (automatically generated):
# Enable ddebs repository first:
#echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse" | \
# sudo tee /etc/apt/sources.list.d/ddebs.list
#sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F2EDC64DC5AEE1F6B9C621F0C8CAB6595FDFF622
#sudo apt update
#sudo apt install package-dbgsym
3. Symbolizing Addresses with addr2line:
# addr2line (from binutils) is preferred for symbolization
# NOTE: eu-addr2line (from elfutils) may show ??:0 even with debug info
# due to DWARF5 compatibility issues. Prefer addr2line.
# IMPORTANT: ASAN reports are ALREADY SYMBOLIZED!
# If your ASAN output shows:
# #0 0x59cc1877a53e in use_after_free src/vulnerable_suite.c:33
# The file:line info (src/vulnerable_suite.c:33) is already there!
# You do NOT need to run addr2line on ASAN output.
#
# addr2line is only needed for:
# - Raw core dumps without ASAN
# - Stripped binaries with separate debug info
# - Non-ASAN crash logs that only show addresses
#
# If ASAN output shows "??:0" instead of file:line, fix symbolization:
# sudo apt install llvm
# export ASAN_SYMBOLIZER_PATH=$(which llvm-symbolizer)
# # Then re-run the crash
# For non-ASAN crashes, use STATIC addresses from nm (not runtime addresses):
# Runtime addresses like 0x59cc1877a53e include PIE base and won't work!
nm ./vuln_asan | grep "T use_after_free"
# Output: 00000000000014a3 T use_after_free
# Use the static address with addr2line:
addr2line -e ./vuln_asan -f -C 0x14a3
# Output:
# use_after_free
# /home/dev/crash_analysis_lab/src/vulnerable_suite.c:27
# addr2line options:
# -f: Show function names
# -C: Demangle C++ symbols
# -i: Show inlined functions
# Example: Look up a function by name and symbolize it
addr2line -e ./vuln_asan -f -C $(nm ./vuln_asan | grep "T print_usage" | awk '{print $1}')
# To convert runtime address to static (for PIE binaries):
# 1. Get the binary's load base from /proc/<pid>/maps or ASAN output
# 2. Subtract base from runtime address
# Example: If base is 0x59cc18779000 and crash addr is 0x59cc1877a53e:
# Static offset = 0x59cc1877a53e - 0x59cc18779000 = 0x153e
# addr2line -e ./vuln_asan -f -C 0x153e
# With debuginfod (for system binaries without local debug packages):
DEBUGINFOD_URLS="https://debuginfod.ubuntu.com" \
addr2line -e /usr/bin/crashed_binary -f -C 0x12345
4. Verifying Symbol Quality:
# Check if binary has debug symbols
file target
# Look for: "with debug_info, not stripped"
# Check symbol table size
nm target | wc -l
# Check DWARF info presence
readelf --debug-dump=info target | head -50
# Verify specific function is symbolized
nm target | grep stack_overflow
1. Configuring _NT_SYMBOL_PATH:
# Set symbol path permanently (user environment)
setx _NT_SYMBOL_PATH "srv*C:\Symbols*https://msdl.microsoft.com/download/symbols"
# Or in current session
set _NT_SYMBOL_PATH=srv*C:\Symbols*https://msdl.microsoft.com/download/symbols
# Multiple symbol sources (local + Microsoft + custom server)
set _NT_SYMBOL_PATH=C:\MySymbols;srv*C:\Symbols*https://msdl.microsoft.com/download/symbols;srv*C:\ThirdParty*https://symbols.example.com/
2. WinDbg Symbol Commands:
# open C:\CrashAnalysisLab\vuln_win.exe in windbg
# Quick setup for Microsoft symbols
.symfix C:\Symbols
# Add additional symbol path
.sympath+ C:\CrashAnalysisLab
.reload
# Show current symbol path
.sympath
# Force reload all symbols
.reload /f
# Reload specific module
# .reload /f ntdll.dll
# Enable verbose symbol loading (debugging symbol issues)
!sym noisy
.reload /f
# Disable noisy mode when done
!sym quiet
# Check symbol status for module
lm m ntdll
# Look for: "pdb symbols" vs "export symbols" vs "no symbols"
# Verify specific symbol loads
x ntdll!Rtl* # List all Rtl* functions - only works with symbols
3. Troubleshooting Symbol Issues:
# Symbol loading failed? Check these:
!sym noisy
.reload /f vuln_win.exe
# Common issues:
# 1. Symbol server timeout → Use local cache
# 2. PDB mismatch → Check build matches binary
# 3. Private symbols missing → Request from vendor
# Verify PDB matches binary
!lmi target
# Check: "Checksum" matches between .exe and .pdb
# Force load unverified symbols (use with caution)
.symopt+ 0x40 # SYMOPT_LOAD_ANYTHING
.reload /f
.symopt- 0x40 # Disable after
_NT_SYMBOL_PATH environment variable set# Load core dump
# If you have a local core file (e.g., from core_pattern writing to CWD):
cd ~/crash_analysis_lab
# run it against Test 1 in line 564
gdb ./vuln_no_protect -c /var/crash/core.vuln_no_protect.1184.1766232655
#Reading symbols from ./vuln_no_protect...
#[New LWP 1184]
#[Thread debugging using libthread_db enabled]
#Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
#Core was generated by `./vuln_no_protect 1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
#Program terminated with signal SIGSEGV, Segmentation fault.
##0 0x4141414141414141 in ?? ()
#------- tip of the day (disable with set show-tips off) -------
#GDB and Pwndbg parameters can be shown or set with show <param> and set <param> <value> GDB commands
#LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
#──────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]#───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
# RAX 0xd5
# RBX 0x7ffcc3436ea8 —▸ 0x7ffcc343748f ◂— './vuln_no_protect'
# RCX 0
# RDX 0
# RDI 0x7ffcc3436b20 —▸ 0x7ffcc3436b50 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH'
# RSI 0xee2e2a0 ◂— 0x66667542205d2a5b ('[*] Buff')
# R8 0
# R9 0
# R10 0xffffffff
# R11 0x202
# R12 3
# R13 0
# R14 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011a0 (__do_global_dtors_aux) ◂— endbr64
# R15 0x74e4537e6000 (_rtld_global) —▸ 0x74e4537e72e0 ◂— 0
# RBP 0x4141414141414141 ('AAAAAAAA')
# RSP 0x7ffcc3436d60 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
# RIP 0x4141414141414141 ('AAAAAAAA')
#───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]#────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
#Invalid address 0x4141414141414141
#─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]#─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
#00:0000│ rsp 0x7ffcc3436d60 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
#... ↓ 7 skipped
#───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]#───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
# ► 0 0x4141414141414141 None
# 1 0x4141414141414141 None
# 2 0x4141414141414141 None
# 3 0x4141414141414141 None
# 4 0x4141414141414141 None
# 5 0x4141414141414141 None
# 6 0x4141414141414141 None
# 7 0x4141414141414141 None
pwndbg> print $_siginfo
#$1 = {
# si_signo = 11,
# si_errno = 0,
# si_code = 1,
# _sifields = {
# _pad = {1094795585, 1094795585, 0 <repeats 26 times>},
# _kill = {
# si_pid = 1094795585,
# si_uid = 1094795585
# },
# _timer = {
# si_tid = 1094795585,
# si_overrun = 1094795585,
# si_sigval = {
# sival_int = 0,
# sival_ptr = 0x0
# }
# },
# ...
#
# Key fields:
# - si_signo = 11 → SIGSEGV
# - si_code = 1 → SEGV_MAPERR (address not mapped to object)
# - si_code = 2 → SEGV_ACCERR (invalid permissions, e.g., NX violation)
# - _sigfault.si_addr → The address that caused the fault
#
# This confirms: Control flow hijack - CPU tried to execute at invalid address 0x4141...
# Check stack for overflow pattern
pwndbg> telescope $rsp 30
#00:0000│ rsp 0x7ffcc3436d60 ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
#... ↓ 14 skipped
#0f:0078│ 0x7ffcc3436dd8 —▸ 0x74e4537e6000 (_rtld_global) —▸ 0x74e4537e72e0 ◂— 0
#10:0080│ 0x7ffcc3436de0 ◂— 0xdd1f52d83d3c0cb0
#11:0088│ 0x7ffcc3436de8 ◂— 0xcb2e72dba51e0cb0
#12:0090│ 0x7ffcc3436df0 ◂— 0x7ffc00000000
#13:0098│ 0x7ffcc3436df8 ◂— 0
#14:00a0│ 0x7ffcc3436e00 ◂— 0
#15:00a8│ 0x7ffcc3436e08 ◂— 3
#16:00b0│ 0x7ffcc3436e10 —▸ 0x7ffcc3436ea0 ◂— 3
#17:00b8│ 0x7ffcc3436e18 ◂— 0xa20d707b5eb54d00
#18:00c0│ 0x7ffcc3436e20 —▸ 0x7ffcc3436e80 ◂— 0
#19:00c8│ 0x7ffcc3436e28 —▸ 0x74e45342a28b (__libc_start_main+139) ◂— mov r15, qword ptr [rip + 0x1d8cf6]
#1a:00d0│ 0x7ffcc3436e30 —▸ 0x7ffcc3436ec8 —▸ 0x7ffcc343756c ◂— 'SHELL=/bin/bash'
#1b:00d8│ 0x7ffcc3436e38 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011a0 (__do_global_dtors_aux) ◂— endbr64
#1c:00e0│ 0x7ffcc3436e40 —▸ 0x7ffcc3436ec8 —▸ 0x7ffcc343756c ◂— 'SHELL=/bin/bash'
#1d:00e8│ 0x7ffcc3436e48 —▸ 0x401485 (main) ◂— endbr64
Command Window: Type commands here Registers Window: View CPU register state Disassembly Window: View assembly code at current IP Memory Window: Inspect memory contents Call Stack Window: View function call hierarchy Locals/Watch Window: Inspect variables
Essential Keyboard Shortcuts:
F5: Go (continue execution)F10: Step overF11: Step intoShift+F9: Set/remove breakpointShift+F11: Step outCtrl+Break: Break into debuggerCrash Scenario: Stack buffer overflow in vulnerable application
Load Crash Dump:
# Open crash dump file
File → Open Dump file → select C:\CrashAnalysisLab\dumps\xxx.dmp (or one of the crashes from linux)
# Or from command line
cd C:\CrashAnalysisLab\dumps
windbg -z xxx.dmp
# Verify dump loaded
!analyze -v
#FILE_IN_CAB: vuln_win.exe_260105_151715.dmp
#COMMENT:
#*** procdump -ma -e -x dumps vuln_win.exe 1 #AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#AAAAAAAAAA
#*** Unhandled exception: C0000005.ACCESS_VIOLATION
#NTGLOBALFLAG: 70
#APPLICATION_VERIFIER_FLAGS: 0
#CONTEXT: (.ecxr)
#rax=00000000000000d7 rbx=000000000052e6b0 rcx=0000000000000000
#rdx=0000000000010000 rsi=0000000000000000 rdi=00000000005342d0
#rip=000000014000744a rsp=000000000014fed8 rbp=0000000000000000
# r8=7ffffffffffffffc r9=0000000000000000 r10=0000000000000000
#r11=000000000014fcd0 r12=0000000000000000 r13=0000000000000000
#r14=0000000000000000 r15=0000000000000000
#iopl=0 nv up ei pl nz na po nc
#cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010204
#vuln_win!stack_overflow+0x3a:
#00000001`4000744a c3 ret
#Resetting default scope
#EXCEPTION_RECORD: (.exr -1)
#ExceptionAddress: 000000014000744a (vuln_win!stack_overflow+0x000000000000003a)
# ExceptionCode: c0000005 (Access violation)
# ExceptionFlags: 00000000
#NumberParameters: 2
# Parameter[0]: 0000000000000000
# Parameter[1]: ffffffffffffffff
#Attempt to read from address ffffffffffffffff
#PROCESS_NAME: vuln_win.exe
#READ_ADDRESS: ffffffffffffffff
#ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s.
#EXCEPTION_CODE_STR: c0000005
#EXCEPTION_PARAMETER1: 0000000000000000
#EXCEPTION_PARAMETER2: ffffffffffffffff
#IP_ON_HEAP: 4141414141414141
#The fault address in not in any loaded module, please check your build's rebase
#log at <releasedir>\bin\build_logs\timebuild\ntrebase.log for module which may
#contain the address if it were loaded.
Initial Analysis Commands:
# Show registers at crash
r
# Display call stack
k
kv # Verbose with frame pointer
kP # With full source paths (if symbols loaded)
kn # With frame numbers
# Show current instruction
u @rip
u @rip L10 # Disassemble 10 instructions
# Examine stack
dps @rsp
dps @rsp L50 # Display 50 pointer-sized values
Using the vuln_win.exe test suite from the "Building a Windows Vulnerable Test Suite" section, generate heap-related crashes:
# Generate heap overflow crash (Test 2)
cd C:\CrashAnalysisLab
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags.exe" /p /enable vuln_win.exe /full
vuln_win.exe 2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#AAAAAAAAAA
# Crash dump saved to C:\CrashDumps\vuln_win.exe.<PID>.dmp
# Generate use-after-free crash (Test 3)
vuln_win.exe 3
# May not crash without PageHeap - see PageHeap lab below
# Generate double-free crash (Test 4)
vuln_win.exe 4
# May not crash without PageHeap - see PageHeap lab below
Load and Analyze Heap Overflow Dump:
# open dump! via GUI: File → Open Crash Dump → select the .dmp file
# Initial analysis
0:000> !analyze -v
# Look for: EXCEPTION_CODE: c0000005 (Access violation)
# Look for: heap_overflow or HeapFree in the stack
Heap Metadata Corruption Pattern (typical output):
# Crash often occurs in HeapFree or subsequent allocation
0:000> k
# ntdll!RtlUserThreadStart$filt$0+0x3f
# ntdll!_C_specific_handler+0x93
# ntdll!RtlpExecuteHandlerForException+0xf
# ntdll!RtlDispatchException+0x437
# ntdll!KiUserExceptionDispatch+0x2e
# vuln_win!__entry_from_strcat_in_strcpy+0x1f
# vuln_win!heap_overflow+0x45
# vuln_win!main+0xdb
# Check heap state
0:000> !heap -s # Summary of all heaps
0:000> !heap -a 0 # Analyze default process heap
# Check what was the destination buffer
0:000> dq @rdx L8
# See how far past the buffer you wrote(WRITE_ADDRESS from !analyze -v)
0:000> !address 0x01fac000
# Examine the vulnerable function
0:000> uf vuln_win!heap_overflow
# Check source if symbols are good
0:000> lsa vuln_win!heap_overflow
Identifying UAF with vuln_win.exe:
# First, enable PageHeap for better UAF detection (run as Administrator)
#cd C:\CrashAnalysisLab
#"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags.exe" /p /enable /full vuln_win.exe
# Now run the UAF test (Test 3)
vuln_win.exe 3
# With PageHeap, this will crash immediately on UAF access
# Load the crash dump
windbg -z C:\CrashDumps\vuln_win.exe.<PID>.dmp
0:000> !analyze -v
# Typical UAF crash pattern
0:000> k
# ChildEBP RetAddr
# 00 ntdll!RtlpLowFragHeapFree+0x42
# 01 vuln_win!use_after_free+0x15
# 02 vuln_win!main+0x89
# Check if address was recently freed (requires PageHeap) -(READ_ADDRESS)
0:000> !heap -p -a 0x01fabfc0
address 0000000001fabfc0 found in
_DPH_HEAP_ROOT @ 1c01000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
1c0c820: 1fab000 2000
00007ffd1074b2d3 ntdll!RtlDebugFreeHeap+0x0000000000000037
00007ffd106e370c ntdll!RtlpFreeHeap+0x000000000000178c
00007ffd10739300 ntdll!RtlFreeHeap+0x0000000000000620
000000014000753d vuln_win!use_after_free+0x000000000000005d
# Don't forget to disable PageHeap after analysis
#"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags.exe" /p /disable vuln_win.exe
Classification: Use-After-Free - object accessed after being freed.
1. Null Pointer Dereference:
0:000> r rax
rax=0000000000000000
0:000> u @rip
mov qword ptr [rax], rcx # Writing to NULL
# Usually not exploitable unless kernel-mode
2. Access Violation (Invalid Address):
0:000> r rax
rax=deadbeefdeadbeef # Invalid address
# Could be:
# - Uninitialized pointer
# - Freed memory
# - Corrupted pointer
3. Stack Cookie Violation:
0:000> k
ntdll!RtlReportCriticalFailure
ntdll!RtlpReportHeapFailure
<Application>!__security_check_cookie
<Application>!function_with_stack_cookie
# Stack overflow detected, but mitigated by /GS
4. Heap Corruption Detected:
0:000> k
ntdll!RtlReportCriticalFailure
ntdll!RtlpHeapHandleError
ntdll!RtlpLogHeapFailure
# Heap allocator detected corruption
# Check nearby allocations for overflow source
Memory Examination:
db <address> # Display bytes
dw <address> # Display words (2 bytes)
dd <address> # Display dwords (4 bytes)
dq <address> # Display qwords (8 bytes)
da <address> # Display ASCII string
du <address> # Display Unicode string
dps <address> # Display pointer-sized values with symbols
Disassembly:
u <address> # Unassemble at address
u <address> L<count> # Unassemble count instructions
ub <address> # Unassemble backward
uf <function> # Unassemble entire function
Breakpoints:
bp <address> # Set breakpoint
bp <module>!<function> # Set breakpoint on function
ba r 1 <address> # Hardware breakpoint on read
ba w 4 <address> # Hardware breakpoint on write (4 bytes)
bl # List breakpoints
bc * # Clear all breakpoints
Execution Control:
g # Go (continue)
p # Step over
t # Step into (trace)
pt # Step to next return
pc # Step to next call
gu # Go up (step out)
Searching Memory:
s -a 0 L?80000000 "string" # Search for ASCII string
s -u 0 L?80000000 "string" # Search for Unicode string
s -b 0 L?80000000 41 41 41 41 # Search for bytes (hex)
Modules and Symbols:
lm # List loaded modules
lm m <module> # Show specific module
x <module>!<symbol> # Examine symbols
dt <structure> # Display type (struct definition)
dt <structure> <addr> # Display structure at address
Heap Commands:
!heap # List all heaps
!heap -s # Heap summary
!heap -a <address> # Analyze heap at address
!heap -p -a <address> # Page heap info for allocation
!heap -x <address> # Search heaps for address
Linux (Pwndbg Equivalents):
| WinDbg Command | Pwndbg Equivalent | Description |
| -------------- | --------------------------------- | --------------------- |
| `db/dd/dq` | `x/b`, `x/w`, `x/g` or `hexdump` | Memory display |
| `dps` | `telescope` | Smart pointer display |
| `u` | `x/i` or `disassemble` | Disassembly |
| `bp` | `break` or `b` | Set breakpoint |
| `ba w` | `watch` or `rwatch` | Hardware watchpoint |
| `g` | `continue` or `c` | Continue execution |
| `p` | `next` or `n` | Step over |
| `t` | `step` or `s` | Step into |
| `s -a` | `search "string"` | Search memory |
| `lm` | `info shared` or `vmmap` | List modules |
| `!heap` | `heap`, `bins`, `arena` | Heap analysis |
| `!analyze -v` | `bt`, `info registers`, `context` | Crash analysis |
Essential Pwndbg Commands for Crash Analysis:
# Start GDB with crash dump
gdb ./target -c core.dump
# Or attach to process
gdb -p <pid>
# Load crash core with pwndbg
pwndbg> # Pwndbg automatically shows context on stop
# Display full context (registers, stack, code, backtrace)
pwndbg> context
# Examine registers
pwndbg> regs
pwndbg> info registers
# Backtrace
pwndbg> bt
pwndbg> bt full
# Memory examination (smart pointer display)
pwndbg> telescope $rsp 20
pwndbg> telescope $rsp 50
# Hexdump
pwndbg> hexdump $rax 64
pwndbg> hexdump 0x7fffffff0000 128
# Memory map
pwndbg> vmmap
pwndbg> vmmap libc
# Check binary protections
pwndbg> checksec
# Search memory
pwndbg> search "AAAA"
pwndbg> search -t qword 0x4141414141414141
pwndbg> search -x "deadbeef"
# Heap analysis (critical for heap bugs)
pwndbg> heap
pwndbg> bins
pwndbg> fastbins
pwndbg> tcache
pwndbg> vis_heap_chunks
# Disassembly
pwndbg> disassemble $rip
pwndbg> nearpc 20
# Find ROP gadgets
pwndbg> rop --grep "pop rdi"
# Cyclic pattern (for offset finding)
pwndbg> cyclic 200
pwndbg> cyclic -l 0x61616174
Stack Overflow Offset Mini-Lab
This mini-lab teaches you to find the exact offset needed to control RIP:
# Step 1: Generate a cyclic pattern (de Bruijn sequence)
cd ~/crash_analysis_lab
python3 -m venv .venv
source .venv/bin/activate
pip install pwntools
python3 -c 'from pwn import *; print(cyclic(200).decode())' > pattern.txt
cat pattern.txt
# aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaa...
# Step 2: Crash the program with the pattern
./vuln_no_protect 1 "$(cat pattern.txt)"
# Segmentation fault (core dumped)
# Step 3: Analyze the crash in GDB/Pwndbg (use the correct crash file- cwd or proper location)
gdb ./vuln_no_protect -c /var/crash/core.vuln_no_protect.3441.1766236363
pwndbg> info reg rip rbp
# rip 0x6161617461616173 0x6161617461616173
# rbp 0x6161617261616171 0x6161617261616171
# Step 4: Find the offset using the pattern in RIP
pwndbg> cyclic -n 4 -l 0x61616173
# Finding cyclic pattern of 4 bytes: b'saaa' (hex: 0x73616161)
# Found at offset 72
# Or using pwntools directly:
python3 -c "from pwn import *; print(cyclic_find(0x61616173))"
# 72
# Step 5: Verify control - overwrite RIP with a known value
python3 << 'EOF'
from pwn import *
p = process(["./vuln_no_protect", "1", b"A"*72 + p64(0xdeadbeefcafebabe)])
p.wait()
EOF
# In GDB, confirm RIP = 0xdeadbeefcafebabe (use the correct crash file)
gdb ./vuln_no_protect -c /var/crash/core.vuln_no_protect.3552.1766237600
pwndbg> info reg rip
# rip 0xdeadbeefcafebabe <-- We control RIP!
[!NOTE] The offset (72 in this example) is the number of bytes from the start of your input to the saved return address. In Week 5, you'll replace
0xdeadbeefcafebabewith actual exploit targets (ROP gadgets, shellcode addresses, etc.).
What Is TTD?:
Why TTD Matters for Crash Analysis:
Example TTD Workflow with vuln_win.exe:
This example uses the stack overflow crash from our test suite:
# Record the crash (if not already done)
# In WinDbg Preview: File → Start debugging → Launch executable (advanced)
# Executable: C:\CrashAnalysisLab\vuln_win.exe
# Arguments: 1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# Check "Record with Time Travel Debugging"
# Click "Record"
# Program crashes, trace saved automatically
# Note: After recording completes, WinDbg loads the trace at position A:0
# (the beginning), NOT at the crash point. You'll see ntdll!LdrInitializeThunk
# in the call stack - this is normal. Use 'g' or '!tt 100' to reach the crash.
0:000> g
# Initial analysis - we're at the crash point (Access violation at 0x4141414141414141)
0:000> k
# Shows call stack at crash - completely corrupted with 0x41414141`41414141
# 00 ntdll!NtRaiseException+0x14
# 01 ntdll!KiUserExceptionDispatch+0x53
# 02 0x41414141`41414141 <- Attempted to execute here!
# 03 0x41414141`41414141 <- Stack smashed with 'AAAAAAAA'
# ... (more 0x41414141`41414141 entries)
0:000> r
# Note: RIP won't show 0x4141414141414141 directly - it points to the
# exception handler (ntdll!NtRaiseException). The crash happened when
# the CPU tried to execute at the corrupted address. Evidence is in the
# call stack above showing the attempted return to 0x41414141`41414141.
# Jump to beginning of trace
0:000> !tt 0
# Now at program start
# Set breakpoint at vulnerable function
0:000> bp vuln_win!stack_overflow
0:000> g
# Breakpoint hit at start of stack_overflow()
# Examine state before overflow
0:000> r
0:000> dps @rsp L10
# Stack looks normal, return address intact
# Step through the function
0:000> p
0:000> p
0:000> p
0:000> p
# At 'add rsp,68h' - about to return
0:000> p
# Crash! Now at 0x41414141`41414141
# Step 8: Use TTD to examine the crash point
# Note: p- steps back to previous "step boundary" (breakpoints, calls),
# not single instructions. To examine state just before crash, use !tt
# with the position shown before the crash:
0:000> !tt 5C:110
# Now at 'add rsp,68h' just before the corrupted ret
# Step 9: Examine the corrupted stack before ret executes
0:000> dps @rsp L10
# Return address at rsp now contains 0x4141414141414141!
# Compare to earlier - the strcpy overwrote the saved return address
# Alternative: p- goes back to step boundaries, not single instructions
0:000> p-
# Goes back to breakpoint at stack_overflow entry (clean stack state)
# Continue to crash
0:000> g
# Crash occurs when function returns to 0x4141414141414141
# Go backward from crash to find corruption point
0:000> g-
# Stops at previous breakpoint - we can examine state just before crash
TTD Data Model Queries:
TTD integrates with WinDbg's data model, enabling powerful queries:
Memory Access Queries:
# Find all memory writes to the return address location
# First, get RSP at function entry to know where return address is stored
0:000> !tt 0
0:000> bp vuln_win!stack_overflow
0:000> g
0:000> r rsp
# rsp=000000000014fed8 # Return address stored here
# Find all writes to this address range
0:000> dx @$cursession.TTD.Memory(0x14fed8, 0x14fee0, "w")
# Returns many entries - each write to this memory region
# Get details of the LAST write (the one that corrupted return address)
0:000> dx @$cursession.TTD.Memory(0x14fed8, 0x14fee0, "w").Last()
# EventType : 0x1
# TimeStart : 59:1A7 [Time Travel]
# AccessType : Write
# IP : 0x140083412
# Address : 0x14fedd
# Size : 0x8
# Value : 0x4141414141414141 <- The overflow!
# OverwrittenValue : 0xa3d5d3000000 <- Original value destroyed
# Navigate to the exact instruction that corrupted the return address
0:000> dx @$cursession.TTD.Memory(0x14fed8, 0x14fee0, "w").Last().TimeStart.SeekTo()
0:000> u @rip L3
# vuln_win!__entry_from_strcat_in_strcpy+0x1f:
# 00000001`40083412 4889040a mov qword ptr [rdx+rcx],rax <- strcpy writing 'AAAAAAAA'
Call Queries:
# Find all calls to strcpy
0:000> dx @$cursession.TTD.Calls("vuln_win!strcpy")
# [0x0] <- One call found
# Find all strcpy-related functions (includes internal helpers)
0:000> dx @$cursession.TTD.Calls("vuln_win!*strcpy*")
# Returns multiple entries for strcpy and its internal routines
# Find calls to stack_overflow with full details
0:000> dx @$cursession.TTD.Calls("vuln_win!stack_overflow")[0]
# EventType : 0x0
# TimeStart : 55:5AA [Time Travel]
# TimeEnd : Max Position [Time Travel] <- Never returned (crashed)
# Function : vuln_win!stack_overflow
# ReturnAddress : 0x14000789d
# Parameters : [expand to see function arguments]
# View function parameters - shows the malicious input!
0:000> dx @$cursession.TTD.Calls("vuln_win!stack_overflow")[0].Parameters
# input : 0xa3d5d3 : "AAAAAAAAAA..." [Type: char *]
# Navigate to specific call
0:000> dx @$cursession.TTD.Calls("vuln_win!stack_overflow")[0].TimeStart.SeekTo()
# Now at the start of stack_overflow() - can step through
Example: Finding Where Return Address Was Overwritten:
# The key insight: use TTD.Memory() to find who wrote to the return address
# Step 1: Find where return address is stored
0:000> !tt 0
0:000> bp vuln_win!stack_overflow
0:000> g
0:000> r rsp
# rsp=000000000014fed8 # Return address at this location
# Step 2: Query all writes to return address location
0:000> dx @$cursession.TTD.Memory(0x14fed8, 0x14fee0, "w").Last()
# Value: 0x4141414141414141 - confirms overflow wrote here
# IP: 0x140083412 - instruction that did the write
# Step 3: Navigate to the corruption point
0:000> dx @$cursession.TTD.Memory(0x14fed8, 0x14fee0, "w").Last().TimeStart.SeekTo()
# Step 4: Examine the guilty instruction
0:000> u @rip L1
# vuln_win!__entry_from_strcat_in_strcpy+0x1f:
# mov qword ptr [rdx+rcx],rax # strcpy's copy loop overwrote return address!
# Step 5: Check registers to see the overflow in action
0:000> r rax
# rax=4141414141414141 # Source data being copied
Example: Tracing User Input Through vuln_win.exe:
# Goal: Trace how command-line input flows to the crash
# Step 1: Find and navigate to main()
0:000> dx @$cursession.TTD.Calls("vuln_win!main")[0]
# TimeStart : 55:4D6 [Time Travel]
# ReturnValue : 0 [Type: int]
# Parameters : [contains argc, argv]
0:000> dx @$cursession.TTD.Calls("vuln_win!main")[0].TimeStart.SeekTo()
# Step 2: Examine argv (RDX = argv in Windows x64 calling convention)
0:000> dps @rdx L4
# 00000000`00a3d590 00000000`00a3d5b0 # argv[0] - program name
# 00000000`00a3d598 00000000`00a3d5d1 # argv[1] - "1" (test number)
# 00000000`00a3d5a0 00000000`00a3d5d3 # argv[2] - overflow input
# 00000000`00a3d5a8 00000000`00000000 # NULL terminator
# Step 3: View the malicious input
0:000> da poi(@rdx+0x10)
# 00000000`00a3d5d3 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
# 00000000`00a3d5f3 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
...
# Step 4: See how input reaches vulnerable function
0:000> dx @$cursession.TTD.Calls("vuln_win!stack_overflow")[0].Parameters
# input : 0xa3d5d3 : "AAAAAAAAAA..." [Type: char *]
# Same address as argv[2] - input passed directly to vulnerable function!
# Step 5: Find all reads from the input buffer to trace data flow
0:000> dx @$cursession.TTD.Memory(0xa3d5d3, 0xa3d5d3+0x100, "r")
# Shows every instruction that read from the malicious input
Practical TTD Crash Analysis: Use-After-Free in vuln_win.exe:
This example demonstrates TTD's power for analyzing UAF bugs:
# Step 1: Enable PageHeap for reliable UAF detection (run as admin)
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags.exe" /p /enable vuln_win.exe /full
# Step 2: Record UAF crash with TTD
# In WinDbg Preview: File → Start debugging → Launch executable (advanced)
# Executable: C:\CrashAnalysisLab\vuln_win.exe
# Arguments: 3
# Check "Record with Time Travel Debugging"
# Click "Record"
# Step 3: Run to the crash
0:000> g
# (24c4.2178): Access violation - code c0000005 (first/second chance not available)
# Time Travel Position: 379:0
# vuln_win!strnlen+0x84:
# 00000001`4005c464 vpcmpeqb ymm1,ymm1,ymmword ptr [rdx] ds:00000000`02393fc0=48
# Step 4: Analyze the crash
0:000> k
# Call stack shows:
# vuln_win!strnlen+0x84 <- Crash here, reading freed memory
# vuln_win!printf+0x41 <- printf trying to print the string
# vuln_win!use_after_free+0x7a <- Our vulnerable function
# vuln_win!main+0xe6
0:000> !analyze -v
# Key findings:
# READ_ADDRESS: 0000000002393fc0 <- Attempting to read freed memory
# Failure.Bucket: INVALID_POINTER_READ_AVRF_c0000005_vuln_win.exe!strnlen
# Step 5: Find all heap frees and identify the one matching crash address
0:000> dx @$cursession.TTD.Calls("ntdll!RtlFreeHeap")
# [0x0], [0x1], [0x2] <- Three frees in the trace
0:000> dx @$cursession.TTD.Calls("ntdll!RtlFreeHeap")[2].Parameters
# [0x0] : 0x2080000 <- HeapHandle
# [0x1] : 0x0 <- Flags
# [0x2] : 0x2393fc0 <- BaseAddress - MATCHES CRASH ADDRESS!
# Step 6: Get details on the free and navigate to it
0:000> dx @$cursession.TTD.Calls("ntdll!RtlFreeHeap")[2]
# TimeStart : 373:118 [Time Travel]
# ReturnAddress : 0x14000753d
# ReturnValue : 0x1 <- Free succeeded
0:000> dx @$cursession.TTD.Calls("ntdll!RtlFreeHeap")[2].TimeStart.SeekTo()
0:000> k
# 00 ntdll!RtlFreeHeap
# 01 vuln_win!use_after_free+0x5d <- free() called here (line 27)
# 02 vuln_win!main+0xe6
# Step 7: Navigate to use_after_free function entry
0:000> dx @$cursession.TTD.Calls("vuln_win!use_after_free")[0]
# TimeStart : 366:1218 <- Function entry
# TimeEnd : Max Position <- Never returned (crashed)
0:000> dx @$cursession.TTD.Calls("vuln_win!use_after_free")[0].TimeStart.SeekTo()
0:000> k
# Now at the start of use_after_free()
# Step 8: Examine the freed memory at crash point
0:000> !tt 379:0
0:000> !address 0x2393fc0
# "Address could not be mapped" - PageHeap unmapped the page after free!
0:000> dc 0x2393fc0 L10
# 02393fc0 6c6c6548 57202c6f 646c726f c0c00021 Hello, World!...
# 02393fd0 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0 ................
# The string data is still there, but 0xc0 fill pattern shows it's freed!
# Timeline Summary:
# Position 366:1218 - use_after_free() called
# Position 373:118 - free(ptr) called, memory freed
# Position 379:0 - printf(ptr) crashes trying to read freed memory
# Step 9: Don't forget to disable PageHeap after analysis
# "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\gflags.exe" /p /disable vuln_win.exe
TTD Best Practices:
!positions to bookmark important execution pointsTTD Limitations:
[!IMPORTANT] In real-world vulnerability research, especially on Windows, you rarely have source code. The sanitizer-based techniques in Day 2 require recompilation. This section covers black-box techniques for when you can't recompile.
When to Use Black-Box Analysis:
Setup: Creating a Symbol-less Binary for Practice:
# Compile without debug symbols to simulate closed-source binary
cl /O2 /GS- src\vulnerable_suite_win.c /Fe:vuln_win_nosym.exe
# Record crash with TTD
# WinDbg Preview: File → Start debugging → Launch executable (advanced)
# Executable: C:\CrashAnalysisLab\vuln_win_nosym.exe
# Arguments: 1 AAAA...(200+ chars)
# Check "Record with Time Travel Debugging"
Initial Crash Assessment:
# After crash, examine the state
0:000> g
# (2194.20e0): Access violation - code c0000005
# Time Travel Position: 61:0
# 41414141`41414141 ?? ???
# RIP is completely controlled - classic stack overflow!
0:000> r
# rip=4141414141414141 # Controlled!
# rbx=4141414141414141 # Also controlled
# rdi=4141414141414141 # Also controlled
# rsp=000000398bb0fe30
# Call stack is destroyed - all 0x41414141
0:000> k
# 00 0x41414141`41414141
# 01 0x41414141`41414141
# 02 0x41414141`41414141
# ...
When RIP is Invalid - Use TTD to Go Back:
# Can't disassemble at invalid RIP
0:000> u @rip-20 L30
# ^ Memory access error # Expected - RIP points to garbage
# Use TTD to find last valid state (position before crash)
0:000> !tt 60:0
0:000> k
# Now we see the real call stack with module offsets:
# 00 ntdll!NtWriteFile+0x14
# 01 KERNELBASE!WriteFile+0x8d
# 02 vuln_win_nosym+0xf186 <- CRT printf internals
# ...
# 0a vuln_win_nosym+0x146b <- Caller
# 0b vuln_win_nosym+0x1098 <- Vulnerable function (returns to 0x41414141)
Module and Section Analysis:
# List loaded modules
0:000> lm
# start end module name
# 00007ff7`73770000 00007ff7`73798000 vuln_win_nosym (n