top of page
  • Writer's pictureJosh Stroschein

Locating DLL Name from the Process Environment Block (PEB)

I often encounter software, especially when performing malware analysis, that dynamically constructs it’s own import table. This can be done for a variety of reasons and in a variety of ways. In this article, we’ll explore one method I recently encountered. I typically become suspicious of this activity when I see the following assembly instructions:

mov ebx, fs:[ 0x30 ]
mov ebx, [ ebx + 0xC ]
mov ebx, [ ebx + 0x14]
mov esi, [ ebx + 0x28 ]

The snippet above is just a small part of this type of functionality and will get the name of a module (DLL) loaded in the process that it is running in. With the name of the module, the malware can search for the desired module (by name) and then locate the image base. From the image base of a DLL, it would be able to walk the export table, locating function pointers for it’s own import table. Let’s take a look at how this code works.

In all recent versions of the Windows OS (at least to my knowledge), the FS register points to the data structure known as the thread environment block, or TEB. This structure is defined in NTDLL and you are able to view it’s members by utilizing public debug symbols from Microsoft (Debug Symbols).  This structure is also defined at NTAPI Undocumented Functions and WikiPedia.

> dt _TEB 
 +0x000 NtTib : _NT_TIB
 +0x01c EnvironmentPointer : Ptr32 Void
 +0x020 ClientId : _CLIENT_ID
 +0x028 ActiveRpcHandle : Ptr32 Void
 +0x02c ThreadLocalStoragePointer : Ptr32 Void
 +0x030 ProcessEnvironmentBlock : Ptr32 _PEB 

From our suspicious instruction, we can see that we’re referencing an offset of 0x30. This offset contains a pointer to the process environment block, or PEB. This data structure is meant for use by the operating system and therefore not documented well. However, you can find an entry for this structure on MSDN. In addition, you can view structure information using the same NTDLL debug symbols and WinDbg. From a WinDbg command prompt:

> dt _PEB
 +0x000 InheritedAddressSpace : UChar
 +0x001 ReadImageFileExecOptions : UChar
 +0x002 BeingDebugged : UChar
 +0x003 BitField : UChar
 +0x003 ImageUsesLargePages : Pos 0, 1 Bit
 +0x003 IsProtectedProcess : Pos 1, 1 Bit
 +0x003 IsLegacyProcess : Pos 2, 1 Bit
 +0x003 IsImageDynamicallyRelocated : Pos 3, 1 Bit
 +0x003 SkipPatchingUser32Forwarders : Pos 4, 1 Bit
 +0x003 SpareBits : Pos 5, 3 Bits
 +0x004 Mutant : Ptr32 Void
 +0x008 ImageBaseAddress : Ptr32 Void
 +0x00c Ldr : Ptr32 _PEB_LDR_DATA

So fs:[0x30] gives us the PEB, the next instruction we want to focus on is:

mov ebx, [ ebx + 0xC ]

We now need to look at the member that is at an offset of 0xC from the base of the PEB structure, which is the PEBLDR_DATAreference the structure of the PEB that we used WinDbg to examine, in the above output this member is in bold. Using WinDbg we can also examine the PEBLDR_DATA structure:

 +0x000 Length : Uint4B
 +0x004 Initialized : UChar
 +0x008 SsHandle : Ptr32 Void
 +0x00c InLoadOrderModuleList : _LIST_ENTRY
 +0x014 InMemoryOrderModuleList : _LIST_ENTRY
 +0x01c InInitializationOrderModuleList : _LIST_ENTRY
 +0x024 EntryInProgress : Ptr32 Void
 +0x028 ShutdownInProgress : UChar
 +0x02c ShutdownThreadId : Ptr32 Void

It’s worth mentioning, if you provide a virtual address as a second argument to the dt command, it will map the bytes at that location to the structure. The following is an example of this usage with the PEBLDR_DATA structure:

> dt _PEB_LDR_DATA 77df0200
 +0x000 Length : 0x30
 +0x004 Initialized : 0x1 ''
 +0x008 SsHandle : (null) 
 +0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x3e2ed8 - 0x3e3f48 ]
 +0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x3e2ee0 - 0x3e3f50 ]
 +0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x3e2f78 - 0x3e3f58 ]
 +0x024 EntryInProgress : (null) 
 +0x028 ShutdownInProgress : 0 ''
 +0x02c ShutdownThreadId : (null)

You should now realize that we’re in a process of walking different structures and their members in order to locate the data that we’re after – in this case the string data that represents the DLL name. After the instruction to locate the PEBLDR_DATA structure, is a mov using the base of that structure and an offset of 0x14, this is a LIST_ENTRY item with a member name of InMemoryOrderModuleList. Microsoft defines this member as:

The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure.

While this does point to the head of a doubly-linked list of LIST_ENTRY items, it doesn’t immediately take you to the LDR_DATA_TABLE_ENTRY structure for that module. Let’s first examine a LIST_ENTRY item, as defined on MSDN.

typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;
   struct _LIST_ENTRY *Blink;

Assuming a 32-bit environment, these values are pointers to other LIST_ENTRY structures, this makes the size of this structure 8 bytes (2 * 4 byte pointers). When we follow this pointer, it’s taking us to LIST_ENTRY structure InMemoryOrderLinks of the LDRDATA_TABLE_ENTRY (see bold below):

 +0x000 InLoadOrderLinks : _LIST_ENTRY
 +0x008 InMemoryOrderLinks : _LIST_ENTRY
 +0x010 InInitializationOrderLinks : _LIST_ENTRY
 +0x018 DllBase : Ptr32 Void
 +0x01c EntryPoint : Ptr32 Void
 +0x020 SizeOfImage : Uint4B
 +0x024 FullDllName : _UNICODE_STRING
 +0x02c BaseDllName : _UNICODE_STRING
 +0x034 Flags : Uint4B
 +0x038 LoadCount : Uint2B
 +0x03a TlsIndex : Uint2B
 +0x03c HashLinks : _LIST_ENTRY
 +0x03c SectionPointer : Ptr32 Void
 +0x040 CheckSum : Uint4B
 +0x044 TimeDateStamp : Uint4B
 +0x044 LoadedImports : Ptr32 Void
 +0x048 EntryPointActivationContext : Ptr32 _ACTIVATION_CONTEXT
 +0x04c PatchInformation : Ptr32 Void
 +0x050 ForwarderLinks : _LIST_ENTRY
 +0x058 ServiceTagLinks : _LIST_ENTRY
 +0x060 StaticLinks : _LIST_ENTRY
 +0x068 ContextInformation : Ptr32 Void
 +0x06c OriginalBase : Uint4B
 +0x070 LoadTime : _LARGE_INTEGER

This is important, for when you want to access other members of this structure, you have to keep in mind that you are at the base of the LIST_ENTRY InMemoryOrderLinks, and not the base of the structure itself. To access other members, you have to adjust your offsets accordingly. If we go back to our original code, our next instruction is:

mov esi, [ ebx + 0x28 ]

If we adjust for the 8 bytes that we’re already offset, this would land us between BaseDLLName and Flags (0x28 + 0x8 = 0x30)

 +0x000 InLoadOrderLinks : _LIST_ENTRY
 +0x008 InMemoryOrderLinks : _LIST_ENTRY
 +0x010 InInitializationOrderLinks : _LIST_ENTRY
 +0x018 DllBase : Ptr32 Void
 +0x01c EntryPoint : Ptr32 Void
 +0x020 SizeOfImage : Uint4B
 +0x024 FullDllName : _UNICODE_STRING
 +0x02c BaseDllName : _UNICODE_STRING
 +0x030 ....?
 +0x034 Flags : Uint4B

So what are we missing? Notice that BaseDllName is of type UNICODESTRING, this will require a little more investigation. According to Microsoft, this isn’t simply a pointer to a Unicode string but rather a UNICODE_STRING structure (MSDN), which is defined as:

typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;

Notice how this structure begins with two short values – Length and MaximumLength. To access the pointer to the string, we need to adjust from the base of this structure 4 bytes (2 shorts/2 bytes * 2).  With this is mind, it should now be clear how the code is obtaining the Unicode string for the DLL name.

 +0x008 InMemoryOrderLinks : _LIST_ENTRY
 +0x010 InInitializationOrderLinks : _LIST_ENTRY
 +0x018 DllBase : Ptr32 Void
 +0x01c EntryPoint : Ptr32 Void
 +0x020 SizeOfImage : Uint4B
 +0x024 FullDllName : _UNICODE_STRING
 +0x02c BaseDllName : _UNICODE_STRING
    +0x02c Length : USHORT
    +0x02e MaximumLength : USHORT
    +0x030 Buffer : PWSTR

And that’s it, you now have the BaseDllName, or just the name of the DLL versus the entire path. I’ve included a short video walking you through these instructions with a sample program.



Want to know when my latest content drops? Sign-up to receive email notications and access to other exclusive content!

bottom of page