Jump to content
Nytro

WinDBG quick start tutorial

Recommended Posts

WinDBG quick start tutorial

Step by step walk-through for learning basic commands and navigation in WinDBG.

© CodeMachine Inc. | codemachine.com | @codemachineinc

Overview

This post goes over the important commands in WinDBG through a step-by-step follow-along style walkthrough to help you get a jump start into using WinDBG and getting familiar with the commonly used commands. This is required pre-course reading for the Windows Security Developer Bootcamp.

We will use WinDBG Preview which can be downloaded from the Windows Store. The version that was used to write this post is WinDBG 1.0.2007.06001. All examples used in the post are from WinDBG running on Windows 10 Version 19042 64-bit version.

Getting setup

Prior to starting the debug session, we will set up the symbol path that we want WinDBG to use by running the following command in an administrative CMD.exe. If you have the variable _NT_SYMBOL_PATH already set up, you don't have to change it, and therefore running the following command would not be necessary.

setx _NT_SYMBOL_PATH SRV*C:\symsrv*http://msdl.microsoft.com/download/symbols

Launching the target process

We are ready to start the debugger. There are a few different ways to use WinDBG to debug a process, the most common ones are attaching to a running process and launching a process from WinDBG. For this walkthrough, we will be launching the native 64-bit executable from WinDBG.

To make it easy to follow along, we will choose an application that is available in every Windows 10 system i.e. notepad.exe. The 64-bit notepad.exe is located in the c:\windows\system32 directory.

Start WinDBG from the Windows 10 Start menu. Once WinDBG has started, select File, then Launch Executable.

http://codemachine.com/articles/figures/figure_windbg_quickstart_launch.png Launch Executable

In the Launch Executable dialog box, browse to the directory c:\Windows\System32 select notepad.exe and click on Open.

http://codemachine.com/articles/figures/figure_windbg_quickstart_notepad.png Select Notepad

When WinDBG launches an application, it stops at the initial breakpoint before the main entry point of the application is executed. When WinDBG launches notepad.exe the following lines will be displayed in WinDBG's command Window. This allows us to run some initial commands and set any desired breakpoints before the main entry point is called. More on this later.

Microsoft (R) Windows Debugger Version 10.0.20153.1000 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: C:\Windows\System32\notepad.exe

************* Path validation summary **************
Response                         Time (ms)     Location
Deferred                                       SRV*C:\symsrv*https://msdl.microsoft.com/download/symbols
Symbol search path is: SRV*C:\symsrv*https://msdl.microsoft.com/download/symbols
Executable search path is: 
ModLoad: 00007ff6`f8830000 00007ff6`f8868000   notepad.exe
ModLoad: 00007ff9`ff090000 00007ff9`ff286000   ntdll.dll
ModLoad: 00007ff9`fd840000 00007ff9`fd8fd000   C:\WINDOWS\System32\KERNEL32.DLL
ModLoad: 00007ff9`fce20000 00007ff9`fd0e9000   C:\WINDOWS\System32\KERNELBASE.dll
ModLoad: 00007ff9`fe5a0000 00007ff9`fe5ca000   C:\WINDOWS\System32\GDI32.dll
ModLoad: 00007ff9`fcc90000 00007ff9`fccb2000   C:\WINDOWS\System32\win32u.dll
ModLoad: 00007ff9`fca90000 00007ff9`fcb99000   C:\WINDOWS\System32\gdi32full.dll
ModLoad: 00007ff9`fcbf0000 00007ff9`fcc8d000   C:\WINDOWS\System32\msvcp_win.dll
ModLoad: 00007ff9`fc910000 00007ff9`fca10000   C:\WINDOWS\System32\ucrtbase.dll
ModLoad: 00007ff9`fd6a0000 00007ff9`fd840000   C:\WINDOWS\System32\USER32.dll
ModLoad: 00007ff9`fd990000 00007ff9`fdce6000   C:\WINDOWS\System32\combase.dll
ModLoad: 00007ff9`fe7d0000 00007ff9`fe8fb000   C:\WINDOWS\System32\RPCRT4.dll
ModLoad: 00007ff9`fe030000 00007ff9`fe0de000   C:\WINDOWS\System32\shcore.dll
ModLoad: 00007ff9`fddd0000 00007ff9`fde6e000   C:\WINDOWS\System32\msvcrt.dll
ModLoad: 00007ff9`f1f70000 00007ff9`f220b000   C:\WINDOWS\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.19041.488_none_ca04af081b815d21\COMCTL32.dll
(4bc.1df4): Break instruction exception - code 80000003 (first chance)
ntdll!LdrpDoDebuggerBreak+0x30:
00007ff9`ff1606d0 cc              int     3

Debugger process architecture

Before we proceed any further let us quickly review the WinDBG Preview process architecture. WinDBG Preview is a UWP application that has very limited access to the system, certainly not enough to debug a process. Hence the WinDBG UI and the WinDBG debugger workhorse are in separate processes that communicate using the named pipe inter-process communication (IPC) mechanism. The WinDBG Preview UI process is DBG.X.Shell.exe which connects over a named pipe to EngHost.exe which is the process responsible for attaching or launching the process being debugged.

http://codemachine.com/articles/figures/figure_windbg_quickstart_processes.png WinDBG Process Hierarchy

The following command displays the command-line options that were passed to the debugger process (EngHost.exe). The named pipe with the name DbgX_c07674536fa94c33bdf0af63c782f816 is used by DbgX.Shell.exe to communicate with EngHost.exe.

0:000> vercommand
command line: '"C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2007.6001.0_neutral__8wekyb3d8bbwe\amd64\EngHost.exe" npipe:pipe=DbgX_c07674536fa94c33bdf0af63c782f816,password=9cd080f41b98 "C:\Program Files\WindowsApps\Microsoft.WinDbg_1.2007.6001.0_neutral__8wekyb3d8bbwe\amd64" "C:\ProgramData\Dbg"'

Performing some initial investigation

Let us obtain some basic information about the OS version and the process being debugged. The Show Target Computer Version (vertarget) command displays information about the Windows version and information debug session time.

0:000> vertarget
Windows 10 Version 19042 MP (4 procs) Free x64
Product: WinNt, suite: SingleUserTS
Edition build lab: 19041.1.amd64fre.vb_release.191206-1406
Build layer:            -> 
Build layer:            -> 
Build layer:            -> 
Machine Name:
Debug session time: Sun Dec  6 00:16:24.332 2020 (UTC - 8:00)
System Uptime: 0 days 17:37:21.507
Process Uptime: 0 days 0:03:22.680
    Kernel time: 0 days 0:00:00.000
    User time: 0 days 0:00:00.000

The (vertarget) command showed that there are 4 CPUs (cores) on the system, let's find out a little bit more about the Family (F), Model (M), Stepping (S), and the speed of the CPUs/cores in the system using the (!cpuid) debugger extension command. Note that the (!) symbol before the (!cpuid) command denotes that this command is not supported natively by the debugger rather it resides in a debugger extension DLL.

0:000> !cpuid
CP  F/M/S  Manufacturer     MHz
    0  6,14,3                  2607
    1  6,14,3                  2607
    2  6,14,3                  2607
    3  6,14,3                  2607

Prior to starting WinDBG, we had set up the environment variable _NT_SYMBOL_PATH to the symbol path. WinDBG should have automatically used the symbol path set in this variable. Let us verify that using the Set Symbol Path (.sympath) command. Note that the (.) in front of the command denotes that this is a meta-command. Most such commands cause a change in the behavior of the debugger. In this specific case, the (.sympath) command could be used with the (+) option to append another path to the current symbol path.

0:000> .sympath 
Symbol search path is: SRV*C:\symsrv*https://msdl.microsoft.com/download/symbols
Expanded Symbol search path is: srv*c:\symsrv*https://msdl.microsoft.com/download/symbols

************* Path validation summary **************
Response                         Time (ms)     Location
Deferred                                       SRV*C:\symsrv*https://msdl.microsoft.com/download/symbols

To find the path to the symbol file (.PDB) that the debugger is using for notepad we can use the debugger extension command (!lmi). This command parses the PE headers and displays information retrieved from the PE file's Debug Directory.

0:000> !lmi notepad
Loaded Module Info: [notepad] 
            Module: notepad
    Base Address: 00007ff6f8830000
        Image Name: notepad.exe
    Machine Type: 34404 (X64)
        Time Stamp: d686c2e9 (This is a reproducible build file hash, not a true timestamp)
            Size: 38000
        CheckSum: 3e7ca
Characteristics: 22  
Debug Data Dirs: Type  Size     VA  Pointer
                CODEVIEW    24, 2b604,   2a404 RSDS - GUID: {17F6F0A8-0039-D425-B201-69009E11D822}
                Age: 1, Pdb: notepad.pdb
                    POGO   49c, 2b628,   2a428 [Data not mapped]
                REPRO    24, 2bac4,   2a8c4 Reproducible build[Data not mapped]
        Image Type: FILE     - Image read successfully from debugger.
                    C:\Windows\System32\notepad.exe
    Symbol Type: PDB      - Symbols loaded successfully from symbol server.
                    c:\symsrv\notepad.pdb\17F6F0A80039D425B20169009E11D8221\notepad.pdb
    Load Report: public symbols , not source indexed 
                    c:\symsrv\notepad.pdb\17F6F0A80039D425B20169009E11D8221\notepad.pdb

Process and Thread Status

The Process Status (|) command can be used to find the process ID and the process name being debugged.

0:000> |
.  0	id: 4bc	create	name: notepad.exe

When WinDBG is being used as a user-mode debugger, the Thread Status (~) command displays information for all threads in the current process. At this time, there is just a single thread in the notepad process and the status of the thread is shown below. This information includes the Thread ID = 0x1df4, the address of the TEB of the thread 0x000000e979a72000, the suspend count of the thread and information about freeze state of the thread.

0:000> ~
.  0  Id: 4bc.1df4 Suspend: 1 Teb: 000000e9`79a72000 Unfrozen

Module Information

To find the virtual address at which any module is loaded in memory, we can run the (List Loaded Modules) (lm) command. Running the lm command by itself will display the starting and ending address range of where each module is loaded in notepad.exe process address space. /p>

0:000> lm
start             end                 module name
00007ff6`f8830000 00007ff6`f8868000   notepad    (pdb symbols)          c:\symsrv\notepad.pdb\17F6F0A80039D425B20169009E11D8221\notepad.pdb
00007ff9`f1f70000 00007ff9`f220b000   COMCTL32   (deferred)             
00007ff9`fc910000 00007ff9`fca10000   ucrtbase   (deferred)             
00007ff9`fca90000 00007ff9`fcb99000   gdi32full   (deferred)             
00007ff9`fcbf0000 00007ff9`fcc8d000   msvcp_win   (deferred)             
00007ff9`fcc90000 00007ff9`fccb2000   win32u     (deferred)             
00007ff9`fce20000 00007ff9`fd0e9000   KERNELBASE   (deferred)             
00007ff9`fd6a0000 00007ff9`fd840000   USER32     (deferred)          
00007ff9`fd840000 00007ff9`fd8fd000   KERNEL32   (pdb symbols)          
00007ff9`fd960000 00007ff9`fd990000   IMM32      (deferred)             
00007ff9`fd990000 00007ff9`fdce6000   combase    (deferred)  
00007ff9`fddd0000 00007ff9`fde6e000   msvcrt     (deferred)             
00007ff9`fe030000 00007ff9`fe0de000   shcore     (deferred)             
00007ff9`fe5a0000 00007ff9`fe5ca000   GDI32      (deferred)             
00007ff9`fe7d0000 00007ff9`fe8fb000   RPCRT4     (deferred)             
00007ff9`ff090000 00007ff9`ff286000   ntdll      (pdb symbols)          c:\symsrv\ntdll.pdb\1EB9FACB04C73C5DEA7160764CD333D01\ntdll.pdb

Running the (lm) command with the (m) option can be used to restrict the output to a specific module. We use this to retrieve the VA range assigned to the module notepad.exe.

0:000> lm m notepad
start             end                 module name
00007ff6`f8830000 00007ff6`f8868000   notepad    (deferred)             

To obtain version information of a module, which is stored in the resources (.rsrc) section use the (v) option of the lm command.

0:000> lm v m notepad
start             end                 module name
00007ff6`f8830000 00007ff6`f8868000   notepad    (pdb symbols)          c:\symsrv\notepad.pdb\17F6F0A80039D425B20169009E11D8221\notepad.pdb
    Loaded symbol image file: C:\Windows\System32\notepad.exe
    Image path: notepad.exe
    Image name: notepad.exe
    Image was built with /Brepro flag.
    Timestamp:        D686C2E9 (This is a reproducible build file hash, not a timestamp)
    CheckSum:         0003E7CA
    ImageSize:        00038000
    File version:     10.0.19041.488
    Product version:  10.0.19041.488
    File flags:       0 (Mask 3F)
    File OS:          40004 NT Win32
    File type:        1.0 App
    File date:        00000000.00000000
    Translations:     0409.04b0
    Information from resource tables:
        CompanyName:      Microsoft Corporation
        ProductName:      Microsoft® Windows® Operating System
        InternalName:     Notepad
        OriginalFilename: NOTEPAD.EXE
        ProductVersion:   10.0.19041.488
        FileVersion:      10.0.19041.488 (WinBuild.160101.0800)
        FileDescription:  Notepad
        LegalCopyright:   © Microsoft Corporation. All rights reserved.

In WinDBG, the name of any module is treated as an expression that evaluates to the start VA at which the module is loaded into memory. If we use the MASM expression evaluator operator (?) on the module "notepad", it displays the start VA of the module.

0:000> ? notepad
Evaluate expression: 140698708017152 = 00007ff6`f8830000

Setting an execution breakpoint

At this point, we have found a reasonable bit of information about the process being debugged. We will now set a breakpoint on the main entry point of the PE file of notepad.exe. To do so, we must find the symbol that represents the main entry point of notepad.exe. To obtain a list of all such symbols we use the Examine Symbol (x) command which accepts wildcards and therefore makes it quite convenient to obtain a list of functions that end with the word main. The parameter we are passing to the (x) command consists of - the name of the module (without the extension), the exclamation sign (!) as the separator followed by the name of the symbols (containing wildcards).

0:000> x notepad!*main
00007ff6`f883b090 notepad!wWinMain (wWinMain)

In the above output, the first column displays the address of the symbol at which the symbol resides followed by the complete name of the symbol that matched the given wildcard.

The Set Breakpoint (bp) command has the ability to set a breakpoint on either a symbol name or an address. We use it to set a breakpoint on the main entry point of notepad.exe.

0:000> bp notepad!wWinMain

Let us verify that the breakpoint is indeed set appropriately using the Breakpoint List (bl) command.

0:000> bl
    0 e 00007ff6`f883b090     0001 (0001)  0:**** notepad!wWinMain

It is noteworthy that the bp command was able to resolve the symbol notepad!wWinMain to the appropriate address i.e. 00007ff6`f883b090 and was able to set an execution breakpoint and enable it as indicated by the (e) in the output above.

Now that we have the breakpoint set, let us continue executing the process notepad.exe using the Go (g) command.

0:000> g
ModLoad: 00007ff9`fd960000 00007ff9`fd990000   C:\WINDOWS\System32\IMM32.DLL
Breakpoint 0 hit
notepad!wWinMain:
00007ff6`f883b090 488bc4          mov     rax,rsp

As soon as we continue execution, the function we set the breakpoint on gets called, the breakpoint triggers and WinDBG gives us back control of the process.

Hitting the breakpoint

WinDBG has stopped the execution of notepad.exe at the first instruction of the function notepad!wWinMain. Let us ascertain that by retrieving the value of all the x64 CPU registers. The values in the registers would also assist us with retrieving the parameters that were passed to this function since on x64 the first 4 parameters are passed in CPU registers. To retrieve the CPU registers we use the Registers (r) command.

0:000> r
rax=0000023771d528b6 rbx=000000000000000a rcx=00007ff6f8830000
rdx= rsi=0000000000000000 rdi=0000000000000000
rip=00007ff6f883b090 rsp=000000e9799dfd48 rbp=0000000000000000
    r8=0000023771d528b6  r9=000000000000000a r10=00000fff3f9cdb1f
r11=0002002080000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
notepad!wWinMain:
00007ff6`f883b090 488bc4          mov     rax,rsp

The above output confirms that the address in the instruction pointer (RIP) indeed points to the first instruction of the function notepad!wWinMain. The prototype of the function wWinMain is as shown below along with the CPU registers that contain the respective parameters.

int WINAPI wWinMain(
    HINSTANCE hInstance,     // rcx
    HINSTANCE hPrevInstance, // rdx
    PWSTR lpCmdLine,         // r8
    int nCmdShow );          // r9

From the output of the (r) command the value of hInstance = 0x00007ff6f8830000, hPrevInstance = 0x 0000000000000000 (NULL), lpCmdLine=0x0000023771d528b6, nCmdShow=000000000000000a (SW_SHOWDEFAULT).

Displaying stack contents

The RSP register points to the top of the stack of the current thread. For a 64-bit process, each value stored on the stack is 64-bits in size i.e. a pointer-sized value. To display the contents of memory starting at the address in the RSP register we use the (dp) variant of the Display Memory command.

Please note that the (@) sign in front of a register is required if the current expression evaluator is C++, but we use it by default just as best practice.

0:000> dp @rsp
000000e9`799dfd48  00007ff6`f8853b86 00000000`00000000
000000e9`799dfd58  00000000`00000000 00000000`00000000
000000e9`799dfd68  00000000`00000000 00000000`00000000
000000e9`799dfd78  00000000`00000000 00000000`00000000
000000e9`799dfd88  00007ff9`fd857034 00000000`00000000
000000e9`799dfd98  00000000`00000000 00000000`00000000
000000e9`799dfda8  00000000`00000000 00000000`00000000
000000e9`799dfdb8  00007ff9`ff0dd0d1 00000000`00000000

By default, the (dp) command displays 64-bit values in two columns. We can change that using the column (/c) option to the (dp) command to display the contents of memory in 4 columns, as shown below.

0:000> dp /c 4 @rsp
000000e9`799dfd48  00007ff6`f8853b86 00000000`00000000 00000000`00000000 00000000`00000000
000000e9`799dfd68  00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000
000000e9`799dfd88  00007ff9`fd857034 00000000`00000000 00000000`00000000 00000000`00000000
000000e9`799dfda8  00000000`00000000 00000000`00000000 00007ff9`ff0dd0d1 00000000`00000000

To display more than the default 16 values, we can use the object count (L) option followed by the number of values to display.

0:000> dp /c 4 @rsp L 20
000000e9`799dfd48  00007ff6`f8853b86 00000000`00000000 00000000`00000000 00000000`00000000
000000e9`799dfd68  00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000
000000e9`799dfd88  00007ff9`fd857034 00000000`00000000 00000000`00000000 00000000`00000000
000000e9`799dfda8  00000000`00000000 00000000`00000000 00007ff9`ff0dd0d1 00000000`00000000
000000e9`799dfdc8  00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000
000000e9`799dfde8  00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000
000000e9`799dfe08  00000000`00000000 000004e8`fffffb30 000004d0`fffffb30 00000000`00000019
000000e9`799dfe28  00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000

And last but not the least, if we would like WinDBG to automatically attempt to map each one of the displayed values to symbols the (dps) variant of the Display Referenced Memory command can be used which in case of the stack would display all the return addresses as the map to symbols representing addresses within the functions that have been pushed on the stack by call instructions.

0:000> dps @rsp
000000e9`799dfd48  00007ff6`f8853b86 notepad!__scrt_common_main_seh+0x106
000000e9`799dfd50  00000000`00000000
000000e9`799dfd58  00000000`00000000
000000e9`799dfd60  00000000`00000000
000000e9`799dfd68  00000000`00000000
000000e9`799dfd70  00000000`00000000
000000e9`799dfd78  00000000`00000000
000000e9`799dfd80  00000000`00000000
000000e9`799dfd88  00007ff9`fd857034 KERNEL32!BaseThreadInitThunk+0x14
000000e9`799dfd90  00000000`00000000
000000e9`799dfd98  00000000`00000000
000000e9`799dfda0  00000000`00000000
000000e9`799dfda8  00000000`00000000
000000e9`799dfdb0  00000000`00000000
000000e9`799dfdb8  00007ff9`ff0dd0d1 ntdll!RtlUserThreadStart+0x21
000000e9`799dfdc0  00000000`00000000

Now that we have learned how to display the raw contents of the stack using the RSP register, let us view the stack the way it is meant to be displayed by the debuggers using the Display Stack Backtrace (k) command and its variants.

0:000> k
Child-SP          RetAddr               Call Site
000000e9`799dfd48 00007ff6`f8853b86     notepad!wWinMain
000000e9`799dfd50 00007ff9`fd857034     notepad!__scrt_common_main_seh+0x106
000000e9`799dfd90 00007ff9`ff0dd0d1     KERNEL32!BaseThreadInitThunk+0x14
000000e9`799dfdc0 00000000`00000000     ntdll!RtlUserThreadStart+0x21

The information displayed is the call chain from the start of this thread's execution (ntdll!RtlUserThreadStart) all the way to the current function (notepad!wWinMain) on which we set our breakpoint. Let's dig a little deeper into the displayed call stack.

Each line displayed above represents the stack frame of a single function. The frame at the bottom is the least recent frame and the frame on top is the most recent frame.

The values listed under Child-SP are the values of stack-pointer (RSP) register for the frame. This is the value of the RSP register right after the prolog of the function listed under the Call Site column has finished execution. This value in the RSP register remains static throughout the function body. Local variables and stack-based parameters to the function are accessed using this value in RSP.

The RetAddr is the return address that the current function i.e. the function listed under Call Site will return to once the function has finished execution. This address corresponds to the location displayed in the next (lower) stack frame. For example, running the List Nearest Symbols (ln) command on the RetAddr in the topmost frame maps to the function and offset listed under Call Site in the frame below the topmost frame.

0:000> ln 00007ff6`f8853b86 
(00007ff6`f8853a80)   notepad!__scrt_common_main_seh+0x106   |  (00007ff6`f8853c00)   notepad!wWinMainCRTStartup

Now that we understand how to interpret the information displayed by the (k) command, let us try some of its variants. To include the frame numbers in the stack display use the (kn) variant of the Display Stack Backtrace (k) command.

0:000> kn
    # Child-SP          RetAddr               Call Site
00 000000e9`799dfd48 00007ff6`f8853b86     notepad!wWinMain
01 000000e9`799dfd50 00007ff9`fd857034     notepad!__scrt_common_main_seh+0x106
02 000000e9`799dfd90 00007ff9`ff0dd0d1     KERNEL32!BaseThreadInitThunk+0x14
03 000000e9`799dfdc0 00000000`00000000     ntdll!RtlUserThreadStart+0x21

To display a clean stack trace which only lists the module and function names use the (kc) variant of the Display Stack Backtrace (k) command.

0:000> kc
Call Site
notepad!wWinMain
notepad!__scrt_common_main_seh
KERNEL32!BaseThreadInitThunk
ntdll!RtlUserThreadStart

And finally, to display the verbose stack trace that includes the stack-based parameters passed to every function on the stack, use the (kv) variant of the Display Stack Backtrace (k) command. It is critical to note that as per the x64 calling convention, the first four parameters to a function are passed via CPU register and not through the stack. Therefore, the values displayed under "Args to Child" which are values on the stack do not represent the actual parameters to the function and using them as such can be quite misleading. Due to this, the (kv) command has very limited use on x64.

0:000> kv
Child-SP          RetAddr               : Args to Child                                                           : Call Site
000000e9`799dfd48 00007ff6`f8853b86     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : notepad!wWinMain
000000e9`799dfd50 00007ff9`fd857034     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : notepad!__scrt_common_main_seh+0x106
000000e9`799dfd90 00007ff9`ff0dd0d1     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
000000e9`799dfdc0 00000000`00000000     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21

Displaying strings

Now we look at some WinDBG commands that can be used to display different types of strings used by applications, such as ASCII strings, wide char strings, and Unicode strings. One relatively straightforward way of finding such strings is to look for symbols in modules such as notepad.exe or NTDLL.dll that represent data values as opposed to functions and whose names are indicative of them representing strings. The list of such variable names can be found using the Examine Symbols (x) command with the (/d) option. We also add the (/a) option which will display the output in ascending order of addresses The following output has been edited for brevity.

0:000> x /a /d ntdll!*
00007ff9`ff1ac000 ntdll!RtlpSlashSlashDot = <no type information>
00007ff9`ff1ac010 ntdll!RtlpDosCONOUTDevice = <no type information>
00007ff9`ff1ac020 ntdll!RtlpDosPRNDevice = <no type information>
00007ff9`ff1ac030 ntdll!RtlpDosNULDevice = <no type information>
00007ff9`ff1ac040 ntdll!RtlpDosAUXDevice = <no type information>
. . . [REDACTED]

Using the above technique on notepad.exe we obtain the symbol notepad!_sz_ADVAPI32_dll. The presence of 'sz' in the name of the data variable implies that the memory contains a NULL-terminated ASCII string. With this assumption, we run the (da) variant of the Display Memory command.

0:000> da notepad!_sz_ADVAPI32_dll
00007ff6`f88573a0  "ADVAPI32.dll"

Again, using the same symbols listing technique listed above, we obtain the symbols ntdll!SlashSystem32SlashString. Unlike the previous case, the name is not suggestive as to the type of string this data variable represents. It may be an ASCII string, wide char string, or Unicode string. When the format of the data in memory is not known in advance, the (dc) variant of the Display Memory command can be used to display the contents of that memory in both DWORD (32-bit) format and as ASCII characters provided the characters are printable.

0:000> dc ntdll!SlashSystem32SlashString
00007ff9`ff1ac1e0  00160014 00000000 ff1b75c8 00007ff9  .........u......
00007ff9`ff1ac1f0  001a0018 00000000 ff1b6fd0 00007ff9  .........o......
00007ff9`ff1ac200  ff0d08e0 00007ff9 ff0fd2c0 00007ff9  ................
00007ff9`ff1ac210  00000000 00000000 ff114580 00007ff9  .........E......
00007ff9`ff1ac220  ff0cea30 00007ff9 ff1a1c70 00007ff9  0.......p.......
00007ff9`ff1ac230  ff0d2950 00007ff9 ff0fd2c0 00007ff9  P)..............
00007ff9`ff1ac240  ff114cf0 00007ff9 ff114580 00007ff9  .L.......E......
00007ff9`ff1ac250  ff0d4700 00007ff9 ff0fd2c0 00007ff9  .G..............

By observing the contents of the memory location and recognizing the pattern - 16-bit integer, 16-bit integer, 32-bit NULL, 64-bit address, we can assume that we have a Unicode string header in memory. To ascertain this let us display the data type for the Unicode string header using the Display Type (dt) command.

0:000> dt ntdll!_UNICODE_STRING
    +0x000 Length           : Uint2B
    +0x002 MaximumLength    : Uint2B
    +0x008 Buffer           : Ptr64 Wchar

We can clearly see that the contents of memory match the fields of the UNICODE_STIRNG structure. So we go one step further and typecast the contents of memory at the symbol ntdll!SlashSystem32SlashString (0x00007ff9`ff1ac1e0) to the UNICODE_STIRNG structure.

0:000> dt ntdll!_UNICODE_STRING 00007ff9`ff1ac1e0
    "\SYSTEM32\"
    +0x000 Length           : 0x14
    +0x002 MaximumLength    : 0x16
    +0x008 Buffer           : 0x00007ff9`ff1b75c8  "\SYSTEM32\"

Now that we have confirmed that ntdll!SlashSystem32SlashString contains a Unicode string header, let us go ahead and display the string with the (dS) variant of the Display String commands.

0:000> dS ntdll!SlashSystem32SlashString
00007ff9`ff1b75c8  "\SYSTEM32\"

Instead of using the address of the UNICODE_STRING structure itself, we can also use the address in the Buffer field of the UNICODE_STRING structure (i.e. 0x00007ff9`ff1b75c8) to display the string. To do this we use the (du) variant of the Display Memory command. Please note that the wide char string must be NULL-terminated.

0:000> du 0x00007ff9`ff1b75c8
00007ff9`ff1b75c8  "\SYSTEM32\"

Displaying Memory Contents

Now that we know that the memory at the symbol notepad!_sz_ADVAPI32_dll contains an ASCII string, let us display the same memory in various other formats such as 8-bit byte (db), 16-bit word (dw), 32-bit double-word (dd) and 64-bit quad-word (dq). Please note that only the (db) command displayed the output in ASCII as well as hexadecimal numbers.

Display as bytes (char).

0:000> db notepad!_sz_ADVAPI32_dll
00007ff6`f88573a0  41 44 56 41 50 49 33 32-2e 64 6c 6c 00 00 00 00  ADVAPI32.dll....
00007ff6`f88573b0  22 05 93 19 01 00 00 00-a8 c9 02 00 00 00 00 00  "...............
00007ff6`f88573c0  00 00 00 00 01 00 00 00-b0 c9 02 00 20 00 00 00  ............ ...
00007ff6`f88573d0  00 00 00 00 05 00 00 00-00 00 00 00 00 00 00 00  ................
00007ff6`f88573e0  61 00 70 00 69 00 2d 00-6d 00 73 00 2d 00 77 00  a.p.i.-.m.s.-.w.
00007ff6`f88573f0  69 00 6e 00 2d 00 63 00-6f 00 72 00 65 00 2d 00  i.n.-.c.o.r.e.-.
00007ff6`f8857400  73 00 79 00 6e 00 63 00-68 00 2d 00 6c 00 31 00  s.y.n.c.h.-.l.1.
00007ff6`f8857410  2d 00 32 00 2d 00 30 00-2e 00 64 00 6c 00 6c 00  -.2.-.0...d.l.l.

Display as words (short).

0:000> dw notepad!_sz_ADVAPI32_dll
00007ff6`f88573a0  4441 4156 4950 3233 642e 6c6c 0000 0000
00007ff6`f88573b0  0522 1993 0001 0000 c9a8 0002 0000 0000
00007ff6`f88573c0  0000 0000 0001 0000 c9b0 0002 0020 0000
00007ff6`f88573d0  0000 0000 0005 0000 0000 0000 0000 0000
00007ff6`f88573e0  0061 0070 0069 002d 006d 0073 002d 0077
00007ff6`f88573f0  0069 006e 002d 0063 006f 0072 0065 002d
00007ff6`f8857400  0073 0079 006e 0063 0068 002d 006c 0031
00007ff6`f8857410  002d 0032 002d 0030 002e 0064 006c 006c

Display as double-words (long).

0:000> dd notepad!_sz_ADVAPI32_dll
00007ff6`f88573a0  41564441 32334950 6c6c642e 00000000 
00007ff6`f88573b0  19930522 00000001 0002c9a8 00000000 
00007ff6`f88573c0  00000000 00000001 0002c9b0 00000020 
00007ff6`f88573d0  00000000 00000005 00000000 00000000 
00007ff6`f88573e0  00700061 002d0069 0073006d 0077002d 
00007ff6`f88573f0  006e0069 0063002d 0072006f 002d0065 
00007ff6`f8857400  00790073 0063006e 002d0068 0031006c 
00007ff6`f8857410  0032002d 0030002d 0064002e 006c006c 

Display as quad-words (__int64).

0:000> dq notepad!_sz_ADVAPI32_dll
00007ff6`f88573a0  32334950`41564441  00000000`6c6c642e
00007ff6`f88573b0  00000001`19930522  00000000`0002c9a8
00007ff6`f88573c0  00000001`00000000  00000020`0002c9b0
00007ff6`f88573d0  00000005`00000000  00000000`00000000
00007ff6`f88573e0  002d0069`00700061  0077002d`0073006d
00007ff6`f88573f0  0063002d`006e0069  002d0065`0072006f
00007ff6`f8857400  0063006e`00790073  0031006c`002d0068
00007ff6`f8857410  0030002d`0032002d  006c006c`0064002e

Navigating in assembler

Although there are better reverse engineering tools than WinDBG such as Ghidra, WinDBG does provide the ability to navigate through assembler functions. The most glaring feature missing in WinDBG is the ability to perform cross-referencing.

To unassemble the instructions starting at the current instruction pointer (RIP) we use the Unassemble (u) command and the RIP register as the address parameter. The (u) command uses a linear scan algorithm to disassemble the opcodes for the next 8 instructions.

0:000> u @rip
notepad!wWinMain:
00007ff6`f883b090 488bc4          mov     rax,rsp
00007ff6`f883b093 48895808        mov     qword ptr [rax+8],rbx
00007ff6`f883b097 48897010        mov     qword ptr [rax+10h],rsi
00007ff6`f883b09b 48897818        mov     qword ptr [rax+18h],rdi
00007ff6`f883b09f 4c896020        mov     qword ptr [rax+20h],r12
00007ff6`f883b0a3 55              push    rbp
00007ff6`f883b0a4 4156            push    r14
00007ff6`f883b0a6 4157            push    r15

To unassemble the instructions backward we use the (ub) variant of the Unassemble command and again specify the RIP register as the address parameter to disassemble 8 instructions prior to the address in the RIP register. The following listing shows a series of INT 3 instructions before the start of the function notepad!wWinMain. These INT 3 instructions were added by the compiler to ensure that notepad!wWinMain starts at a 16 (0x10) byte boundary and provides a small code cave that can be potentially used for inline hooking.

0:000> ub @rip
notepad!IsElevated+0x80:
00007ff6`f883b088 cc              int     3
00007ff6`f883b089 cc              int     3
00007ff6`f883b08a cc              int     3
00007ff6`f883b08b cc              int     3
00007ff6`f883b08c cc              int     3
00007ff6`f883b08d cc              int     3
00007ff6`f883b08e cc              int     3
00007ff6`f883b08f cc              int     3

To disassemble more than 8 instructions we can specify an address range consisting of an address (RIP) and object count (L) followed by the number of instructions to display.

0:000> ub @rip L10
notepad!IsElevated+0x65:
00007ff6`f883b06d 85c0            test    eax,eax
00007ff6`f883b06f 0f455c2448      cmovne  ebx,dword ptr [rsp+48h]
00007ff6`f883b074 48ff150dba0100  call    qword ptr [notepad!_imp_CloseHandle (00007ff6`f8856a88)]
00007ff6`f883b07b 0f1f440000      nop     dword ptr [rax+rax]
00007ff6`f883b080 8bc3            mov     eax,ebx
00007ff6`f883b082 4883c430        add     rsp,30h
00007ff6`f883b086 5b              pop     rbx
00007ff6`f883b087 c3              ret
00007ff6`f883b088 cc              int     3
00007ff6`f883b089 cc              int     3
00007ff6`f883b08a cc              int     3
00007ff6`f883b08b cc              int     3
00007ff6`f883b08c cc              int     3
00007ff6`f883b08d cc              int     3
00007ff6`f883b08e cc              int     3
00007ff6`f883b08f cc              int     3

Both the (u) and the (ub) variants use a linear sweep algorithm to disassemble a function and as such are not aware of function boundaries or basic blocks within functions. The Unassemble Function (uf) command on the other hand uses a recursive algorithm that evaluates every basic block within a function to find other basic blocks. The following output has been edited for brevity.

0:000> uf @rip 
notepad!wWinMain:
00007ff6`f883b090 488bc4          mov     rax,rsp
00007ff6`f883b093 48895808        mov     qword ptr [rax+8],rbx
00007ff6`f883b097 48897010        mov     qword ptr [rax+10h],rsi
00007ff6`f883b09b 48897818        mov     qword ptr [rax+18h],rdi
00007ff6`f883b09f 4c896020        mov     qword ptr [rax+20h],r12
00007ff6`f883b0a3 55              push    rbp
00007ff6`f883b0a4 4156            push    r14
00007ff6`f883b0a6 4157            push    r15
00007ff6`f883b0a8 488d68a1        lea     rbp,[rax-5Fh]
00007ff6`f883b0ac 4881ec90000000  sub     rsp,90h
. . . 
[REDACTED]
. . .
00007ff6`f883b427 8b451f          mov     eax,dword ptr [rbp+1Fh]
00007ff6`f883b42a 4c8d9c2490000000 lea     r11,[rsp+90h]
00007ff6`f883b432 498b5b20        mov     rbx,qword ptr [r11+20h]
00007ff6`f883b436 498b7328        mov     rsi,qword ptr [r11+28h]
00007ff6`f883b43a 498b7b30        mov     rdi,qword ptr [r11+30h]
00007ff6`f883b43e 4d8b6338        mov     r12,qword ptr [r11+38h]
00007ff6`f883b442 498be3          mov     rsp,r11
00007ff6`f883b445 415f            pop     r15
00007ff6`f883b447 415e            pop     r14
00007ff6`f883b449 5d              pop     rbp
00007ff6`f883b44a c3              ret

If all we are interested in are the calls a function is making as opposed to the actual disassembly the (/c) flag to the (uf) command can list those. The following output has been edited for brevity.

0:000> uf /c @rip 
notepad!wWinMain (00007ff6`f883b090)
    notepad!wWinMain+0x41 (00007ff6`f883b0d1):
    call to notepad!wil::details::FeatureImpl. . . (00007ff6`f883cf44)
    notepad!wWinMain+0x92 (00007ff6`f883b122):
    call to notepad!wil_details_FeatureReporting_ReportUsageToService (00007ff6`f883e014)

[REDACTED]

    notepad!wWinMain+0x35e (00007ff6`f883b3ee):
    call to KERNEL32!FreeLibraryStub (00007ff9`fd85c7d0)
    notepad!wWinMain+0x36a (00007ff6`f883b3fa):
    call to combase!CoUninitialize (00007ff9`fd9b28d0) 
    notepad!wWinMain+0x38b (00007ff6`f883b41b):
    call to notepad!_imp_load_EventUnregister (00007ff6`f88538db)

Continuing Execution

Now that we are done with the walkthrough let WinBDG continue executing the process notepad.exe using the Go (g) command, once again.

0:000> g

List of commands

To recap here are the list commands we have learned about in this tutorial.

vercommand Show Debugger Command Line
vertarget Show Target Computer Version
!cpuid Displays information about the processors on the system
.sympath Set Symbol Path
!lmi Displays detailed information about a module
| Process Status
~ Thread Status
lm List Loaded Modules
? Evaluate Expression
x Examine Symbols
bp Set Breakpoint
bl Breakpoint Enable
g Go
r Registers
dp Display Memory - Pointer-sized values
dps Display Referenced Memory - Display known symbols
k Display Stack Backtrace
ln List Nearest Symbols
da Display Memory - ASCII characters
dc Display Memory - Double-word values and ASCII characters
dt Display Type
dS Display String - UNICODE_STRING structure
du Display Memory - Wide char characters
db Display Memory - ASCII characters
dw Display Memory - Word values
dd Display Memory - Double-word values
dq Display Memory - Quad-word values
u Unassemble
ub Unassemble backward
uf Unassemble Function

Finishing up

This brings us to the conclusion of this tutorial of executing the most common commands in WinDBG on a user-mode process. We have learned how to set up symbols, launch a process from WinDBG, obtain basic information about the target process, threads and modules, set breakpoints, display stacks, display memory contents, display stack traces and navigate through function assembler listings. Most of these commands we have learned here would also work when WinDBG is used as a kernel debugger.

 

Sursa: http://codemachine.com/articles/windbg_quickstart.html

  • Upvote 2
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...