Tools
WinDbg
Basic Features
We can debug an application in two ways.
Attach to an already-running process: File > Attach to a process
Open an executable stored on disk. In this case, Windbg spawns a new process from the executable and attaches to it. Attaching allows the debugger to receive events from the target process, which is suspended until the event is handled by the debugger. Note that execution pauses before it reaches the entry point: File > Open Executable
To stop debugging:
Debug > Detach Debugee, which leaves the application running
Debug > Stop Debugging or with the B+% keyboard shortcut, which closes the application
Display setup:
Command: View > Command > drag it over the main WinDbg window
Disassembly: View > Disassembly > drag it over the main WinDbg window
Registers : View > Disassembly > drag it over the main WinDbg window
Memory : View > Disassembly > drag it over the main WinDbg window > Configure it to represent the stack by setting the Virtual field to @esp and the Display format field to Pointer and Symbol. Then add another memory window and leave it by default
Adjust sizes and place docked windows in prefered parts of the screen
Colors: View > Options
Workspaces:
To save settings
Two types, default and named
Default workspaces are loaded by default depending on how WinDbg is being used: File > Save Workspace.
Named workspaces can be customized and contain some additional information that default workspaces do not
There are multiple different default workspaces, for example the base workspace loaded when launching WinDbg before
attaching to a process, another default workspace is the default user-mode workspace that is loaded when we attached to a user-mode process, and there are other default workspaces for kernel and remote debugging: File > Save Workspace As, then File > Open Workspace.
To save workspace to a file: File > Save Workspace to File, then to open it: File > Open Workspace
.hh
-> built-in manual
Symbols
As we work in the debugger, we will often inspect functions, structures, and global variables. We could reference these by their addresses, but we would need to find the addresses first and then inspect them, for example, to inspect 00a01020 address:
u 00a01020
There's nothing wrong with this method, but we could use symbols, which allow us to refer to functions, structures, and global variables by name.
In Windows, this information is stored in a symbol file, which has a .PDB (Program Database) extension. If no PDB is available, we can only refer to such information by address.
The PDB file is often created by default when an executable is compiled in Debug mode, yet not included when an executable is compiled in Release mode.
We can set the location of the .pdb file to make the debugger know which symbols should use: File > Symbol File Path.
To force WinDbg to reload symbols:
.reload /f
To resolve the main function:
u myprogram!main
To inspect a function, a call to waitforuser function for example:
u myprogram!waitforuser
Another important set of symbol files are those available from the Microsoft Symbol Server: https://msdl.microsoft.com/download/symbols
To force loading available symbols, in the symbols path window:
srv *c:\symbols*https://msdl.microsoft.com/download/symbols
Accessing and Manipulating Memory and Registers
- Unassemble from memory (Disassembly)
To disassemble the main function from memory with symbols:
u memory!main
u kernel32!GetCurrentThread
We can use the L parameter to select the number of instructions:
u memory!main L3
We can also supply an address via a register.:
u @eip
- Dump and read from memory
To inspect memory to gain insight into what a process is doing, or what it will do, we'll often rely on the d*
(Display Memory) command, in which the second character corresponds to a display type.
db
--> display the contents of memory in bytes (1 byte / 8 bits)
dw
--> display the contents of memory in words (2 byte / 16 bits)
dd
--> display the contents of memory in dwords (4 byte / 32 bits)
dq
--> display the contents of memory in qwords (8 byte / 64 bits)
For example:
db @esp
db esp
We can use the L
parameter to select the number of instructions:
db @esp L2
To read ASCII strings:
da memory!hola
To read UNICODE strings:
du memory!uhola
The db command displays the byte type side-by-side with ASCII characters. We can also display other types alongside ASCII characters:
dW memory!hola La
dW KERNELBASE+0x40
dc memory!hola L5
The poi
operator is used to reference a pointer.
dd memory!ptr L1
To access the data within the pointer (dereferncing):
da {previouslly extracted memory address}
To achieve this with only one command:
da poi(memory!ptr)
dd poi(esp)
- Dump structures from memory
dt
(Display Type) can be used to inspect structures.
dt memory!_MYSTRUCTURE
dt ntdll!_TEB
To recursively inspect nested structures (i.e. structures that are members of other structures):
dt -r memory!_MYSTRUCTURE
dt -r ntdll!_TEB @$teb
(Here @$teb
is a psudo register)
If we don't want to inspect all nested structures, we can also specify a recursion depth:
dt -r1 memory!_MYSTRUCTURE
This output can be filtered. If we know the element's name and we want to find its offset or data type, for example, for fruit, name or both:
dt memory!_MYSTRUCTURE fruit
dt memory!_MYSTRUCTURE fruit name
If name was within fruit:
dt memory!_MYSTRUCTURE fruit.name
We can also filter for an address previouslly obtained:
db memory!hola
dt memory!_MYSTRUCTURE {address of hola}
dt ntdll!_TEB @$teb ThreadLocalStoragePointer
To obtain the size of a structure extracted from a symbol file:
?? sizeof(ntdll!_TEB)
- Modify and write to memory
e*
(Enter Values) can be used to write to memory
eb
--> Edit the contents of memory in bytes (1 byte / 8 bits)
ew
--> Edit the contents of memory in words (2 byte / 16 bits)
ed
--> Edit the contents of memory in dwords (4 byte / 32 bits)
eq
--> Edit the contents of memory in qwords (8 byte / 64 bits)
For example, to modify an integer:
Read original values:
db memory!integer L4
Modify them:
eb memory!integer 41 42 43 44
Check:
db memory!integer L4
We can also write strings to memory.
ASCII example:
ea memory!hola "Adios!"
ea esp "Hello"
UNICODE example:
eu memory!uhola "Adios!"
Note that, in both cases, the string that is written is not null terminated, which is why the remainder of the original string is present when we display it after editing. This is because ea and eu do not null terminate strings. This can be done with the eza and ezu commands.
- Search in memory
To search memory, we can use the s (Search Memory) command. Here the type is specified as a flag rather than as a part of the command itself.
To search a byte, -b
, -d
dword, and so on.
We also need to specify a range <start> <end>
.
s -d 0 L?80000000 41414141
We can also search for ASCII strings (-a
) or UNICODE (-u
)
In the following example, the L?
range operator removes the 256 Megabyte limit on the search range and 0x8000000
is the highest user-mode address.
s -a 0 L?80000000 "Hello"
s -a 0 L?80000000 "This program cannot be run in DOS mode"
s -b 0 L?80000000 6d 20 4f 66
- Inspect and modify CPU registers
To list the content of all registers (as well as other information such as the current instruction):
r
To inspect a specific register:
r eax
To modify the value of a register:
r eax = 41414141
To inspect and modify the content of flags:
r zf
r zf = 0
Controlling the Execution Flow
- Software Breakpoints
Temporarily replaces the first opcode of the instruction where we want execution to halt with an INT 3 assembly instruction. The advantage of software breakpoints is that we are allowed to set as many as we want.
To set a breakpoint in the location where we want the application to stop:
bp kernel32!WriteFile
To list all curent breakpoints:
bl
Once the program hits the breakpoint, press g (let the application continue execution) to list the current values.
To disable a breakpoint:
bd {number of breakpoint obtained with bl}
To reenable it:
be {number of breakpoint obtained with bl}
To clear a breackpoint
bc {number of breakpoint obtained with bl}
- Unresolved Function Breakpoint
We can set a breakpoint on an unresolved function. This is a function residing in a module that isn’t yet loaded in the process memory space.
For example, in notepad.exe, OLE32.dll is not initially loaded in the process, but is loaded once a file is saved.
First we obtain the module list:
lm m ole32
Then, we set the breakpoint in the desired unresolved function:
bu ole32!WriteStringStream
- Breakpoint-Based Actions
We can also automate the execution of commands within the debugger when a breakpoint35 is triggered, for example, to print the register’s content, dereference memory locations, and perform other actions when a breakpoint is hit.
For example, to execute the .printf command every time the breakpoint set on the kernel32!WriteFile API is triggered:
bp kernel32!WriteFile ".printf \"The number of bytes written is: %p\", poi(esp + 0x0C);.echo;g"
In the previous examples we usd esp + 0x0C (12 bytes) because in this specific win32 API the the bytes written are in the third argument on the stack.
We can also set a conditional breakpoint. For example, to set a conditional breakpoint on the kernel32!WriteFile Windows API only if we write exactly four bytes of data to a file:
bp kernel32!WriteFile ".if (poi(esp + 0x0C) != 4) {gc} .else {.printf \"The number of bytes written is 4\";.echo;}"
We use gc
(go from conditional breakpoint) to resume execution.
- Hardware Breakpoints
Hardware or processor breakpoints are handled by the processor and stored in the processor’s debug registers. They can stop code execution when a particular type of access, such as read, write, or execute, is made to a targeted memory location.
They provide the ability to monitor changes or access to data in memory.
The x86 and x64 architectures only use four debug registers, so we are limited by the number of processor breakpoints.
To set a hardware breakpoint:
ba {type of access, which can be either e (execute), r (read), or w (write)} {size in bytes for the specified memory access} {memory address where we want to set the breakpoint at}
ba e 1 kernel32!WriteFile
For example to search for a string and set a breakpoint when the memory address containing that string is written:
s -u 0x0 L?80000000 hola
ba w 2 {obtained memory address}
Now if we modify the first letter of hola and write it, the breakpoint will halt execution.
We can now go to the address in the disassembler and look at the previous functions to notice where the string is stored and then display contents of the register.
- Stepping Through the Code
After halting the application flow, we can use the following commands:
p
-> step over (execute one single instruction at a time and steps over function calls)
t
-> step into (execute one single instruction at a time and steps into function calls)
pt
-> step to next return (fast-forward to the end of a function)
ph
-> executes code until a branching instruction is reached (conditional or unconditional branches, function calls, and return instructions)
Additional Features
- Listing Modules and Symbols
lm
-> display all loaded modules, including their starting and ending addresses in virtual memory space
We can also filter, for example, to show all modules starting with “kernel”:
lm m kernel*
x
-> examining symbol
For example, to display all the symbols of kernelbase module that start with “CreateProc”:
x kernelbase!CreateProc*
!address @eip
-> Provides detailed information about the memory region an address belongs to, including base address, size, state, protection, type, and the module it is associated with. It offers an extensive view of the address space and can also give hints about what more to investigate (lmv
, lmi
, ln
, dh
).
!vprot @esp
-> Provides information about the memory protection attributes of the page that contains the specified address. Focuses specifically on the protection attributes of the page itself.
!teb
-> Displays the contents of the TEB, which includes thread-specific data such as stack limits, thread local storage (TLS), and thread ID.
!peb
-> Shows the contents of the PEB, which holds process-related information such as the image base address, the process heap, and environment variables.
- Calculator
We can do calculations in a debugger, saving the annoyance of switching between applications.
To evaluate offset (distance) between two addresses, outputting the result in both decimal and hexadecimal:
? features!buf1 - features!buf2
For example to find the difference between two addresses (default input is hex format unless specified 0n
or 0y
):
? 77269bc0 - 77231430
To convert Hex to Dec:
? 0x20
To convert Dec to Hex:
? 0n54
To perform a bitwise left shift on a number, effectively multiplying it by 2 for each shift:
? 3 << 1
For example, to find the lower byte value of a DWORD (by right shifthing it 18 bits):
? 77269bc0 >> 18
To performs a bitwise AND operation, which can be used to mask out certain bits in a number (e.g., getting the lower 16 bits of a 32-bit number):
? 0x12345678 & 0xffff
To evaluates an expression using the current value of a register and adds a decimal value to it:
? @eip + 24
To finds the hexadecimal representation of a negative decimal number:
? -0n254
- Data Output Format
By default, WinDbg displays content in hexadecimal format.
For example, to convert the hex number 41414141 to decimal:
? 41414141
To convert the decimal number 0n41414141 to hexadecimal:
? 0n41414141
To convert the binary 0y1110100110111 to decimal and hexadecimal:
? 0y1110100110111
We can also convert between different formats at once including ASCII characters:
.formats 41414141
- Pseudo Registers
These are not registers used by the CPU but are variables pre-defined by WinDbg.
When using pseudo registers as well as regular registers, it is recommended to prefix them with the “@
” character. This tells WinDbg to treat the content as a register or pseudo register. It speeds up the evaluation process because WinDbg will not try to resolve it as a symbol first.
There are 20 user-defined pseudo registers named $t0 to $t19 that can be used as variables during mathematical calculations.
To store a calculation in a pseudo register:
r @$t0 = (41414141 - 414141) * 0n10
To read it:
r @$t0
To perform operations with it:
? @$t0 >> 8
There are other pseudo registers like $teb which have a predefined meaning.
- Custom Scripts
To use the scripts:
.load pykd
!py c:\path\to\this\repo\script.py
or put the scripts in C:\python37\scripts and execute them with !py SCRIPT_NAME
Script to find P/P/R (pop r32; pop r32; ret) Addresses within a module: https://raw.githubusercontent.com/epi052/osed-scripts/main/find-ppr.py
Script to find bad characters: https://raw.githubusercontent.com/epi052/osed-scripts/main/find-bad-chars.py
Script to improve the search functionality of Windbg: https://raw.githubusercontent.com/epi052/osed-scripts/main/search.py
IDA Pro
Basic Features
In cases where more detailed information about the code flow is needed, a debugger alone is not enough.
IDA Pro and IDA Freeware can disassemble both 32-bit and 64-bit applications, but the application itself is only available in 64-bit.
For 32-bit Windows executables and Dynamic Link Libraries (DLLs), we should select the “Portable executable for 80386” option shown above. This is a common denominator for all 32bit x86 processors.
When closing the application, Pack database option should always be checked if we don’t want to lose our changes. If, on the other hand, we do not want to save our changes, we would select DON’T SAVE the database.
We can switch between graph view and text view by pressing SPACE.
Proximity view is a more advanced feature for viewing and browsing the relationships between functions, global variables, and constants: View > Open subviews > Proximity browser
Every basic block has a color palette icon at the top left corner. We could use a combination of two colors can help show desired and undesired paths through basic blocks.
We can also comment in a specific line of assembly by pressin ":".
We can also rename functions (since if we have no symbols, “sub_XXXXXX" will be the function names) by locating it in the Functions window, right-clicking it, and selecting Edit function.
We can create a bookmark by choosing the line we want to bookmark and pressing ALT + M.
To install the freeware version in linux:
chmod +x idafree70_linux.run
sudo ./idafree70_linux.run
sudo ln -s /opt/idafree-7.0/ida64 /usr/bin
ida64
- Search Functionality
We can search for a string using the text option in the Search menu
We can earch for an immediate value such as a hardcoded DWORD using ALT + I
We can search a specific sequence of bytes ALT + B
All the imported and exported functions are available from the Imports and Exports tabs respectively.
We can use cross referencing (xref) to detect all usages of a specific function or global variable in the entire executable or DLL.
- Static-Dynamic Analysis Synchronization
For example,open Notepad, and attach WinDbg to it.
Then dump the base address of notepad:
k
lm m notepad
Then in IDA, Edit > Segments > Rebase program > enter the new image base address.
Once completed, all addresses, references, and global variables will match those found in WinDbg during the debugging session
In IDA Pro, we can press g to bring up the “Jump to address” dialog box and enter the absolute address of the function to end up at the same location.
So, once in the desired function within Windbg:
p
Then in IDA: Jump > Jump to function > Right-click any function name to enter a Quick filter with the name of the function obtained in Windbg
Last updated