Windbg Preview

WinDbg Preview is a modern, user-friendly debugger tool provided by Microsoft. It is an evolution of the traditional WinDbg debugger.

Windbg Preview is an updated version of Windbg. It has additional features such as the extensible debugger data model whilst still having the same commands and workflows that Windbg had.

Download and Install

Windbg Preview can be downloaded and installed from the Microsoft Store, simply search for it and press the Get button:

Basic Use

Now that the basic tools are installed we can remind ourselves of basic Windbg commands we will need to debug our shellcode. Feel free to skip over these if you are well versed in WinDbg.

Debugging Notepad

This short section will serve as a reminder on how to do basic debugging in Windbg Preview.

Open a new Notepad instance then open Windbg Preview. If we press the File tabe we will be presented with many debugging options. To debug the Notepad process we should press Attach to a Process and then find notepad.exe:

Press the Attach button and your debugging session will begin. When the debugging session starts the Notepad process (more specifically it's threads) are 'broken into'. That is a breakpoint has been inserted and hit:

It is likely that your workspace does not reflect mine. It is worth having a play around with the View tab to build the workspace you are more comfortable with.

Breakpoints

Windbg supports different types of breakpoints: Software, Hardware, Access, Condition, and Function breakpoints.

We have already seen a software breakpoint. These are implemented using the int3 instruction. This allows us to examine the program's state and perform debugging operations. The int3 instruction is typically inserted into shellcode temporarily for debugging purposes and is removed in the final exploit code. If we look at the familiar MessageBox call we explored in an earlier section, we can insert an int3 breakpoint (line 16):

Breakpoint example
; this code assumes that the address of the MessageBox function is in r10
call_messagebox:
    xor  rax, rax                     ; RAX = 0
    mov  rcx, rax                     ; RCX = hWnd = NULL
    mov  r9,  rax                     ; R9 = uType = NULL (default)
    push rax                          ; push HelloWorld\0 on to the stack
    mov  rax, 0x646c72                ; .
    push rax                          ; .
    mov  rax, 0x6f57206f6c6c6548      ; .
    push rax                          ; ---
    mov  rax, rsp                     ; RAX = RSP
    mov  rdx, rax                     ; RDX = lpText = *HelloWorld\0
    mov  r8,  rax                     ; R8 = lpCaption = *HelloWorld\0
    sub  rsp, 0x28                    ; Allocate 40 bytes for call 
                                      ; and stack alignment   
    int3                              ; breakpoint
    call r10                          ; CALL R10 (MessageBox)
    add  rsp, 0x20                    ; cleanup allocated stack space

We might choose to break into the code and ensure the registers and stack are set up correctly before the function call (line 16).

At this point we can debug the shellcode, such as examine registers, unassemble instructions, and examine the stack, to name a few. We will examine other types of breakpoints when we encounter them in other sections.

Adding Breakpoints

We can set a breakpoint in WinDbg by symbol (using bp), and then list the existing breakpoints (using bl):

0:002> bp User32!MessageBoxA
0:002> bl
     0 e Disable Clear  00007ffa`1d0590d0     0001 (0001)  0:**** USER32!MessageBoxA

Clearing Breakpoints

Breakpoints can also be cleared using the bc command:

0:002> bc *

In the preceding example we used the * operator to clear all existing breakpoints, we could have cleared a single breakpoint using the index displayed in the list using bl.

Disabling Breakpoints

Breakpoints can be disabled using the bc command:

0:002> bd *

Displaying Registers

We can display the contents of the general purpose registers all at once or individually. To display the registers we use the r command:

0:002> r
rax=0000005d74e23000 rbx=0000000000000000 rcx=0000000000000000
rdx=00007ffa1e69cba0 rsi=0000000000000000 rdi=0000000000000000
rip=00007ffa1e670b70 rsp=0000005d7507f898 rbp=0000000000000000
 r8=0000000000000000  r9=00007ffa1e69cba0 r10=00000fff43cd3974
r11=0010008800200000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000

If we are only interested in a single register, we can specify the register with the r command:

0:002> r r9
r9=00007ffa1e69cba0

Sometimes it is beneficial to display the memory that is at the location pointed to by a register. That will be discussed in the next sub-section.

Displaying Memory

The addresses in memory essentially point to 64-bit values stored as binary. This binary numbner can represent many different things depending upon the context. For example it can be an ASCII string, a Unicode string, a pointer to another object in memory, or a QWORD etc.

Memory can be displayed using the d command. We can also specify how many memory entries we would like to list using the L operand. Using MessageBoxA as an example we can examine the outputs of the display command:

QWORD

0:002> dq User32!MessageBoxA L2
00007ffa`1d0590d0  44db3345`38ec8348 2e740003`82121d39

DWORD

0:002> dd User32!MessageBoxA L2
00007ffa`1d0590d0  38ec8348 44db3345

WORD

0:002> dw User32!MessageBoxA L2
00007ffa`1d0590d0  8348 38ec

Byte

0:002> db User32!MessageBoxA L2
00007ffa`1d0590d0  48 83

Strings

If there are strings stored in the memory we would like to display we can use the da (ASCII) and du (UNICODE) commands.

It does not make sense to display the memory at User32!MessageBoxA as a string. if we do, it is displayed as 'garbage':

0:002> da User32!MessageBoxA
00007ffa`1d0590d0  "H..8E3.D9...."
0:002> du User32!MessageBoxA
00007ffa`1d0590d0  "荈㣬.䓛ᴹ舒..䡥ҋ〥"

Dereferencing Pointers

Finally, we can display the contents of memory by dereferencing a pointer (using poi):

0:002> dq poi(rsp)
00007ffa`1e69cbee  f87a49e8`c93300eb cccccccc`cccc90ff
00007ffa`1e69cbfe  3025048b`4865cccc 16a88889`48000000
00007ffa`1e69cc0e  cccccccc`ccc30000 cccccccc`cccccccc
00007ffa`1e69cc1e  3025148b`4865cccc 16a8928b`48000000
00007ffa`1e69cc2e  ccfffd32`4be90000 cccccccc`cccccccc
00007ffa`1e69cc3e  3025048b`4865cccc 8b4cc98b`4c000000
00007ffa`1e69cc4e  16a8888b`4801b2c2 ccfffd3e`23e90000
00007ffa`1e69cc5e  cccccccc`cccccccc cccccccc`cccccccc

In the example above, we are dereferencing the value stored in rsp. This could easily be a memory address being dereferenced: 0:002> dq poi(0000005d`7507f898).

Unassemble

We can unassemble machine code in memory using the u command. This can be done by symbol or by memory address location. In the example below we show the beginning of the MessageBoxA function in user32.dll:

0:002> u User32!MessageBoxA
USER32!MessageBoxA:
00007ffa`1d0590d0 4883ec38        sub     rsp,38h
00007ffa`1d0590d4 4533db          xor     r11d,r11d
00007ffa`1d0590d7 44391d12820300  cmp     dword ptr [USER32!gSharedInfo+0x2c0 (00007ffa`1d0912f0)],r11d
00007ffa`1d0590de 742e            je      USER32!MessageBoxA+0x3e (00007ffa`1d05910e)
00007ffa`1d0590e0 65488b042530000000 mov   rax,qword ptr gs:[30h]
00007ffa`1d0590e9 4c8b5048        mov     r10,qword ptr [rax+48h]
00007ffa`1d0590ed 33c0            xor     eax,eax
00007ffa`1d0590ef f04c0fb115f88c0300 lock cmpxchg qword ptr [USER32!gSharedInfo+0xdc0 (00007ffa`1d091df0)],r10

If we know the address of the function, we can unassemble this too:

0:002> u 00007ffa`1d0590d0
USER32!MessageBoxA:
00007ffa`1d0590d0 4883ec38        sub     rsp,38h
00007ffa`1d0590d4 4533db          xor     r11d,r11d
00007ffa`1d0590d7 44391d12820300  cmp     dword ptr [USER32!gSharedInfo+0x2c0 (00007ffa`1d0912f0)],r11d
00007ffa`1d0590de 742e            je      USER32!MessageBoxA+0x3e (00007ffa`1d05910e)
00007ffa`1d0590e0 65488b042530000000 mov   rax,qword ptr gs:[30h]
00007ffa`1d0590e9 4c8b5048        mov     r10,qword ptr [rax+48h]
00007ffa`1d0590ed 33c0            xor     eax,eax
00007ffa`1d0590ef f04c0fb115f88c0300 lock cmpxchg qword ptr [USER32!gSharedInfo+0xdc0 (00007ffa`1d091df0)],r10

Flow Control

TODO.

Searching Memory

TODO

This section was a short reminder of some of the most common Windbg commands, more will be discussed as the course progresses.

Last updated