When to use / when not to use
- Use: Windows user-mode process debugging (launch/attach, breakpoints, single-stepping, registers); crash/exception analysis (
!analyze -v); kernel debugging (two-machine/VM/local kd); driver and kernel module analysis (together with [[re-kernel]]); crash dump (minidump/full dump) analysis - Use: Time Travel Debugging (TTD) —
ttd.exeafter recording a .run trace, replay at any point, suitable for verification/decryption logic where "reproduce once and investigate slowly" (see [[commands]] TTD section) - Don't use: Linux targets (use [[re-gdb]]); macOS targets ([[re-lldb]]); when you only need lightweight GUI debugging ([[re-x64dbg]] is faster)
- Don't use: when only doing static analysis/decompilation ([[re-ida]] / [[re-ghidra]] / [[re-binaryninja]]); purely static workflows that don't need to interact with the running state don't need a debugger
- Don't use: attaching across the boundary to Windows processes inside WSL ([[re-analyze/platform-tips]] WSL branch: for cross-boundary use Windows-side tools)
Tool preparation
Refer to [[re-analyze/platform-tips]] Windows branch — attaching requires administrator privileges; debugging is dynamic execution and is performed inside a sandbox by default ([[re-analyze/platform-tips]] highest principle).
WinDbg (Microsoft Store version / Windows SDK / WDK)
- Store version (new UI WinDbg):
winget install Microsoft.WinDbg(or search "WinDbg" in the Microsoft Store) - Classic version: in the Windows SDK installer, check "Debugging Tools for Windows";
choco install windbg - WDK (includes kernel debugging tools kd.exe/kdnet.exe):
winget install Microsoft.WindowsWDKor check WDK in the Visual Studio installer - Verify: open WinDbg,
File > Open Executableloading any exe should stop at the initial breakpoint; in the command windowveroutputs the version number - Version differences: the Store version (WinDbg 1.x, Chromium UI) and the classic version (WinDbg 10.0.x) are command-compatible, but the UI and attach workflow differ; dump analysis works the same for both (see [[gotchas]])
Windows symbols (Windows SDK symbols / public symbol server)
- Symbol path:
.sympath srv*C:\symbols*https://msdl.microsoft.com/download/symbols(or set the environment variable_NT_SYMBOL_PATH) - Kernel module symbols: under kernel debugging
.reload /f nt(force pulling from the symbol server) - Verify:
!analyze -vfunction names appear in the output instead of+0x1abare offsets
Two-machine/virtual machine kernel debugging environment
- Target machine (being debugged):
bcdedit /debug on+bcdedit /dbgsettings serial debugport:1 baudrate:115200(serial port) orbcdedit /dbgsettings net hostip:<宿主机IP> port:50000 key:<kdnet.exe生成的key>(NET network card, recommended for Windows 10+) - VM scenario: VMware/Hyper-V add a serial device to the target VM (Named Pipe
\\.\pipe\com_1/-vmconnect), on the host WinDbgFile > Kernel Debug > COMfill in the pipe name - Local kernel debugging (test machines only, requires test signing):
bcdedit /set testsigning onafter restart, WinDbgFile > Kernel Debug > Local - Verify: after the target machine restarts and stops at "Waiting to reconnect" or enters the debug wait, the host WinDbg attaches successfully,
gafterCtrl+Breakcan break in,lmlists modules
TTD (Time Travel Debugging, Windows 10 1903+)
- Recording:
ttd.exe -out C:\traces target.exe(or-attach <pid>attach and record;-monitor <进程名>monitor launch) - Open: WinDbg
File > Openselect.runtrace file (orOpen Trace File) - Verification:
ttd.exe -helpthere is output; after recording a session, open it with WinDbg,!ttyou can jump to any position,g-and you can run backwards (see the [[commands]] TTD section)
Steps
-
Attach/launch a user-mode target:
- Launch:
File > Open Executableselect the target exe, stop at the initial breakpoint (where ntdll has finished loading) - Attach:
File > Attach to Processselect the target by PID/process name (administrator privilegesstart WinDbg to attach to a high-privilege process) - Command line:
windbg -p <pid>/windbg 目标.exe;-gskip the initial breakpoint and run directly
- Launch:
-
Breakpoints / stepping / registers:
- Symbol breakpoint:
bp kernel32!CreateFileW; address breakpoint:bp 0x401000;bllist,bd/bedisable/enable,bc *clear - For symbols in modules that are not loaded, use
bu(unresolved, automatically bound after the module loads) — when the target DLL is loaded on demand,buis the right answer - Command breakpoint:
bp 地址 "r rax; k; gc"— when hit, print the state and automatically continue, observing log-style without interrupting the flow - Stepping:
gcontinue,pstep over,tstep into,guexecute until the current function returns - Registers:
rall,r raxview,r eax=0modify (commonly used to bypass validation) - Memory/disassembly:
u 0x401000disassemble,dd/dbread memory,dps rspview stack contents - Search:
s -d 0x0 L?10000000 0xdeadbeef(value),s -a 0x0 L?10000000 "text"(ASCII string) — used to find hardcoded constants/keys - Symbol search:
x 模块!*关键字*(such asx kernel32!*Virtual*) — fuzzy matching when you are unsure of the exact symbol name
- Symbol breakpoint:
-
Exception analysis
!analyze -v:- After the target crashes and stops:
!analyze -v→ readEXCEPTION_CODE、FAULTING_IP、STACK_TEXT、PROCESS_NAME - Kernel blue screen corresponds to the bugcheck code (
BUGCHECK_CODE, such as 0xD1 for a driver accessing invalid memory) — write down the code first, then investigate; the meaning of common codes should match the fault direction - Context recovery: from
STACK_TEXTget theEXCEPTION_RECORDaddress.exr <addr>(exception record), then get theCONTEXTaddress.cxr <addr>to restore the register context, and thenkoutput the real call stack (the only reliable stack unwinding method when the stack is corrupted) - Dump context is simpler: after opening the dump, directly
.ecxr(automatically set the register context using the exception context) →k— the standard opening for minidump analysis - Dump analysis:
File > Open Crash Dumpopen.dmp(for user mode use.dump /mato generate; for kernel blue screens use%SystemRoot%\Minidump\*.dmpor a complete dump), and similarly!analyze -voutput the bugcheck code and faulty module
- After the target crashes and stops:
-
Kernel debugging (two-machine / VM / local kd):
- See "Tool preparation" for target machine configuration; after configuring, restart, and the host
File > Kernel Debugconnects using the transport used (COM/NET) - VM pipe serial port procedure: add a serial port Named Pipe to the target VM → target machine
bcdedit /dbgsettings serial→ hostFile > Kernel Debug > COMFill in the pipe name (\\.\pipe\com_1) → target machine restarts, etc., connect - After connecting:
gLet the target keep running;Ctrl+Breakinterrupt back to the debugger at any time;!process 0 0confirm the session is alive - Local debugging:
bcdedit /set testsigning onAfter restartFile > Kernel Debug > Local(test machine only, does not affect production machine usage)
- See "Tool preparation" for target machine configuration; after configuring, restart, and the host
-
Extended commands:
!process 0 0(full process list),!process <EPROCESS> 1(single-process details + threads);.process /p <EPROCESS>after switching to the target process context, thenk/!thread!teb(current thread TEB),!peb(current process PEB — command line, loaded modules, environment block)lm(module list, line end(deferred)=symbols not loaded,(no symbols)=no symbols),lmv m <模块>(version + path),lmi <模块>(image headers)kv(full stack backtrace with symbols, commonly used in driver analysis),!analyze -vthen pressSTACK_TEXTto verify frame by frame- Driver-specific:
lmv m 驱动名get the base address →.reload /f 驱动名load symbols → dig deeper by turning to [[re-kernel]]
-
TTD replay (slow investigation of validation/decryption logic):
ttd.exe -out C:\traces target.exe # 录制 WinDbg 打开 .run → g- / t- / p- # 往回跑 !tt 50 # 跳到轨迹 50% 处 !tt 7213:36 # 跳指定位置(事件序号:步数)- Move breakpoint backward/forward:
!tt brstop where a register value changes;!tt bmstop at a memory access - First time opening a trace, first
!tt.indexbuild the index (subsequent opens skip it); during replay you can also breakpointbp(work on the trace) - Query API:
dx @$cursession.TTD.Calls("模块!函数")count calls/parameters (e.g....Calls("ntdll!mem*")) - TTD limitations: requires running as administrator, user-mode only, trace files are very large (several GB), replay is read-only and cannot write memory (see [[gotchas]])
- Move breakpoint backward/forward:
-
Pseudo-registers and scripting (simple automation):
- Pseudo-registers:
r $t0 = 0to create temporary variables;$$ > <文件>redirect command output to a file (e.g.$$ > c:\log.txt) - Conditional execution:
.if (rax==0) { ... } .else { ... }— combined withgcto do logic inside breakpoints !runawayview thread CPU time (find busy-waiting/infinite-loop threads);!lockscheck lock status (hang troubleshooting)
- Pseudo-registers:
Cross-domain combinations
- [[re-binary-core]]: workflow step 6 Windows debugger (user mode + kernel)
- [[re-kernel]]: kernel debugging and driver/rootkit runtime verification always invoke this skill
- [[re-anti-analysis]]: anti-debugging countermeasures (kernel-level detection points), post-unpacking verification
- [[re-malware]]: malicious sample crash dump analysis and kernel components
- [[re-cracking]]: locating authorization check crashes (
!analyze -vquickly find the crash cause) - Complements [[re-x64dbg]]: use x64dbg for lightweight GUI breakpoint debugging, use WinDbg for kernel/exception scene recovery/trace replay
Common pitfalls and traps
- Symbols not configured → all raw offsets without function names: symptom —
lmline end(no symbols),koutputntdll!+0x1a; cause — symbol path is empty or not_NT_SYMBOL_PATH; countermeasure —.sympath srv*C:\symbols*https://msdl.microsoft.com/download/symbolsafter.reload /f, same for kernel modules (first confirm the symbol server is reachable) - Kernel debugging misconfigured → target machine bluescreens, waits forever, or cannot connect: Symptom — after the target machine restarts, it stays at the debug wait or boots straight into the system without breaking; Cause — the bcdedit transport type/port does not match the host, or local debugging on a standalone physical machine is restricted (PatchGuard); Countermeasure — a VM serial pipe is the most reliable (both sides must
\\.\pipe\com_1),bcdedit /dbgsettingsrecheck the configuration; for physical machine scenarios, prefer KDNET - 32/64-bit command and register differences: Symptom — under a 64-bit target,
r eaxchanges the wrong value,!tebfields do not match; Cause — the x64 context usesrax/rbx...(the low 32 bits areeax, etc.), and in wow64 the target thread stack is at the x86 layer; Countermeasure — first confirm the target bitness; in wow64 scenarios,.load wow64exts+!wow64exts.swswitch to the x86 context - When the stack is corrupted,
kis an illusion: Symptom — the call stack is garbled (addresses do not look like code); Cause — the stack pointer has been corrupted/memory has been trampled, so a normal backtrace is unreliable; Countermeasure — use.exrto get the exception record,.cxrrestore the CONTEXT and thenk, and if necessary!analyze -vinSTACK_TEXTto retrieve the scene directly - The blue screen dump cannot be opened: Symptom —
Open Crash Dumpreports a format error; Cause — the minidump is incomplete (disk full) or a 32-bit WinDbg is being used to read a 64-bit dump; Countermeasure — confirm on the target machine that the kernel page swap file (crash dump requires a pagefile) is large enough, and switch to a WinDbg of the same bitness - Unified entry point for RPC calls NdrStubCall3 (svchost feature reverse engineering): Symptom — you want to intercept/analyze a system feature (such as Toast notifications) but do not know through which channel it enters svchost; Cause — the feature is implemented via RPC to the svchost process, and the entry point is hard to find; Countermeasure — use Process Monitor to see the target process load rpcrt4.dll + RpcView to confirm the RPC target process;All RPC server stubs uniformly go through rpcrt4's NdrStubCall3(x64; NdrStubCall2 is the old x86 version, as can be corroborated by the ReactOS/Wine source) — set a breakpoint on it
kvnto see the call source; filter the target call: the third parameter (r8) points to RPC_MESSAGE (+0x1C is ProcNum method number, +0x28 is the interface description pointer RpcInterfaceInformation → RPC_SERVER_INTERFACE's InterfaceId at +0x04);opnum is numbered from 0 by MIDL according to the method declaration order(for example, PostNotification3 is at position 0x29 → opnum=0x29), and in ObjectStublessClientN, rax=N means ProcNum; Pitfall: NdrStubCall3 handles all RPC interfaces within svchost,you must filter by both InterfaceId + ProcNum, and intercept only the target method (intercepting the wrong one will break other features such as Edge) - For TTD limitations, version differences, and symbol server details, see [[gotchas]]