githubEdit
windbgwinternalpe-structure

Dissecting PE with WinDbg

Dissecting the PE structure with WinDbg

The aim of this post is to get better at windbg, and also get a better understanding of the PE Structure. Alright so without wasting any time, I'll start with opening WinDbg and starting notepad.exe or just attach the debugger to it if already started.

This is what we would see when we first attach the debugger.

*** wait with pending attach

************* Path validation summary **************
Response                         Time (ms)     Location
Deferred                                       srv*
Symbol search path is: srv*
Executable search path is: 
ModLoad: 00007ff6`fa9a0000 00007ff6`fa9d8000   C:\Windows\system32\notepad.exe
ModLoad: 00007ffc`6f1b0000 00007ffc`6f3a8000   C:\Windows\SYSTEM32\ntdll.dll
ModLoad: 00007ffc`6ea10000 00007ffc`6ead2000   C:\Windows\System32\KERNEL32.DLL
ModLoad: 00007ffc`6cec0000 00007ffc`6d1b7000   C:\Windows\System32\KERNELBASE.dll
ModLoad: 00007ffc`6e150000 00007ffc`6e17b000   C:\Windows\System32\GDI32.dll
ModLoad: 00007ffc`6cd30000 00007ffc`6cd52000   C:\Windows\System32\win32u.dll
ModLoad: 00007ffc`6c830000 00007ffc`6c949000   C:\Windows\System32\gdi32full.dll
ModLoad: 00007ffc`6c950000 00007ffc`6c9ed000   C:\Windows\System32\msvcp_win.dll
ModLoad: 00007ffc`6cc30000 00007ffc`6cd30000   C:\Windows\System32\ucrtbase.dll
ModLoad: 00007ffc`6eea0000 00007ffc`6f03d000   C:\Windows\System32\USER32.dll
ModLoad: 00007ffc`6e3d0000 00007ffc`6e723000   C:\Windows\System32\combase.dll
ModLoad: 00007ffc`6eb40000 00007ffc`6ec66000   C:\Windows\System32\RPCRT4.dll
ModLoad: 00007ffc`6e800000 00007ffc`6e8ad000   C:\Windows\System32\shcore.dll
ModLoad: 00007ffc`6e970000 00007ffc`6ea0e000   C:\Windows\System32\msvcrt.dll
ModLoad: 00007ffc`56df0000 00007ffc`5708b000   C:\Windows\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.19041.6926_none_60b5a53971f8f7e6\COMCTL32.dll
ModLoad: 00007ffc`6e0c0000 00007ffc`6e0ef000   C:\Windows\System32\IMM32.DLL
ModLoad: 00007ffc`6cba0000 00007ffc`6cc22000   C:\Windows\System32\bcryptPrimitives.dll
ModLoad: 00007ffc`6e8b0000 00007ffc`6e961000   C:\Windows\System32\ADVAPI32.dll
ModLoad: 00007ffc`6e180000 00007ffc`6e21f000   C:\Windows\System32\sechost.dll
ModLoad: 00007ffc`6cb70000 00007ffc`6cb97000   C:\Windows\System32\bcrypt.dll
ModLoad: 00007ffc`6a6d0000 00007ffc`6a6e2000   C:\Windows\SYSTEM32\kernel.appcore.dll
ModLoad: 00007ffc`6a1e0000 00007ffc`6a27e000   C:\Windows\system32\uxtheme.dll
ModLoad: 00007ffc`6db00000 00007ffc`6dba9000   C:\Windows\System32\clbcatq.dll
ModLoad: 00007ffc`5e6e0000 00007ffc`5e7d8000   C:\Windows\System32\MrmCoreR.dll
ModLoad: 00007ffc`6d380000 00007ffc`6daf2000   C:\Windows\System32\SHELL32.dll
ModLoad: 00007ffc`6a8d0000 00007ffc`6b076000   C:\Windows\SYSTEM32\windows.storage.dll
ModLoad: 00007ffc`6c190000 00007ffc`6c1bb000   C:\Windows\system32\Wldp.dll
ModLoad: 00007ffc`6f0a0000 00007ffc`6f16d000   C:\Windows\System32\OLEAUT32.dll
ModLoad: 00007ffc`6eae0000 00007ffc`6eb3b000   C:\Windows\System32\shlwapi.dll
ModLoad: 00007ffc`6ed60000 00007ffc`6ee75000   C:\Windows\System32\MSCTF.dll
ModLoad: 00007ffc`57240000 00007ffc`572ec000   C:\Windows\system32\TextShaping.dll
ModLoad: 00007ffc`4a940000 00007ffc`4aa1e000   C:\Windows\System32\efswrt.dll
ModLoad: 00007ffc`5fff0000 00007ffc`6000d000   C:\Windows\System32\MPR.dll
ModLoad: 00007ffc`67e90000 00007ffc`67fe7000   C:\Windows\SYSTEM32\wintypes.dll
ModLoad: 00007ffc`66250000 00007ffc`66453000   C:\Windows\System32\twinapi.appcore.dll
ModLoad: 00007ffc`53190000 00007ffc`531f6000   C:\Windows\System32\oleacc.dll
ModLoad: 00007ffc`5a710000 00007ffc`5a809000   C:\Windows\SYSTEM32\textinputframework.dll
ModLoad: 00007ffc`699f0000 00007ffc`69ae2000   C:\Windows\System32\CoreMessaging.dll
ModLoad: 00007ffc`69310000 00007ffc`6966b000   C:\Windows\System32\CoreUIComponents.dll
ModLoad: 00007ffc`6e790000 00007ffc`6e7fb000   C:\Windows\System32\WS2_32.dll
ModLoad: 00007ffc`6ba60000 00007ffc`6ba93000   C:\Windows\SYSTEM32\ntmarta.dll
(174c.f7c): Break instruction exception - code 80000003 (first chance)
ntdll!DbgBreakPoint:
00007ffc`6f251180 cc              int     3

The debugger loads the main executable and then all the other DLLs which are required by it. One thing to note is that the NTDLL.dll is almost always loaded as the first dll since it is always required for handling the transition from user mode to kernel mode. We can also see the address range for the dll which are loaded and also the base address of the exe (00007ff6fa9a0000).

Loading Symbols

First things first, we have to load the symbols for windbg. This is to make sure that windbg can resolve internal structures like the _TEB and _PEB. After which we can run .cls to clear the screen.

TEB & PEB

There are n no. of ways to read the TEB and PEB, I'll show the easiest ones.

PE Headers

DOS Header

Since we have the base address for the exe, we can try to look at the _IMAGE_DOS_HEADER Structure using the dt command, which tries to display type located at the particular address. We can refer this site for the command usage. Here's what all we can do with it.

Recalling the Dos Header, we know that the first member is the magic bytes and the last member e_lfanew contains the offset to the NT Headers structure.

DOS Stub & Rich Headers

To get a look at the DOS Stub and the Rich Header, we would have to manually dump the memory after the e_lfanew until the start of the NT Header. We can do this using different commands like dq, dd, dw, db to dump the memory in QWORD, DWORD, WORD and BYTE respectively. Or we can also use the View->memory and give the address manually to take a look at it.

We can see the Dos stub and also the Rich string which proves the presence of the Rich header. Although it is xor encrypted and to my knowledge there aren't inbuilt commands to display it.

NT Headers

Let's take a look at the NT Headers, we know it consists of only a static signature which is set to PE and the address of the File header and the Optional Header.

File Header & Optional Header

And then comes the file header and the optional header, I won't deep dive much into this and directly start with the data directories.

Data Directories

I'll use the !dh command to dump the headers for the file, we need to provide the image base address as the argument for it to work. Here's what all parameters we can provide to it.

I'll use -f in order to avoid dumping all the section headers.

I can also check it through the Optional Header way, When we look at the optional header, we get the choice to click at the DataDirectory since it is a structure, which allows us to look at that particular structure.

Import Directory

and I can see the Virtual Address for the Import Table which is at an offset of 0x2d0c8. I can also dump the memory using dd and look at all the other imports as well, since both the members of the _IMAGE_DATA_DIRECTORY are of type unsigned long which is 4 bytes aka DWORD.

The Import Table consists of series of _IMAGE_IMPORT_DESCRIPTOR structures, and there's no member/structure that tells us the count of them present. All we know is that the last entry would have every member zeroed out.

One important thing to note here is that the type UNION has all of its member share the same memory address space. So, the Characteristics and the OriginalFirstThunk are both the one and same. I'm not sure what's the use of characteristics here though, please let me know if you are aware of it.

The OriginalFirstThunk member points to the ILT or the Import Lookup Table which is very similar to IAT but the only thing is that it remains static and contains RVA and ordinal or hint-name table for the functions imported, and the IAT gets overwritten with the address of the imported functions when the binary is loaded.

This can be a bit confusing at first (atleast it did for me), but think of it like a double pointer. I have provided a visualization diagram below (just before the IAT section) which you can take a look at to get a better understanding of the flow.

Looking at the OriginalFirstThunk (2d3e0), we see a list of RVAs, those RVAs are pointing to the ILT or the Import Lookup Table. And visiting that RVA, we can see the list of all the functions imported into the binary. The reason behind this behavior is explained well herearrow-up-right. We can see the name of the DLL being loaded by looking at the Name for each of the entry. In this case, the first one being Kernel32.dll

The hint-name table structure is as follows

Where the Hint is the number that is used to lookup the function in the DLL, as the name suggests, it "hints" where the PE should start looking for that particular function. its first used as index to Export Name Table pointer array (of the DLL) , and if that is incorrect then a binary search is performed.

So what we do is take the first _IMAGE_DESCRIPTOR_TABLE which will give us a list of RVAs present in the ILT for one DLL Loaded, in this case the first one was kernel32.dll so we can see all the functions imported from it.

Now let's take a look at the FirstThunk, this is what points to the IAT.

And we can see the functions being loaded into the process from the kernel32.dll. The ln command is used to list the nearest symbol, could be used to determine what a pointer is pointing to.

This was a bit confusing for me, so I decided to visualize it using a small diagram which helped me with it.

IAT

We can also look at the IAT using the dps command, this will display the symbols present at that address (you can check this MSDNarrow-up-right). Since from the !dh command, we know that the IAT is located at an offset of 267E8.

We can see that the GetProcAddressStub is located at the same address as when we checked the ILT. The only difference b/w them being that the IAT is overwritten during runtime with the correct Virtual Addresses.

Export Directory

Recalling our output of dumping the header, we know that notepad.exe doesn't export anything, so I'll probably have to look at a DLL in order to have an export directory with functions actually being exported, so I'll look at the kernel32.dll. I'll use the dh command to dump it's headers, which will give me the base address for it as well. I'll also save the base address in a temporary register for my ease.

Now I can take a look at the Export Directory for it. We know that the members of the Export Directory are of type _IMAGE_EXPORT_DIRECTORY and the structure is as follows

I'll start with dumping the export directory using dd.

Now that we have the AddressOfNames member, we can take a look at it and get the names of all the functions imported by the DLL.

We can also take a look at the AddressOfNameOrdinals to see the ordinals for the functions exported.

This looks soo neat to me. Anyways, that's it for now, I really enjoyed showcasing this, and I hope you do too. I'm too tired and want some sleep now.

References

Last updated