anthropics/skills180koffensive-crash-analysis
Install
Send this to Claude Code, Codex or Cursor. The agent checks the Skill for safety first and installs it only after you confirm.
读取 https://funcoding.ai/skills/snailsploit/claude-red/offensive-crash-analysis/install.md ,按里面的步骤帮我安装这个 Skill。
SKILL.md
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.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
Windows Symbol Management
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
Cross-Platform Symbol Checklist
- Linux
- debuginfod URL configured
- Debug packages installed for target libraries
- Binary built with -g flag
- eu-addr2line available for batch symbolization
- Windows
-
_NT_SYMBOL_PATHenvironment variable set - Symbol cache directory exists and writable
- Microsoft symbol server accessible
- PDB files match target binaries (same build)
-
- Both Platforms
- Third-party library symbols obtained
- Symbol server accessible (or offline cache populated)
- Test symbolization: verify backtrace shows function names
Analyzing Crash in Pwndbg
# 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
WinDbg User Interface Overview
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 debugger
Analyzing Stack Buffer Overflow Crashes
Crash 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
Analyzing Heap Corruption Crashes
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.
Common Crash Patterns and Identification
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
Essential WinDbg Commands Reference
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 |
Pwndbg Crash Analysis Commands
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.).
Time Travel Debugging (TTD)
What Is TTD?:
- Time Travel Debugging (TTD) is Microsoft's revolutionary debugging technology that records program execution and allows stepping backward in time.
- Unlike traditional debugging where you can only step forward, TTD captures the entire execution trace, enabling you to navigate to any point in the program's history.
Why TTD Matters for Crash Analysis:
- No More "Oops, I stepped too far": Step backward to inspect the exact state before a crash
- Perfect Reproducibility: Recorded traces can be replayed indefinitely with identical behavior
- Non-deterministic Bug Analysis: Catches race conditions, timing issues, and heisenbug patterns
- Offline Analysis: Record on one machine, analyze on another
- Root Cause Discovery: Trace backward from crash to find where corruption originated
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:
- Record Minimal Scope: Only record the crashing process to keep traces manageable
- Use Breakpoints Wisely: Set breakpoints before recording to stop at interesting points
- Leverage Data Model: TTD queries are more powerful than manual navigation
- Save Interesting Positions: Use
!positionsto bookmark important execution points - Combine with Memory Analysis: Use TTD to find when corruption occurred, traditional commands to analyze it
- Enable PageHeap for Heap Bugs: TTD + PageHeap gives you allocation/free stacks AND time travel
TTD Limitations:
- Trace Size: Long-running processes create large trace files (GBs)
- Performance: Recording adds ~10-20x slowdown
- Windows Only: No Linux equivalent (use rr instead - see Day 4)
- No Kernel Mode: TTD is user-mode only
- x64 Only: No 32-bit support in modern versions
- WinDbg Preview Required: Classic WinDbg from Windows SDK doesn't include TTD
Black-Box Crash Analysis
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:
- Analyzing crashes in closed-source software (Microsoft, Adobe, etc.)
- Third-party libraries shipped as binaries
- Malware analysis
- CTF challenges without source
- Production crash dumps from customers
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"
Manual Crash State Analysis
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 (no symbols)
# 00007ff9`179e0000 00007ff9`17c47000 ntdll (pdb symbols)
# Get PE header info - entry point, sections
0:000> !dh vuln_win_nosym
# 8664 machine (X64)
# 1000 base of code
# 16D4 address of entry point <- Entry point offset
# 15200 size of code
# 17000 [ 240] address [size] of Import Address Table
# Check exception directory for function boundaries
0:000> .fnent vuln_win_nosym+0x1010
# BeginAddress = 00000000`00001010
# EndAddress = 00000000`000012a3 <- Function spans 0x1010-0x12a3
# UnwindInfoAddress = 00000000`0002003c
Reverse Engineering the Vulnerable Function:
# Disassemble the function that crashed (identified from call stack)
0:000> u vuln_win_nosym+0x1010 L50
# Look for function prologue
vuln_win_nosym+0x1010:
# mov qword ptr [rsp+10h],rbx # Save rbx
# push rdi # Save rdi
# sub rsp,60h # Allocate 0x60 bytes stack frame
# Find the vulnerable call - look for string copy patterns
# vuln_win_nosym+0x1071:
# lea rcx,[rsp+20h] # Destination: stack buffer at rsp+0x20
# vuln_win_nosym+0x1076:
# call vuln_win_nosym+0x15b90 # <- This is strcpy!
# Function epilogue shows where crash happens
# vuln_win_nosym+0x1098:
# xor eax,eax
# mov rbx,qword ptr [rsp+78h]
# add rsp,60h
# pop rdi
# ret # <- Returns to corrupted address!
Identifying Library Functions Without Symbols:
# Dump Import Address Table to identify API calls
0:000> dps vuln_win_nosym+0x17000 L20
# 00007ff7`73787000 ntdll!RtlAllocateHeap
# 00007ff7`73787008 KERNEL32!HeapFreeStub
# 00007ff7`73787010 KERNEL32!GetProcessHeap
# 00007ff7`737870d8 KERNEL32!GetStdHandleStub
# 00007ff7`737870e0 KERNEL32!WriteFile
# Identify strcpy by its implementation pattern
0:000> u vuln_win_nosym+0x15b90 L15
# Byte-by-byte copy loop with null check = strcpy
# mov r11,rcx # Save dest
# sub rcx,rdx # Calculate offset
# mov al,byte ptr [rdx] # Load source byte
# mov byte ptr [rdx+rcx],al # Store to dest
# test al,al # Check for null
# je <end> # Exit if null
# inc rdx # Next byte
# ...
String Search for Context Clues:
# Search for interesting strings in the binary
0:000> s -a vuln_win_nosym L28000 "overflow"
# 00007ff7`7378741c "overflow detected! alloc_size=%u."
# 00007ff7`737874dd "overflow (need ~100+ chars)."
# View the strings
0:000> da 00007ff7`7378741c
# "overflow detected! alloc_size=%u."
# Search for function names, error messages
0:000> s -a vuln_win_nosym L28000 "Test"
# 00007ff7`73787473 "Test Suite."
0:000> s -a vuln_win_nosym L28000 "free"
# 00007ff7`737873f2 "free done."
Pattern Recognition Without Symbols:
# 1. Stack Overflow Pattern (what we found):
# - RIP contains controlled data (0x41414141...)
# - Stack filled with repeating pattern
# - Function epilogue (add rsp, XX / ret) leads to crash
# 2. Heap Corruption Pattern:
# - Crash in ntdll!Rtl*Heap* functions
# - Invalid forward/backward pointers
# - Corrupted heap metadata
# 3. Use-After-Free Pattern:
# - Crash reading/writing freed memory
# - PageHeap shows 0xc0c0c0c0 fill pattern
# - !address shows "could not be mapped"
# 4. Type Confusion Pattern:
# - Valid object pointer
# - Wrong vtable being used
# - Field access at unexpected offset
WinDbg Scripting for Black-Box Analysis
Automated Crash Classification Script:
// crash_classify.js - Save to C:\CrashAnalysisLab\crash_classify.js
// Run with: .scriptrun C:\CrashAnalysisLab\crash_classify.js
"use strict";
function initializeScript() {
return [new host.apiVersionSupport(1, 7)];
}
function invokeScript() {
var dbgControl = host.namespace.Debugger.Utility.Control;
var regs = host.currentThread.Registers.User;
host.diagnostics.debugLog("=== BLACK-BOX CRASH ANALYSIS ===\n\n");
// Get exception record
host.diagnostics.debugLog("[*] Exception Record:\n");
try {
var exrOutput = dbgControl.ExecuteCommand(".exr -1");
for (var line of exrOutput) {
host.diagnostics.debugLog(" " + line + "\n");
}
} catch (e) {
host.diagnostics.debugLog(" Could not get exception record\n");
}
// Check RIP validity and controlled input patterns
host.diagnostics.debugLog("\n[*] Register Analysis:\n");
var patterns = {
41414141: "ASCII 'AAAA' - controlled input!",
42424242: "ASCII 'BBBB' - controlled input!",
43434343: "ASCII 'CCCC' - controlled input!",
cccccccc: "Uninitialized stack (MSVC debug)",
cdcdcdcd: "Uninitialized heap (MSVC debug)",
c0c0c0c0: "PageHeap freed memory",
feeefeee: "Freed heap memory (MSVC debug)",
baadf00d: "Uninitialized heap (LocalAlloc)",
deadbeef: "Marker value (test/exploit)",
};
var criticalRegs = ["Rip", "Rax", "Rbx", "Rcx", "Rdx", "Rsi", "Rdi", "Rsp"];
var ripControlled = false;
for (var i = 0; i < criticalRegs.length; i++) {
var regName = criticalRegs[i];
try {
var regVal = regs[regName];
var val = regVal.toString(16);
// Pad to 16 chars
while (val.length < 16) {
val = "0" + val;
}
var analysis = "";
for (var pattern in patterns) {
if (val.toLowerCase().indexOf(pattern) !== -1) {
analysis = " <- " + patterns[pattern];
if (regName === "Rip") {
ripControlled = true;
}
break;
}
}
host.diagnostics.debugLog(
" " + regName + ": 0x" + val + analysis + "\n",
);
} catch (e) {
host.diagnostics.debugLog(" " + regName + ": <error reading>\n");
}
}
// Exploitability assessment
host.diagnostics.debugLog("\n[*] Exploitability Assessment:\n");
if (ripControlled) {
host.diagnostics.debugLog(
" [CRITICAL] RIP contains controlled pattern - EXPLOITABLE!\n",
);
host.diagnostics.debugLog(
" Stack overflow with RIP control detected.\n",
);
} else {
// Try to disassemble at RIP
try {
var uOutput = dbgControl.ExecuteCommand("u @rip L1");
var instruction = "";
for (var line of uOutput) {
instruction += line + " ";
}
if (instruction.indexOf("???") !== -1) {
host.diagnostics.debugLog(
" [HIGH] Invalid instruction at RIP - likely controlled\n",
);
} else if (
instruction.indexOf("mov") !== -1 &&
instruction.indexOf("[") !== -1
) {
host.diagnostics.debugLog(
" [HIGH] Crash on memory access - potential read/write primitive\n",
);
} else if (
instruction.indexOf("call") !== -1 &&
instruction.indexOf("[") !== -1
) {
host.diagnostics.debugLog(
" [HIGH] Crash on indirect call - potential code execution\n",
);
} else {
host.diagnostics.debugLog(
" [MEDIUM] Examine crash context for exploitability\n",
);
}
} catch (e) {
host.diagnostics.debugLog(
" [HIGH] Cannot disassemble at RIP - address likely controlled\n",
);
}
}
// Stack analysis for return addresses
host.diagnostics.debugLog("\n[*] Stack Analysis (valid return addresses):\n");
try {
var stackOutput = dbgControl.ExecuteCommand("dps @rsp L20");
var validAddrs = 0;
var controlledAddrs = 0;
for (var line of stackOutput) {
var lineStr = line.toString();
if (
lineStr.indexOf("41414141") !== -1 ||
lineStr.indexOf("42424242") !== -1
) {
controlledAddrs++;
}
if (lineStr.indexOf("!") !== -1) {
validAddrs++;
host.diagnostics.debugLog(" " + lineStr + "\n");
}
}
host.diagnostics.debugLog(
"\n Valid return addresses: " + validAddrs + "\n",
);
host.diagnostics.debugLog(
" Controlled values on stack: " + controlledAddrs + "\n",
);
} catch (e) {
host.diagnostics.debugLog(" <error reading stack>\n");
}
host.diagnostics.debugLog("\n=== END ANALYSIS ===\n");
}
Usage:
# First go to the crash point
0:000> g
# Or for TTD traces, go to crash position
# 0:000> !tt 61:0
# Run the analysis script (uses invokeScript automatically)
0:000> .scriptrun C:\CrashAnalysisLab\crash_classify.js
#JavaScript script successfully loaded from 'C:\CrashAnalysisLab\crash_classify.js'
#=== BLACK-BOX CRASH ANALYSIS ===
#
#[*] Exception Record:
# ExceptionAddress: 4141414141414141
# ExceptionCode: c0000005 (Access violation)
# ExceptionFlags: 00000000
# NumberParameters: 2
# Parameter[0]: 0000000000000000
# Parameter[1]: 0000414141414141
# Attempt to read from address 0000414141414141
#[*] Register Analysis:
# Rip: <error reading>
# Rax: <error reading>
# Rbx: <error reading>
# Rcx: <error reading>
# Rdx: <error reading>
# Rsi: <error reading>
# Rdi: <error reading>
# Rsp: <error reading>
#[*] Exploitability Assessment:
# [HIGH] Cannot disassemble at RIP - address likely controlled
#[*] Stack Analysis (valid return addresses):
# 00000039`8bb0feb8 00007ff9`17a6c510 ntdll!RtlUserThreadStart
# Valid return addresses: 1
# Controlled values on stack: 16
#=== END ANALYSIS ===
Quick Black-Box Analysis Commands:
# List modules (identify target binary without symbols)
0:000> lm
# start end module name
# 00007ff7`73770000 00007ff7`73798000 vuln_win_nosym (no symbols)
# 00007ff9`179e0000 00007ff9`17c47000 ntdll (pdb symbols)
# Get exception details
0:000> .exr -1
# ExceptionAddress: 4141414141414141
# ExceptionCode: c0000005 (Access violation)
# Check all registers
0:000> r
# Short call stack - shows controlled return addresses
0:000> k 5
# 00 0x41414141`41414141
# 01 0x41414141`41414141
# ... (all corrupted)
# Check stack for controlled values - classic overflow pattern
0:000> dps @rsp L30
# 00000039`8bb0fe30 41414141`41414141 <- Controlled!
# 00000039`8bb0fe38 41414141`41414141
# ... (16 entries of 0x41414141)
# 00000039`8bb0feb8 00007ff9`17a6c510 ntdll!RtlUserThreadStart <- Only valid addr
# Search binary for strings (clues about functionality)
0:000> s -a vuln_win_nosym L28000 "overflow"
# 00007ff7`7378741c "overflow detected..."
# 00007ff7`737874dd "overflow (need ~100+ chars)..."
GDB/Pwndbg Black-Box Script:
# blackbox_analyze.py - Source this in GDB: source blackbox_analyze.py
import gdb
class BlackBoxAnalyze(gdb.Command):
"""Analyze crash without symbols"""
def __init__(self):
super(BlackBoxAnalyze, self).__init__("bb-analyze", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
print("=== BLACK-BOX CRASH ANALYSIS ===\n")
# Get exception record
try:
pc = int(gdb.parse_and_eval("$pc"))
print(f"[*] Crash at: {hex(pc)}")
except:
print("[-] Could not get program counter")
return
# Check instruction at crash
print("\n[*] Crash Instruction:")
try:
gdb.execute(f"x/10i {pc-20}")
except gdb.MemoryError:
print(f" Cannot disassemble at {hex(pc)} - address not mapped")
print(" (PC likely contains attacker-controlled value)")
# Register analysis
print("\n[*] Register Analysis:")
controlled_patterns = [0x41414141, 0x42424242, 0x61616161]
for reg in ["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "r8", "r9"]:
try:
val = int(gdb.parse_and_eval(f"${reg}"))
analysis = ""
# Check for controlled input
for pattern in controlled_patterns:
if (val & 0xffffffff) == pattern or (val >> 32) == pattern:
analysis = " <- CONTROLLED INPUT!"
break
# Check for null
if val == 0:
analysis = " <- NULL"
# Check for heap-like address
if 0x10000 < val < 0x800000000000:
analysis = analysis or " <- possible heap/data"
print(f" {reg}: {hex(val)}{analysis}")
except:
pass
# Stack analysis
print("\n[*] Stack Contents (potential return addresses):")
try:
gdb.execute("x/20gx $rsp")
except:
gdb.execute("x/20wx $esp")
# Exploitability hints
print("\n[*] Exploitability Assessment:")
# Check if PC is controlled
pc_controlled = False
for pattern in controlled_patterns:
if (pc & 0xffffffff) == pattern or (pc >> 32) == pattern:
print(" [CRITICAL] Program counter contains controlled input!")
pc_controlled = True
break
# Check for common exploit marker patterns
marker_patterns = {
0xdeadbeef: "DEADBEEF marker",
0xcafebabe: "CAFEBABE marker",
0xdeadc0de: "DEADC0DE marker",
0xfeedface: "FEEDFACE marker",
}
if not pc_controlled:
pc_lower = pc & 0xffffffff
pc_upper = (pc >> 32) & 0xffffffff
for pattern, name in marker_patterns.items():
if pc_lower == pattern or pc_upper == pattern:
print(f" [CRITICAL] PC contains {name} - likely controlled!")
pc_controlled = True
break
# Check if PC is in non-executable region (indicates control)
if not pc_controlled and pc > 0x7f0000000000:
print(" [WARNING] PC in high memory - possible stack/heap address")
elif not pc_controlled and pc < 0x10000:
print(" [WARNING] PC near NULL - possible partial overwrite")
elif not pc_controlled:
print(" [INFO] PC not directly controlled - check for indirect paths")
BlackBoxAnalyze()
print("Black-box analysis command loaded. Use: bb-analyze")
Lab: Root Cause ≠ Crash Site
The Problem:
- Heap corruption crashes often occur in
malloc()/free()consistency checks - The actual overflow/UAF happened earlier—sometimes thousands of instructions before
- Without understanding this, you'll waste hours staring at allocator internals
Lab Setup: The Delayed Corruption Bug
vulnerable_delayed.c - A bug where corruption and crash are separated:
// ~/crash_analysis_lab/src/vulnerable_delayed.c
// The bug is in process_data(), but the crash is in cleanup()
// This version uses HEAP allocations to demonstrate delayed corruption
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct metadata {
size_t size;
char* data;
struct metadata* next;
};
struct metadata* head = NULL;
void add_entry(const char* input) {
struct metadata* entry = malloc(sizeof(struct metadata));
entry->size = strlen(input);
entry->data = malloc(entry->size + 1);
strcpy(entry->data, input);
entry->next = head;
head = entry;
printf("[+] Added entry at %p (data=%p, next=%p)\n", entry, entry->data, entry->next);
}
void process_data(const char* input) {
if (head == NULL) return;
char* buffer = malloc(16);
printf("[*] Allocated 16-byte buffer at %p\n", buffer);
printf("[*] About to copy %zu bytes into 16-byte buffer...\n", strlen(input));
strcpy(buffer, input); // OVERFLOW if input > 16 bytes!
printf("[*] Copy complete (overflow occurred if input > 16 bytes)\n");
// Note: we intentionally don't free buffer here to keep corruption intact
}
void cleanup() {
printf("[*] Starting cleanup - traversing linked list...\n");
struct metadata* current = head;
int i = 0;
while (current) {
printf("[*] Entry %d: current=%p, data=%p, next=%p\n",
i++, current, current->data, current->next);
struct metadata* next = current->next;
free(current->data);
free(current);
current = next;
}
printf("[*] Cleanup complete\n");
}
int main(int argc, char** argv) {
if (argc < 2) {
printf("Usage: %s <input>\n", argv[0]);
printf("Example: %s $(python3 -c \"print('A'*200)\")\n", argv[0]);
return 1;
}
printf("[*] Creating linked list entries...\n");
add_entry("normal entry 1");
add_entry("normal entry 2");
printf("\n[*] Processing user input (%zu bytes)...\n", strlen(argv[1]));
process_data(argv[1]);
printf("\n[*] Adding more entries after overflow...\n");
add_entry("post-overflow entry");
printf("\n[*] Starting cleanup (CRASH likely here, not in process_data!)...\n");
cleanup();
printf("[*] Program completed successfully\n");
return 0;
}
Exercise Part 1: Observe the Problem (Without ASAN)
# Build WITHOUT sanitizers
cd ~/crash_analysis_lab
gcc -g -fno-stack-protector -o delayed_vuln src/vulnerable_delayed.c
source .venv/bin/activate
# Trigger the bug with a LARGE overflow (200+ bytes needed to corrupt heap structures)
./delayed_vuln $(python3 -c "print('A'*200)")
# Analyze with GDB
gdb ./delayed_vuln
(gdb) run $(python3 -c "print('A'*200)")
# CRASH in free() or during list traversal
(gdb) bt
# Backtrace shows crash in add_entry() NOT in process_data() where the bug actually is!
What You'll See:
- Crash occurs in
add_entry()orcleanup()- NOT inprocess_data()! - The error message is
malloc(): corrupted top size- heap corruption detected - Backtrace shows allocator functions (
_int_malloc,malloc_printerr, etc.) - The actual vulnerable
strcpy()inprocess_data()is NOT visible in the backtrace - Signal is SIGABRT (from allocator detecting corruption)
Example backtrace (notice process_data is NOT shown):
#0 __pthread_kill_implementation at ./nptl/pthread_kill.c:44
#1-4 ... (signal handling) ...
#5 __libc_message_impl at ../sysdeps/posix/libc_fatal.c:134
#6 malloc_printerr (str="malloc(): corrupted top size")
#7 _int_malloc at ./malloc/malloc.c:4447
#8 __GI___libc_malloc
#9 add_entry (input="post-overflow entry") at vulnerable_delayed.c:17 <-- CRASH HERE
#10 main at vulnerable_delayed.c:78
The crash is in add_entry() during a malloc() call - the allocator detected that heap metadata was corrupted. But the actual bug is in process_data() which overwrote heap structures with 'A's.
Exercise Part 2: Reproduce with ASAN
# Build WITH ASAN
gcc -g -O0 -fsanitize=address -fno-omit-frame-pointer \
-U_FORTIFY_SOURCE -o delayed_vuln_asan src/vulnerable_delayed.c
# Now ASAN catches the overflow AT THE SOURCE (even with small overflow!)
./delayed_vuln_asan $(python3 -c "print('A'*20)")
ASAN Output (shows TRUE root cause):
[*] Creating linked list entries...
[+] Added entry at 0x503000000040 (data=0x502000000010, next=(nil))
[+] Added entry at 0x503000000070 (data=0x502000000030, next=0x503000000040)
[*] Processing user input (20 bytes)...
[*] Allocated 16-byte buffer at 0x502000000050
[*] About to copy 20 bytes into 16-byte buffer...
=================================================================
==3871==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000060 at pc 0x7174a0ca7923 bp 0x7ffee2ef7d60 sp 0x7ffee2ef7508
WRITE of size 21 at 0x502000000060 thread T0
#0 0x7174a0ca7922 in strcpy ../../../../src/libsanitizer/asan/asan_interceptors.cpp:563
#1 0x6273088c443c in process_data src/vulnerable_delayed.c:36
#2 0x6273088c46e8 in main src/vulnerable_delayed.c:75
#3 0x7174a082a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#4 0x7174a082a28a in __libc_start_main_impl ../csu/libc-start.c:360
#5 0x6273088c41e4 in _start (/home/dev/crash_analysis_lab/delayed_vuln_asan+0x11e4) (BuildId: 5ba4175df72d24b28ce5
Similar Skills
anthropics/skills180k
anthropics/skills180kbrand-guidelines
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
AI & agents
anthropics/skills180kinternal-comms
A set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.).
AI & agents
anthropics/skills180kmcp-builder
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
AI & agents
anthropics/skills180kalgorithmic-art
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
AI & agents
anthropics/skills180kacademy-guide
Stop and check this skill before finishing any reply to a question about how to use Claude or a Claude product — it recommends matching courses, tutorials, and use cases from Claude Academy (academy.claude.com), Anthropic's learning hub. Trigger on: "how do I", "how can I", "getting started with", "what can Claude do", "teach me", "learn to use"; questions about artifacts, projects, skills, plugins, connectors, MCP; requests about rolling Claude out to a team, class, or organization; and any ask for training materials, onboarding content, or learning resources. Use it when the user is learning how to use a feature or product — not when they are mid-task and just want the task done. This skill composes with other skills: after consulting product documentation to answer how a Claude feature works, also check here for a matching course or tutorial — a docs-grounded answer and an Academy recommendation belong together. Only recommend on a strong match; never invent Academy content.
AI & agents