WinDbg

Notes on using WinDbg for reverse engineering compiled in October 2021.

When using WinDbg to perform Dynamic Reverse Engineering start with configuring the symbol path

# Following guidance from https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/symbol-path


srv*c:\MyServerSymbols*https://msdl.microsoft.com/download/symbols
Selecting notepad.exe

After WinDbg attaches to a process the application execution flow is paused. If this is your first time using WinDbg it may take a minute to finish installing the symbol files.

notepad.exe loaded into WinDbg

In order to have the process continue enter the "g" command

Because the process was attached after execution has already begun it's useful to reload the process in order to load all relevant symbol files. This can be done with the command ".reload /f". This may take several minutes.

The "u" command can be used to specify which location to start disassembling from. In this instance the addresses following the instruction pointer are all 3's this is becasue of how WinDbg injects into the process and controls it's execution. (I'm not sure of why this works, will need to look into it closer).

Reading process memory with dW

Dumping structures from memory using dt. This requires the symbol files.

It is also possible to add -r in order to recursively dump nested structures.

Due to some APIs taking structures as arguments it is important to be able to determine the size of structures. This can be done using the sizeof command.

It is possible to modify registry values using the edit command ed in order to change execution flow or results from arithmetic operations.

Changing where EIP points

searching through memory space can be done with the s command. For instance the following pattern will look for four As.

# s -d lowerbound uperbound pattern    # switching out -d for -a will look for ascii strings.
s -d 0 L?800000000 41414141

Using r to modify and view registry values

WinDbg can be used to set both software and hardware breakpoints. Software breakpoints for instance can be used to stop at API calls including functions not yet loaded into the memory space. Hardware breakpoints can be used to determine when data is accessed.

By setting a breakpoint on the WriteFile API call it was possible to trigger the process to break when attempting to save the file. When you first click the save as button it will attempt to load a handful of symbol files and you may also need to hit g in order to go forward. After the first execution whenever you save the file the breakpoint will hit.

Breakpoints can be cleared using the bc command and specifying the breakpoint number

Unresolved functions can have breakpoints set with the bu command. Unresolved functions are functions contained within modules that have not yet been loaded.

It is possible using the ba command to set hardware breakpoints which trigger when a register is read, modified or executed.

# ba e||r||w size Memoryaddress

ba e 1 kernel32!WriteFile

----------------------------------------------------------------------------------------------------

Useful Hooks

  1. bp wsock32!recv This will hook the Windows recv API.

# Various Commands Used By WinDbgs
g                       # The process continues to run

.reload /f              # The process reloads

u 0x13371337            # The u command tells the process which address to start diassembling from. It can also take in function symbols or ranges of memory addresses.

.cls                    # Clear Screen

dW 0x13371337           # Read from memory at the specified address. dW is for WORDs, DD DWORDs, db bytes, dq QWORDs

dt symbol!_yeet         # Display Type will print the structures from the specified area in memory.
dt nt!_TEB  ## this will retrieve the TEB structure
                dt nt!_TIB  ## This will dump the TIB structure containing the exception registration records.
                dt nt!_EXCEPTION_REGISTRATION_RECORD ## Contains the linkend list of exception structures.
                dt ntdll!_CONTEXT ## dump the context structure.
                
?? sizeof(structure)    # Will provide the size of a structure.

ed                      # The Edit command can be used for modifying process memory data stored in the various registers. ea can be used to modify ascii characters and eu can be used to modify unicode characters.

s                       # The Search command can be used to look through memory space for a specific pattern.

r                       # Display all the registers. It's also possible to display specific registers or to modify there values.

bp                      # Set a breakpoint at a specific address or API access location.

bl                      # List all current breakpoints.
bc 0                    # Clear the first breakpoint set.
bd 0                    # Disable the first breakpoint set
be 0                    # Enable the first breakpoint set.
bu                      # Set a breakpoint on an unresolved function.
ba                      # Create a hardware break point taking advantage of the four debug registers available. This will allow for the detection of modifications or access to registers.

lmD                     # List loaded modules. Can also do lm m module to look up the address for a specific module.

p                       # Will step over function calls
t                       # Will step into function calls
pt                      # Step to next return, moves you to the end of the current function.

ph                      # Step until a branching instruction is reached.

lm                      # List the loaded modules. Modules can be searched through with the usage of wild card "lm m kernel*"

.formats 41414141       # Will translate this to other usable formats such as AAAA
!exchain                # Display the execution handlers of the current thread                

Useful Tips:

  • When searching for a string don't only use ascii, for instance strings in notepad.exe are stored as unicode and wouldn't show up in an ascii search.

Last updated

Was this helpful?