Nytro Posted June 28, 2023 Report Posted June 28, 2023 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 2 Quote