Windows Process Internals with WinDbg

Last modified: 2024-05-21

Debugger Reverse Engineering Windows

We can examine the Windows process using WinDbg.

Preparation

Here we’re going to start notepad process as example.

Click FileLaunch Executable then select C:\Windows\System32\notepad.exe.

After that, if we’ve not already set symbol file path, run the following commands:

.sympath SRV*c:\symbols*http://msdl.microsoft.com/download/symbols
.reload /f

Examine DOS Header

1. Get Address of Module Base Address

Firstly, we need to get the module (notepad here) base address. To do that, list loaded modules with the following command:

lm

Output:

start             end                 module name
00007ff6`52990000 00007ff6`529ea000   notepad    (pdb symbols)          C:\ProgramData\Dbg\sym\notepad.pdb\123456789ABCDEF123456789ABCDEF123\notepad.pdb
00007ffb`0e1a0000 00007ffb`0e433000   COMCTL32   (deferred)             
00007ffb`29c10000 00007ffb`29d21000   ucrtbase   (deferred) 

As output above, the address of the notepad is 00007ff6`52990000 so take a note of this address.

2. Examine IMAGE_DOS_HEADER Structure

To get inside the DOS header, run the following command with specified address that we retrieved in the previous section:

dt ntdll!_IMAGE_DOS_HEADER 00007ff6`52990000

Output:

   +0x000 e_magic          : 0x5a4d
   ...
   +0x03c e_lfanew         : 0n240

Maybe what data we should check are e_magic and e_lfanew .

  • e_magic: It contains 2-byte (0x5A4D : MZ in ASCII) data.
  • e_lfanew: It holds an offset of NT Headers.

3. Examine e_magic

dd 00007ff6`52990000 L1

Output:

00007ff6`52990000  00905a4d

As above, we can see the value contains 5A4D.

4. Examine e_lfanew

To see the value of e_lfanew, run the following command with specifying the offset 0x03C:

dd 00007ff6`52990000+0x03C L1

Output:

00007ff6`5299003c  000000f0

As above, we get 000000f0 . This value is an offset of PE Header.

5. Examine PE Header

To get information of PE header, run the following command with specifying the offset 0x0F0:

dd 00007ff6`52990000+0x0F0 L1

Output:

00007ff6`529900f0  00004550

The result value is 00004550 that is \0\0EP (little-endian, so it means PE\0\0 in ASCII.


Examine PEB (Process Environment Block)

1. Get Address of PEB

At first, we need to know the PEB (Process Environment Block) address with the following command:

!peb

Output:

PEB at 00000055d7905000
...

Although the output is large, we can get the address at the first line as above.

2. PEB Structure

To see the inside of the PEB, run dt command with the specified address:

dt _PEB 00000055d7905000

Output:

ntdll!_PEB
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   ...
   +0x018 Ldr : 0x00007ffb`2ca76440 _PEB_LDR_DATA
   ...

What we should pay attention to is the Ldr line because the Ldr has important roles in loading APIs required by the executable file when the process is started.

So take a note of this LDR address (0x00007ffb`2ca76440 here).

3. Examine Ldr Structure

Same ways as above, run dt command to see the Ldr structure. Please note that we should specify the _PEB_LDR_DATA .

dt _PEB_LDR_DATA 0x00007ffb`2ca76440

Output:

ntdll!_PEB_LDR_DATA
   +0x000 Length           : 0x58
   +0x004 Initialized      : 0x1 ''
   +0x008 SsHandle         : (null) 
   +0x010 InLoadOrderModuleList : _LIST_ENTRY [ 0x000001c4`b1ef28c0 - 0x000001c4`b1eff510 ]
   +0x020 InMemoryOrderModuleList : _LIST_ENTRY [ 0x000001c4`b1ef28d0 - 0x000001c4`b1eff520 ]
   +0x030 InInitializationOrderModuleList : _LIST_ENTRY [ 0x000001c4`b1ef2740 - 0x000001c4`b1ef75e0 ]
   +0x040 EntryInProgress  : (null) 
   +0x048 ShutdownInProgress : 0 ''
   +0x050 ShutdownThreadId : (null) 

We’re going to see the details of InLoadOrderModuleList in the next, so take a note of the address (0x000001c4`b1ef28c0).

4. Examine InLoadOrderModuleList Structure

To see the information in the InLoadOrderModuleList , run dt command as below. Please note that we should set _LDR_DATA_TABLE_ENTRY instead of _LIST_ENTRY to see the desired information.

dt _LDR_DATA_TABLE_ENTRY 0x000001c4`b1ef28c0

Output:

ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x000001c4`b1ef2720 - 0x00007ffb`2ca76450 ]
   +0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0x000001c4`b1ef2730 - 0x00007ffb`2ca76460 ]
   +0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
   +0x030 DllBase          : 0x00007ff6`52990000 Void
   +0x038 EntryPoint       : 0x00007ff6`529919a0 Void
   +0x040 SizeOfImage      : 0x5a000
   +0x048 FullDllName      : _UNICODE_STRING "C:\Windows\System32\notepad.exe"
   +0x058 BaseDllName      : _UNICODE_STRING "notepad.exe"
   ...
   

As above, the notepad.exe itself is loaded in here by seeing the BaseDllName line.

5. Examine Next InLoadOrderModuleList Structure

To examine the next loaded module, we should get the next InLoadOrderModuleList structure. To do that, run the following command with specifying the address of the InLoadOrderModuleList that we took a note of in the previous section. Please note that we should set _LIST_ENTRY to see the next entry:

dt _LIST_ENTRY 0x000001c4`b1ef28c0

Output:

ntdll!_LIST_ENTRY
 [ 0x000001c4`b1ef2720 - 0x00007ffb`2ca76450 ]
   +0x000 Flink            : 0x000001c4`b1ef2720 _LIST_ENTRY [ 0x000001c4`b1ef75c0 - 0x000001c4`b1ef28c0 ]
   +0x008 Blink            : 0x00007ffb`2ca76450 _LIST_ENTRY [ 0x000001c4`b1ef28c0 - 0x000001c4`b1eff510 ]

This output contains the next entry (Flink) and the last entry (Blink). Naturally, what we want is the Flink!. So please take a note of this address (0x000001c4`b1ef2720)

Now we can get the next data entry with the following command:

dt _LDR_DATA_TABLE_ENTRY 0x000001c4`b1ef2720

Output:

ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x000001c4`b1ef75c0 - 0x000001c4`b1ef28c0 ]
   +0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0x000001c4`b1ef75d0 - 0x000001c4`b1ef28d0 ]
   +0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x000001c4`b1ef7bd0 - 0x00007ffb`2ca76470 ]
   +0x030 DllBase          : 0x00007ffb`2c8f0000 Void
   +0x038 EntryPoint       : (null) 
   +0x040 SizeOfImage      : 0x217000
   +0x048 FullDllName      : _UNICODE_STRING "C:\Windows\SYSTEM32\ntdll.dll"
   +0x058 BaseDllName      : _UNICODE_STRING "ntdll.dll"
   ...

Seeing at the BaseDllName line, it loads the ntdll.dll module.

If we repeat the steps above, we should see that the KERNEL32.DLL is loaded in the next entry.

6. Examine BaseDllName Structure

Up to this point, we have been able to see the information of InloadOrderModuleList structure. Next, let's take a look inside the BaseDllName.

With the example above, the offset of the BaseDllName from the InLoadOrderModuleList is +0x058 . So we can use this in the following command:

dt _UNICODE_STRING 000001c4`b1ef2720+0x058

Output:

ntdll!_UNICODE_STRING
 "ntdll.dll"
   +0x000 Length           : 0x12
   +0x002 MaximumLength    : 0x14
   +0x008 Buffer           : 0x00007ffb`2ca2e5f0  "ntdll.dll"