Getting Started with WinDbg

The Windows Debugger, also known as Win-Dee-Bee-Gee is one, if not THE, most popular tool used by reverse engineers and exploit developers to understand how an application works. It has hundreds of commands that allow you to step through the code, analyze crashes, gather information about loaded modules and images, inspect memory, work with symbols, get process or thread related information, and much more.

With so many commands, it can be difficult to analyze an application in WinDbg. That’s why I decided to go through some of the most useful commands and show you how to use them. First, let’s open our target application inside WinDbg. In this case, our target application is WinRar 6.0.0 (x64).

Every time you open an application inside WinDbg or attach WinDbg to a running process, the process is immediately paused. This allows you to gather some information about your target and set breakpoints.

So, let’s start by getting some information about our target with the !peb command, a command that dumps the content of the Process Environment Block (PEB).

0:000> !peb
PEB at 0000001fe0ba4000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            Yes
    ImageBaseAddress:         00007ff6e5760000
    NtGlobalFlag:             70
    NtGlobalFlag2:            0
    Ldr                       00007fff1120f3a0
    Ldr.Initialized:          Yes
    Ldr.InInitializationOrderModuleList: 000001f021862870 . 000001f021863100
    Ldr.InLoadOrderModuleList:           000001f021862a20 . 000001f02186dda0
    Ldr.InMemoryOrderModuleList:         000001f021862a30 . 000001f02186ddb0
                    Base TimeStamp                     Module
            7ff6e5760000 5fc684ae Dec 01 19:00:14 2020 C:\Program Files\WinRAR\WinRAR.exe
            7fff110b0000 20af46db May 18 19:37:31 1987 C:\Windows\SYSTEM32\ntdll.dll
            7fff0f080000 6edcef78 Dec 09 12:09:44 2028 C:\Windows\System32\KERNEL32.DLL
            7fff0d950000 91fb2582 Aug 11 18:57:38 2047 C:\Windows\System32\KERNELBASE.dll
            7fff10c80000 2aafc763 Sep 11 00:08:35 1992 C:\Windows\System32\USER32.dll
            7fff0dc70000 1900dcc9 Apr 18 06:08:41 1983 C:\Windows\System32\win32u.dll
            7fff0f520000 3d1ed208 Jun 30 11:40:24 2002 C:\Windows\System32\GDI32.dll
            7fff0d4b0000 c18abd9e Nov 23 03:53:18 2072 C:\Windows\System32\gdi32full.dll
            7fff0e4e0000 1d1dafdc Jun 24 20:13:48 1985 C:\Windows\System32\msvcp_win.dll
            7fff0dc90000 27fc3e10 Apr 05 11:06:24 1991 C:\Windows\System32\ucrtbase.dll
            7fff0eb90000 f7d4a12d Oct 05 00:14:05 2101 C:\Windows\System32\COMDLG32.dll
            7fff0e580000 20688290 Mar 26 02:21:20 1987 C:\Windows\System32\msvcrt.dll
            7fff0eca0000 ef941eb1 May 15 15:10:09 2097 C:\Windows\System32\combase.dll
            7fff10f30000 d066af57 Oct 17 13:34:47 2080 C:\Windows\System32\RPCRT4.dll
            7fff0d6b0000 0984de7c Jan 23 10:50:52 1975 C:\Windows\System32\bcryptPrimitives.dll
            7fff10e10000 76e52162 Mar 18 04:02:58 2033 C:\Windows\System32\shcore.dll
            7fff0e620000 2303fb66 Aug 13 10:18:46 1988 C:\Windows\System32\SHLWAPI.dll
            7fff0f760000 8971038d Jan 26 12:37:49 2043 C:\Windows\System32\SHELL32.dll
            7fff0d900000 a7a2bb4e Feb 14 15:50:54 2059 C:\Windows\System32\cfgmgr32.dll
            7fff0dd90000 71998de0 May 24 23:32:16 2030 C:\Windows\System32\
            7fff0f560000 44ac0c41 Jul 05 21:00:17 2006 C:\Windows\System32\advapi32.dll
            7fff0f4c0000 f030acf4 Sep 11 09:10:44 2097 C:\Windows\System32\sechost.dll
            7fff0d440000 c779bd8b Jan 19 08:26:03 2076 C:\Windows\System32\kernel.appcore.dll
            7fff0d460000 883ace6c Jun 05 06:28:28 2042 C:\Windows\System32\powrprof.dll
            7fff0d420000 ce335499 Aug 17 06:01:29 2079 C:\Windows\System32\profapi.dll
            7fff0f610000 1e6c2994 Mar 05 12:10:12 1986 C:\Windows\System32\ole32.dll
            7fff0efb0000 9aa147c8 Mar 17 05:02:16 2052 C:\Windows\System32\OLEAUT32.dll
            7ffefa3b0000 1129a048 Feb 15 16:31:20 1979 C:\Windows\WinSxS\\COMCTL32.dll
            7fff0ba30000 d4c34281 Feb 11 11:12:17 2083 C:\Windows\SYSTEM32\UxTheme.dll
            7ffef7080000 ebfce134 Jun 18 08:54:44 2095 C:\Windows\WinSxS\\gdiplus.dll
            7fff07d10000 8f22dbf7 Feb 05 07:53:43 2046 C:\Windows\SYSTEM32\MSIMG32.dll
    SubSystemData:     0000000000000000
    ProcessHeap:       000001f021860000
    ProcessParameters: 000001f021861fa0
    CurrentDirectory:  'C:\Users\user\Desktop\'
    WindowTitle:  'C:\Program Files\WinRAR\WinRAR.exe'
    ImageFile:    'C:\Program Files\WinRAR\WinRAR.exe'
    CommandLine:  '"C:\Program Files\WinRAR\WinRAR.exe"'
    DllPath:      '< Name not readable >'
    Environment:  000001f021861100
        CommonProgramFiles=C:\Program Files\Common Files
        CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files
        CommonProgramW6432=C:\Program Files\Common Files
        FPS_BROWSER_APP_PROFILE_STRING=Internet Explorer
        Path=C:\Program Files (x86)\Windows Kits\10\Debuggers\x64;C:\Python27\;C:\Python27\Scripts;C:\Program Files (x86)\Python37-32\Scripts\;C:\Program Files (x86)\Python37-32\;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Users\user\AppData\Local\Microsoft\WindowsApps;
        PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 78 Stepping 3, GenuineIntel
        ProgramFiles=C:\Program Files
        ProgramFiles(x86)=C:\Program Files (x86)
        ProgramW6432=C:\Program Files
        PSModulePath=C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules
        WINDBG_DIR=C:\Program Files (x86)\Windows Kits\10\Debuggers\x64

If we go through the !peb command’s output, we can see a lot of information about the target, including the location in memory of some key elements (e.g. heap), the list of modules and their base address, as well as the environment variables.

To get additional information about the loaded modules, one can take advantage of the lm command. The lm command is used to list modules and if used in conjunction with the v parameter, it provides a wealth of information. For example, let’s say we want to get more information about the ntdll module. In that case, we would use the following command.

0:000> lmv m ntdll
Browse full module list
start             end                 module name
00007fff`110b0000 00007fff`11290000   ntdll      (pdb symbols)          c:\symbols\ntdll.pdb\D1465676F2C4BFFF8390E9F1EA4CC4AA1\ntdll.pdb
    Loaded symbol image file: C:\Windows\SYSTEM32\ntdll.dll
    Image path: ntdll.dll
    Image name: ntdll.dll
    Browse all global symbols  functions  data
    Image was built with /Brepro flag.
    Timestamp:        20AF46DB (This is a reproducible build file hash, not a timestamp)
    CheckSum:         001DF94D
    ImageSize:        001E0000
    File version:     10.0.16299.1806
    Product version:  10.0.16299.1806
    File flags:       0 (Mask 3F)
    File OS:          40004 NT Win32
    File type:        2.0 Dll
    File date:        00000000.00000000
    Translations:     0409.04b0
    Information from resource tables:
        CompanyName:      Microsoft Corporation
        ProductName:      Microsoft® Windows® Operating System
        InternalName:     ntdll.dll
        OriginalFilename: ntdll.dll
        ProductVersion:   10.0.16299.1806
        FileVersion:      10.0.16299.1806 (WinBuild.160101.0800)
        FileDescription:  NT Layer DLL
        LegalCopyright:   © Microsoft Corporation. All rights reserved.

Okay. We now know how to get the list of loaded modules and retrieve some information about a specific module. What’s next? Well, modules typically provide functionality through exported functions also known as symbols. To see the symbols of a module, for example the kernel32 module, we can use the x command as follows:

0:000> x kernel32!*
00007fff`0f0a4dae KERNEL32!ThpCreateRawSnap$filt$0 (void)
00007fff`0f0a4ae0 KERNEL32!GlobalUnlock$filt$0 (void)
00007fff`0f090c14 KERNEL32!SdbpCleanupLocalDatabaseSupport (void)
00007fff`0f0a02b0 KERNEL32!CreateActCtxA (void)
00007fff`0f0d5368 KERNEL32!RtlStringCopyWideCharArrayWorker (void)
00007fff`0f0a4c69 KERNEL32!GlobalSize$fin$1 (void)
00007fff`0f093450 KERNEL32!QueryInformationJobObject (void)
00007fff`0f0c6c8c KERNEL32!Internal_InvokeSwitchCallbacksOnINIT (void)
00007fff`0f094250 KERNEL32!RegisterWaitForSingleObject (void)
00007fff`0f091dc8 KERNEL32!BasepFindActCtxSection_CheckAndConvertParameters (void)
00007fff`0f0a4928 KERNEL32!BaseDllReadWriteIniFileOnDisk$fin$0 (void)
00007fff`0f094d20 KERNEL32!GetLongPathNameW (void)
00007fff`0f0f54a8 KERNEL32!_imp_GetProcessPriorityBoost = <no type information>
00007fff`0f0b6884 KERNEL32!RtlStringCchPrintfW (<no parameter info>)
00007fff`0f0f4b00 KERNEL32!_imp_MoveFileWithProgressW = <no type information>
00007fff`0f0f3270 KERNEL32!ByteMatchSectionInstallers = <no type information>
00007fff`0f0e77a0 KERNEL32!QuirkIsEnabledForProcessWorker (<no parameter info>)
00007fff`0f09e3b0 KERNEL32!GetConsoleWindow (<no parameter info>)
00007fff`0f0f48a8 KERNEL32!_imp_ReadFile = <no type information>
00007fff`0f0f3400 KERNEL32!ByteMatchAppendedDataInstallers = <no type information>
00007fff`0f0f63c0 KERNEL32!_imp_wcsncpy = <no type information>
00007fff`0f0d6220 KERNEL32!BackupReadGhostedFileExtents (<no parameter info>)
00007fff`0f0f4318 KERNEL32!_imp_AppXPostSuccessExtension = <no type information>
00007fff`0f0b7160 KERNEL32!GetProcessWorkingSetSizeExStub (<no parameter info>)
00007fff`0f0f4340 KERNEL32!_imp_GetUnicodeStringToEightBitStringRoutine = <no type information>

Not surprisingly, the list of symbols for the kernel32 module is quite long. To see only symbols that contain a specific keyword, we can use the following syntax:

x modulename!*keyword*

Here is an example, to see only symbols in the kernel32 module that contain the word “File”.

0:000> x kernel32!*File*
00007fff`0f0a4928 KERNEL32!BaseDllReadWriteIniFileOnDisk$fin$0 (void)
00007fff`0f0eb1b8 KERNEL32!AslpFileGetImageNtHeader$filt$0 (void)
00007fff`0f08e310 KERNEL32!BaseDllReadWriteIniFileOnDisk (void)
00007fff`0f0a494c KERNEL32!AslpFileMappingGetFileKind$filt$0 (void)
00007fff`0f0a0ce0 KERNEL32!GetPrivateProfileSectionA (void)
00007fff`0f091be0 KERNEL32!GetPrivateProfileSectionW (void)
00007fff`0f08f5cc KERNEL32!AslFileMappingCreate (void)
00007fff`0f090b4c KERNEL32!AslFileMappingDelete (void)
00007fff`0f0a17d0 KERNEL32!WritePrivateProfileSectionW (void)
00007fff`0f0eac31 KERNEL32!AslpFileGetExeWrapper$filt$0 (void)
00007fff`0f0ebadc KERNEL32!AslpFileGetVersionBlock$filt$0 (void)
00007fff`0f0e5f2a KERNEL32!WerpUnregisterFile$fin$0 (void)
00007fff`0f0a05f0 KERNEL32!GetPrivateProfileIntA (void)
00007fff`0f09f450 KERNEL32!WerRegisterFileStub (<no parameter info>)
00007fff`0f0a2f20 KERNEL32!SetFilePointer (<no parameter info>)
00007fff`0f0b75a0 KERNEL32!OpenFileByIdStub (<no parameter info>)
00007fff`0f0a2be0 KERNEL32!FindFirstFileNameW (<no parameter info>)
00007fff`0f0f5600 KERNEL32!_imp_K32GetModuleFileNameExW = <no type information>
00007fff`0f0f48e0 KERNEL32!_imp_SetFileAttributesW = <no type information>
00007fff`0f0b6fd0 KERNEL32!GetCompressedFileSizeAStub (<no parameter info>)
00007fff`0f0fb508 KERNEL32!ENCRYPTFILEEX_NAME = <no type information>
00007fff`0f0f5540 KERNEL32!_imp_K32GetModuleFileNameExA = <no type information>
00007fff`0f08ecb0 KERNEL32!BaseDllIniFileNameLength (<no parameter info>)
00007fff`0f0e7c50 KERNEL32!AslFileNotFound (<no parameter info>)
00007fff`0f0f4b00 KERNEL32!_imp_MoveFileWithProgressW = <no type information>
00007fff`0f0f48a8 KERNEL32!_imp_ReadFile = <no type information>
00007fff`0f0d6220 KERNEL32!BackupReadGhostedFileExtents (<no parameter info>)

Despite being a bit shorter the list still counts dozens of functions. Nonetheless, if we scroll through the list, it is now possible to spot interesting functions like the KERNEL32!ReadFile or the KERNEL32!WriteFile, and their implementations (e.g. KERNEL32!_imp_ReadFile, KERNEL32!_imp_WriteFile, etc.).

Now, let’s assume we want to see the files WinRar opens. The function that is typically used for that is the CreateFile(). Using the command syntax seen a moment ago, we can look for every instance of the “CreateFile” string.

0:000> x kernel32!CreateFile*
00007fff`0f0a4f70 KERNEL32!CreateFileTransactedW$fin$0 (void)
00007fff`0f0a1fd0 KERNEL32!CreateFileTransactedW (void)
00007fff`0f09bf40 KERNEL32!CreateFileMappingA (<no parameter info>)
00007fff`0f0b6b30 KERNEL32!CreateFileMappingNumaWStub (<no parameter info>)
00007fff`0f0dabb0 KERNEL32!CreateFileMappingNumaA (<no parameter info>)
00007fff`0f09c750 KERNEL32!CreateFileMappingWStub (<no parameter info>)
00007fff`0f0da3b0 KERNEL32!CreateFileTransactedA (<no parameter info>)
00007fff`0f0a2af0 KERNEL32!CreateFile2 (<no parameter info>)
00007fff`0f0a2b10 KERNEL32!CreateFileW (<no parameter info>)
00007fff`0f0a2b00 KERNEL32!CreateFileA (<no parameter info>)

In this case, there are ten symbols that contain the “CreateFile” string, but only one is likely to be used by WinRar: KERNEL32!CreateFileW.

             HANDLE __fastcall CreateFileW(LPCWSTR lpFileName, DWORD 
             HANDLE            RAX:8          <RETURN>
             LPCWSTR           RCX:8          lpFileName
             DWORD             EDX:4          dwDesiredAccess
             DWORD             R8D:4          dwShareMode
             LPSECURITY_ATT    R9:8           lpSecurityAttributes
             DWORD             Stack[0x28]:4  dwCreationDisposition
             DWORD             Stack[0x30]:4  dwFlagsAndAttributes
             HANDLE            Stack[0x38]:8  hTemplateFile

To set a breakpoint at the kernel32!CreateFileW function, we can use the bp command as follows:

0:000> bp Kernel32!CreateFileW

To verify that our breakpoint has been set correctly, we can then use the bl command to list all the breakpoints.

0:000> bl
     0 e Disable Clear  00007fff`0f0a2b10     0001 (0001)  0:**** KERNEL32!CreateFileW

Great. Our breakpoint is set. Let’s resume the execution of WinRar. If everything goes well, we should hit the breakpoint.

0:000> g
ModLoad: 00007fff`11050000 00007fff`1107d000   C:\Windows\System32\IMM32.DLL
ModLoad: 00007ffe`fc1d0000 00007ffe`fc1e9000   C:\Windows\SYSTEM32\CLDAPI.dll
ModLoad: 00007fff`00c90000 00007fff`00c9a000   C:\Windows\SYSTEM32\FLTLIB.DLL
ModLoad: 00007ffe`fdaf0000 00007ffe`fdb72000   C:\Windows\SYSTEM32\AEPIC.dll
ModLoad: 00007fff`0c570000 00007fff`0c5a1000   C:\Windows\SYSTEM32\ntmarta.dll
ModLoad: 00007fff`0cf00000 00007fff`0cf25000   C:\Windows\SYSTEM32\bcrypt.dll
ModLoad: 00007fff`0cdf0000 00007fff`0ce07000   C:\Windows\SYSTEM32\cryptsp.dll
ModLoad: 00007fff`0e680000 00007fff`0e71e000   C:\Windows\System32\clbcatq.dll
ModLoad: 00007fff`09a30000 00007fff`09be1000   C:\Windows\system32\propsys.dll
Breakpoint 0 hit
*** ERROR: Module load completed but symbols could not be loaded for WinRAR.exe
00007fff`0f0a2b10 ff25e21e0500    jmp     qword ptr [KERNEL32!_imp_CreateFileW (00007fff`0f0f49f8)] ds:00007fff`0f0f49f8={KERNELBASE!CreateFileW (00007fff`0d991940)}

Perfect. The breakpoint 0 has been hit. At this point, to better understand how we got to the KERNEL32!CreateFileW function, a reverse engineer would typically inspect the stack using the k command.

0:000> k
 # Child-SP          RetAddr           Call Site
00 0000001f`e09a64c8 00007ff6`e57cf220 KERNEL32!CreateFileW
01 0000001f`e09a64d0 00007ff6`e57efbcb WinRAR+0x6f220
02 0000001f`e09a7570 00007ff6`e57ef8fb WinRAR+0x8fbcb
03 0000001f`e09ab930 00007ff6`e58496d5 WinRAR+0x8f8fb
04 0000001f`e09ac970 00007ff6`e58618e3 WinRAR+0xe96d5
05 0000001f`e09afaa0 00007fff`0f0937e4 WinRAR+0x1018e3
06 0000001f`e09afae0 00007fff`1111cb61 KERNEL32!BaseThreadInitThunk+0x14
07 0000001f`e09afb10 00000000`00000000 ntdll!RtlUserThreadStart+0x21

In this case, the KERNEL32!CreateFileW was called by the WinRAR+0x6f220 function. Cool. So what about the content of the registers. How can we take a look at the registers? That’s simple. We only need to type r in the command window and WinDbg will dump the content of all the main registers.

0:000> r
rax=0000000000000000 rbx=0000001fe09a75d0 rcx=0000001fe09a88e0
rdx=0000000080000000 rsi=0000000000000800 rdi=0000001fe09ab950
rip=00007fff0f0a2b10 rsp=0000001fe09a64c8 rbp=0000001fe09a88e0
 r8=0000000000000003  r9=0000000000000000 r10=0000000000000000
r11=0000001fe09a88e0 r12=0000000080000000 r13=0000000000000000
r14=0000000000000003 r15=0000000008000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
00007fff`0f0a2b10 ff25e21e0500    jmp     qword ptr [KERNEL32!_imp_CreateFileW (00007fff`0f0f49f8)] ds:00007fff`0f0f49f8={KERNELBASE!CreateFileW (00007fff`0d991940)}

And, if we want to take a look at the code, we can simply use the u command to unassemble the code at the location specified.

0:000> u rip
00007fff`0f0a2b10 ff25e21e0500    jmp     qword ptr [KERNEL32!_imp_CreateFileW (00007fff`0f0f49f8)]
00007fff`0f0a2b16 cc              int     3
00007fff`0f0a2b17 cc              int     3
00007fff`0f0a2b18 cc              int     3
00007fff`0f0a2b19 cc              int     3
00007fff`0f0a2b1a cc              int     3
00007fff`0f0a2b1b cc              int     3
00007fff`0f0a2b1c cc              int     3

So, let’s go back to our initial question and find out the first file opened by WinRar. We know that KERNEL32!CreateFileW opens a file and most likely the name of the file to open is passed as an argument.

However, there are a handful of ways to pass arguments to a function and it is very important to know which way (or calling convention) KERNEL32!CreateFileW is using. The easiest way to know the calling convention used by a function is to use your favorite disassembler (e.g. Ghidra, IDA Pro, etc.) and look at the prototype of the function. In this case, the KERNEL32!CreateFileW function uses the fastcall calling convention, which means that the pointer to the file name will be in RCX.

To dump the content of the RCX register, we have several options. The dd command or the du command. Since we expect RCX to point to a Unicode string, I will use the du command.

0:000> du rcx
0000001f`e09a88e0  "C:\Program Files\WinRAR\winrar.l"
0000001f`e09a8920  "ng"

And there you have it! C:\Program Files\WinRAR\winrar.lng. This language pack file called winrar.lng is the first file accessed by WinRar. Now, try to resume the execution and open a RAR archive. What happens? Is the breakpoint hit again? Well, if you find out, let me know in the comment section down below. 😉