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