News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

LDR_DATA_TABLE_ENTRY Structure

Started by Zen, August 06, 2015, 08:03:40 AM

Previous topic - Next topic

Zen

AARRGGHH !!!
I can't believe how INCREDIBILY ANNOYING this problem is,...
OK,...what am I talking about ???
Here's the background concept: I'm writing a COM application (which, as DAVE !!! says, is hard). It's going OK,...with the usual maddening problems along the way. But, my current snafu is this: I'm instantiating a COM Class by using a Class Moniker. Everything is normal, and works as expected. What I'm trying to accomplish is to read the opcodes of the function: QueryInterface. This is WAY beyond my mental abilities,...but, in assembly programming,...anything is possible,...right ???
I noticed, when printing out the addresses of the pointers contained in the COM Class Virtual Function Table (vtbl), that in some Inproc Servers, those pointer values were outside the COM InprocServer DLL listed under the InprocServer32 SubKey, under the COM Class's CLSID Registry Key.
...So,...what,...you're thinking,...:dazzled:
Well, because I want to know everything there is to know about COM, I was determined to find out where exactly those IUnknown pointers were pointing to (the implementations of QueryInterface, AddRef, and Release). I assumed that there was some dependent DLL that contained these routines,...
...And, so,...my brilliant plan was to access the Process Environment Block, and iterate through the processes' loaded modules by walking through what Windows Internals calls the Loaded Module Database. (Yes, I could have simply called EnumProcessModules, but that would have been TOO easy.)

Quote from: WINDOWS INTERNALSThe loader maintains a list of all modules (DLLs as well as the primary executable) that have been loaded by a process. This information is stored in a per-process structure called the Process Environment Block, or PEB) -- namely, in a substructure identified by Ldr and called PEB_LDR_DATA. In the structure, the loader maintains three doublylinked lists, all containing the same information but ordered differently (either by load order, memory, location, or initialization order).

The problem is this: The LDR_DATA_TABLE_ENTRY structure is one of those structures that Microsoft doesn't really want you to know about, so they have provided very minimal documentation: PEB_LDR_DATA structure (scan down the page about half way).   

In searching the Internet, I have come across several different structure definitions. Here is the one from TOUTENMASM (Winternals.h):
LDR_DATA_TABLE_ENTRY STRUCT DEFALIGNMASM
Reserved1 DWORD 2 dup (?)
InMemoryOrderLinks LIST_ENTRY <>
Reserved2 DWORD 2 dup (?)
DllBase DWORD ?
Reserved3 DWORD 2 dup (?)
FullDllName UNICODE_STRING <>
Reserved4 BYTE 8 dup (?)
Reserved5 DWORD 3 dup (?)
union DUMMYUNIONNAME
CheckSum DWORD ?
Reserved6 DWORD ?
ENDS
TimeDateStamp DWORD ?
LDR_DATA_TABLE_ENTRY ENDS

Zen

Zen

#1
When I was researching this subject, I found a couple blogs that stated that the LDR_DATA_TABLE_ENTRY Structure they were able to view in WinDbg was different from the structure that MSDN documentation lists.
And, then I found this cool French blog: Ivanlef0u's Blog, LdrpHashTable

The author defines the  LDR_DATA_TABLE_ENTRY Structure like this (in C++, but, much different from the MSDN version):   
//
// Loader Data Table Entry
//
typedef struct _LDR_DATA_TABLE_ENTRY
{
    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderModuleList;
    LIST_ENTRY InInitializationOrderModuleList;
    PVOID DllBase;
    PVOID EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    ULONG Flags;
    USHORT LoadCount;
    USHORT TlsIndex;
    union
    {
        LIST_ENTRY HashLinks;
        PVOID SectionPointer;
    };
    ULONG CheckSum;
    union
    {
        ULONG TimeDateStamp;
        PVOID LoadedImports;
    };
    PVOID EntryPointActivationContext;
    PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY * PLDR_DATA_TABLE_ENTRY;


...Another excellent Forum entry that describes the entire scenario that I'm trying to understand is here:
PEB, LDR_DATA_TABLE_ENTRY, SysInternals Forum, Oct 2009

:bgrin:...Anyway,...at this point, I've no doubt lost you all,...:bgrin:

But,...I really did have a question: Have any of you ever traversed the Loaded Module Database, by using just pointers to the TEB, and then the PEB, and then, the InMemoryOrderModuleList (which is a LIST_ENTRY that points to a LDR_DATA_TABLE_ENTRY structure) ??? I don't absolutely have to know the REAL SIZE of the LDR_DATA_TABLE_ENTRY Structure because it contains LIST_ENTRY Structures that are pointers to the next LDR_DATA_TABLE_ENTRY Structure in the Loaded Modules Database, and then I can use offsets to get the data (mainly the UNICODE_STRING containing the complete path and name of the DLL module).

...But,...I'm curious,...you all are knowledgeable and reasonable people,...just how INSANE does this whole scenario sound to you ???
Zen

mabdelouahab



LDR_DATA_TABLE_ENTRY struc ; (sizeof=0X54, standard type)
    InLoadOrderModuleList           LIST_ENTRY     <>
    InMemoryOrderModuleList         LIST_ENTRY     <>
    InInitializationOrderModuleList LIST_ENTRY     <>
    DllBase                         DWORD          ?         ; offset (FFFFFFFF)
    EntryPoint                      DWORD          ?         ; offset (FFFFFFFF)
    SizeOfImage                     DWORD          ?
    FullDllName                     UNICODE_STRING <>
    BaseDllName                     UNICODE_STRING <>
    Flags                           DWORD          ?
    LoadCount                       WORD           ?
    TlsIndex                        WORD           ?
    U0                              LDR_DATA_TABLE_ENTRY_U0 <>
    CheckSum                        DWORD          ?
    U1                              LDR_DATA_TABLE_ENTRY_U1 <>
    EntryPointActivationContext     DWORD          ?    ; offset (FFFFFFFF)
    PatchInformation                DWORD          ?    ; offset (FFFFFFFF)
LDR_DATA_TABLE_ENTRY ends

https://code.google.com/p/downalbum/source/browse/trunk/src/%E6%89%93%E9%80%A0API/FILTINPT.Inc?r=725

TouEnMasm

Quote:bgrin:...Anyway,...at this point, I've no doubt lost you all,...:bgrin:

All this are Windows Internal,this mean that you have not to work with.
API given by each windows versions knows how to use that,it's enough.
search well,and you wil find a very few cases using those defines.

Quote
;/************************************************************************
;*                                                                       *
;*   winternl.h -- This module defines the internal NT APIs and data     *
;*       structures that are intended for the use only by internal core  *
;*       Windows components.  These APIs and data structures may change  *
;*       at any time.                                                    *
;*                                                                       *
;*   These APIs and data structures are subject to changes from one      *
;*       Windows release to another Windows release.  To maintain the    *
;*       compatiblity of your application, avoid using these APIs and    *
;*       data structures.                                                *
;*                                                                       *
;*   The appropriate mechanism for accessing the functions defined in    *
;*       this header is to use LoadLibrary() for ntdll.dll and           *
;*       GetProcAddress() for the particular function.  By using this    *
;*       approach, your application will be more resilient to changes    *
;*       for these functions between Windows releases.  If a function    *
;*       prototype does change, then GetProcAddress() for that function  *
;*       might detect the change and fail the function call, which your  *
;*       application will be able to detect.  GetProcAddress() may not   *
;*       be able to detect all signature changes, thus avoid using these *
;*       internal functions.  Instead, your application should use the   *
;*       appropriate Win32 function that provides equivalent or similiar *
;*       functionality.                                                  *
;*                                                                       *
;*   Copyright (c) Microsoft Corp. All rights reserved.                  *
;*                                                                       *
;************************************************************************/

Fa is a musical note to play with CL

sinsi

Here's one with extra bits at the end
http://www.nirsoft.net/kernel_struct/vista/LDR_DATA_TABLE_ENTRY.html

Zen

MABDELOUAHAB, TOUTENMASM, AND SINSI,   
All excellent responses, thank you,...:icon_eek: 

MABDELOUAHAB,...excellent link, thanks,...I hadn't found that one (these definitions are probably what I will end up using).   
TOUTENMASM,...You're right, of course,...but, that's what I want to find out: what structure definitions work with what versions of Windows. Hell, the code will probably be a disaster, but, I'll learn from it,...
...And, SINSI,...another excellent link which I hadn't found,...very similar to MABDELOUAHAB's link,...
Zen

adeyblue

Just use the XP definition (the one in the second post). The Vista/7 are just extensions of that. Same probably goes for 8 & 10.

One thing to bear in mind is that the head of these lists are in the PEB_LDR_DATA which is in ntdll's data section. When you come to that link in the list don't try to read the size or BaseAddress or DllName since it isn't an actual LDR_DATA_TABLE_ENTRY. Also the Initialization Order links aren't filled in on 7 and they're always null.

Zen

#7
Hi, ADEYBLUE,   
Thanks for the advice. My thinking is that I would use the Flink pointer from that first LIST_ENTRY, which should point to the first LDR_DATA_TABLE_ENTRY in the InMemoryOrderModuleList, because the whole point of doing this is to get the BaseDLLName (or, FullDllName) and DllBase (the address, which is really an HMODULE).
Also, to make it more difficult, I have multiple definitions of the PEB_LDR_DATA, so I am somewhat confused about what the actual offset is.  :dazzled:
...I'll probably write a completely radioactive code block, pointing to something unknown in Chinese,...Ha, Ha, Ha,...:icon_eek:
When I finally get it to work, I'll post some code. By the way, I'm running Windows Seven,...

I used the following definition for the PEB_LDR_DATA Structure, which is a variant of several that I found online (but, based on the one that MABDELOUAHAB posted). The key thing is the offset to the List that you want to iterate (in this case, the InMemoryOrderModuleList). I used the offset,...
PEB_LDR_DATA STRUCT    ;    sizeof = 24h   
BYTE 12 DUP (0)
_Length DWORD 0    ;   
Initialized BYTE 0    ;   
BYTE 3 DUP(0)    ;    Unknown as to what this really is (padding ???).   
SsHandle PVOID 0    ;   
InLoadOrderModuleList LIST_ENTRY <>    ;    0Ch
InMemoryOrderModuleList LIST_ENTRY <>    ;    14h
InInitializationOrderModuleList LIST_ENTRY <>    ;    1Ch
PEB_LDR_DATA ENDS   
Zen

Zen

#8
:bgrin: OK,...I finally got it to work,...sort of,...:bgrin: And, it's a surprisingly simple process. I iterated the InMemoryOrderModuleList.
My concentration is completely gone,... :icon_eek: I crashed my app numerous times by 'de-referencing' pointers that weren't actually pointers. :bgrin:

ScanPEBModule PROC AddrQryIntr:DWORD   

;     Recall that the TEB begins with some pointers which bound the stack, and the seventh pointer is a self-pointer. What's even more     
;     useful is the thirteenth pointer (offset 0x30 for 32-bit TEBs, offset 0x60 for 64-bit TEBs), because that is where the PEB is stored.   
;     The loader maintains a list of all modules (DLLs as well as the primary executable) that have been loaded by a process. This
;     information is stored in a per-process structure called the Process Environment Block, or PEB) -- namely, in a substructure
;     identified by Ldr and called PEB_LDR_DATA. In the structure, the loader maintains three doublylinked lists, all containing the same
;     information but ordered differently (either by load order, memory, location, or initialization order). These lists contain structures
;     called Loader Data Table Entries (LDR_DATA_TABLE_ENTRY) that store information about each module.   

      mov ecx, 0        ;   
      mov ptrPrcssEnvrnBlk, ecx    ;    Pointer to the Process Environment Block.   
      mov MemOrdModList, ecx    ;    Pointer to LIST_ENTRY structure that serves as the list header for the InMemoryOrderModuleList (???).   
      mov dwBuffrDllName, ecx    ;    Pointer to UNICODE_STRING.Buffer that holds unicode string to Base Dll Name.   
      mov dwNumLoadModls, ecx    ;    This is the count of Loaded DLL Modules in the InMemoryOrderModuleList.   
      mov dwCurLstEntry, ecx    ;    This is the address of the LIST_ENTRY structure that is currently being iterated.   
      mov dwLenWDllName, ecx    ;    Return value from lstrlenW, length of unicode string that contains BaseDllName.   

      ASSUME fs:nothing
      mov ecx, fs:[030h]    ;    Get a pointer to the Process Environment Block (PEB)   
      ASSUME fs:error   
      mov ptrPrcssEnvrnBlk, ecx    ;    Save the pointer to the Process Environment Block (PEB).   
      mov ebx, [ecx + 0Ch]    ;    Pointer to a PEB_LDR_DATA structure providing information about loaded modules .     

;     The PEB has three lists: InLoadOrderModuleList, InMemoryOrderModuleList, and InInitializationOrderModuleList.
;     The LIST_ENTRYs for each list are pointers to LDR_DATA_TABLE_ENTRY structure.     

      mov edx, [ebx + 14h]    ;    This is the LIST_ENTRY for the InMemoryOrderModuleList (LDR_DATA_TABLE_ENTRY STRUCT, List header)   
      mov MemOrdModList, edx    ;    Save the address of the list header for the InMemoryOrderModuleList to determine end of list (???).   

      mov ebx, [edx]    ;    The Flink points to the next entry (LIST_ENTRY) in the InMemoryOrderModuleList (This is the first DLL Module).   
      mov dwCurLstEntry, ebx    ;    Save the address of the InMemoryOrderModuleList LIST_ENTRY structure for the next DLL iteration.   

ModuleLoop:   

      mov ecx, [ebx - 8]    ;    We want to get to the beginning of the LDR_DATA_TABLE_ENTRY STRUCT, so subtract 8 bytes (sizeof LIST_ENTRY)

      ASSUME ecx:PTR LDR_DATA_TABLE_ENTRY
      mov edx, [ecx].LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer    ;    Pointer to a wide-character string.   
      ASSUME ecx:nothing   
      mov dwBuffrDllName, edx   

;     The following code block should detect the address value at the end of the list that points to the list head.   
;     But, it doesn't work. Reason unknown, but, I'd guess that my concept of the List Head differs from that of Microsoft.   

      mov edi, dwCurLstEntry    ;    This should be a LIST_ENTRY, Flink (pointer to next entry in list).   
      mov edx, [edi]    ;   
      .IF edx==MemOrdModList    ;   
      invoke LogEntry, ADDR szEndMemOrdMods
      RET
      .ENDIF   

      invoke lstrlenW, dwBuffrDllName
      mov dwLenWDllName, eax   
      .IF dwLenWDllName==0    ;         
      invoke LogEntry, ADDR szWLenLstrlen
      RET
      .ENDIF

      INC dwLenWDllName    ;    Include NULL Character.   
      invoke RtlZeroMemory, ADDR ConvrtDllName, 256
      invoke WideCharToMultiByte, CP_ACP, 0, dwBuffrDllName, dwLenWDllName, ADDR ConvrtDllName, 256, NULL, NULL
     
      invoke RtlZeroMemory, ADDR RprtDllName, 256
      invoke lstrcpy, ADDR RprtDllName, ADDR szDllNameLdMod
      invoke lstrcat, ADDR RprtDllName, ADDR ConvrtDllName   
      invoke lstrcat, ADDR RprtDllName, ADDR szEndLine
      invoke LogEntry, ADDR RprtDllName

      INC dwNumLoadModls

      mov edi, dwCurLstEntry    ;    This should be a LIST_ENTRY, Flink (pointer to next entry in list).   
      mov edx, [edi]    ;   
      mov dwCurLstEntry, edx
;     For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next 
;     entry in the list or to the list header if there is no next entry in the list.     
      .IF edx==MemOrdModList    ;   
      RET
      .ENDIF   
     
      mov ebx, edx
      JMP ModuleLoop

      RET   

ScanPEBModule ENDP   


LogEntry is a routine that I have written that just writes information to a text file (Log File) to check the correctness of the function returns. 
This is the list that it wrote to my Log File:
The BaseDllName from the InMemoryOrderModuleList is: kernel32.dll
The BaseDllName from the InMemoryOrderModuleList is: KERNELBASE.dll
The BaseDllName from the InMemoryOrderModuleList is: user32.dll
The BaseDllName from the InMemoryOrderModuleList is: GDI32.dll
The BaseDllName from the InMemoryOrderModuleList is: LPK.dll
The BaseDllName from the InMemoryOrderModuleList is: USP10.dll
The BaseDllName from the InMemoryOrderModuleList is: msvcrt.dll
The BaseDllName from the InMemoryOrderModuleList is: ADVAPI32.dll
The BaseDllName from the InMemoryOrderModuleList is: sechost.dll
The BaseDllName from the InMemoryOrderModuleList is: RPCRT4.dll
The BaseDllName from the InMemoryOrderModuleList is: SspiCli.dll
The BaseDllName from the InMemoryOrderModuleList is: CRYPTBASE.dll
The BaseDllName from the InMemoryOrderModuleList is: comctl32.dll
The BaseDllName from the InMemoryOrderModuleList is: ole32.dll
The BaseDllName from the InMemoryOrderModuleList is: psapi.dll
The BaseDllName from the InMemoryOrderModuleList is: IMM32.DLL
The BaseDllName from the InMemoryOrderModuleList is: MSCTF.dll
The BaseDllName from the InMemoryOrderModuleList is: uxtheme.dll
The BaseDllName from the InMemoryOrderModuleList is: dwmapi.dll
The BaseDllName from the InMemoryOrderModuleList is: RICHED32.DLL
The BaseDllName from the InMemoryOrderModuleList is: RICHED20.dll
The BaseDllName from the InMemoryOrderModuleList is: CRYPTSP.dll
The BaseDllName from the InMemoryOrderModuleList is: rsaenh.dll
The BaseDllName from the InMemoryOrderModuleList is: RpcRtRemote.dll
The BaseDllName from the InMemoryOrderModuleList is: urlmon.dll
The BaseDllName from the InMemoryOrderModuleList is: api-ms-win-downlevel-ole32-l1-1-0.dll
The BaseDllName from the InMemoryOrderModuleList is: api-ms-win-downlevel-shlwapi-l1-1-0.dll
The BaseDllName from the InMemoryOrderModuleList is: shlwapi.DLL
The BaseDllName from the InMemoryOrderModuleList is: api-ms-win-downlevel-advapi32-l1-1-0.dll
The BaseDllName from the InMemoryOrderModuleList is: api-ms-win-downlevel-user32-l1-1-0.dll
The BaseDllName from the InMemoryOrderModuleList is: api-ms-win-downlevel-version-l1-1-0.dll
The BaseDllName from the InMemoryOrderModuleList is: version.DLL
The BaseDllName from the InMemoryOrderModuleList is: api-ms-win-downlevel-normaliz-l1-1-0.dll
The BaseDllName from the InMemoryOrderModuleList is: normaliz.DLL
The BaseDllName from the InMemoryOrderModuleList is: iertutil.dll
The BaseDllName from the InMemoryOrderModuleList is: WININET.dll
The BaseDllName from the InMemoryOrderModuleList is: USERENV.dll
The BaseDllName from the InMemoryOrderModuleList is: profapi.dll
The BaseDllName from the InMemoryOrderModuleList is: CLBCatQ.DLL
The BaseDllName from the InMemoryOrderModuleList is: OLEAUT32.dll
The BaseDllName from the InMemoryOrderModuleList is: wmpdxm.dll
The BaseDllName from the InMemoryOrderModuleList is: MPR.dll
The BaseDllName from the InMemoryOrderModuleList is: MF.dll
The BaseDllName from the InMemoryOrderModuleList is: ATL.DLL
The BaseDllName from the InMemoryOrderModuleList is: MFPlat.DLL
The BaseDllName from the InMemoryOrderModuleList is: WS2_32.dll
The BaseDllName from the InMemoryOrderModuleList is: NSI.dll
The BaseDllName from the InMemoryOrderModuleList is: AVRT.dll
The BaseDllName from the InMemoryOrderModuleList is: ksuser.dll 


So,...this seems to work OK,...except that I'm still unsure what that last pointer value is. The documentation for the LIST_ENTRY structure states that: "For a LIST_ENTRY structure that serves as a list entry, the Flink member points to the next entry in the list or to the list header if there is no next entry in the list." So what is the List Header address ? I got that one wrong, but, the lstrlenW function terminated the Module iteration loop.

...As a check, I invoked EnumProcessModules,...and, counted the number of HMODULES in the returned array,...and, it returns two more HMODULEs than I have come up with in my Loaded Module Database code (above),...:icon_eek:...so, there are unresolved errors here,...(I'm guessing that I missed the first and the last LDR_DATA_TABLE_ENTRYs)...
...Also,...when I ran the app in ProcessExplorer, it's DLL list has about ten DLLs that I didn't get in my list,...:dazzled:

I THINK COM HAS DESTROYED MY MIND !!!
...Don't let this happen to you !!!
Zen

Zen

Finally,...I have almost got the code to work perfectly. I can find the InMemoryOrderModuleList List head correctly, and iterate through the list of loaded modules,...and, I'm only missing one DLL,...
Is anybody interested in the code ??? (If so,...I'll post.) I'm guessing not,...as it is a somewhat unrelialable technique. I have combined the iteration of the LDR_DATA_TABLE_ENTRY structures with the much easier and almost foolproof, EnumProcessModules. I still cannot find one loaded DLL by iterating the InMemoryOrderModuleList,...can't imagine what the problem is. It's kind of like wandering through a completely dark room,...assuming that it is rectangular, trying not to crash into any furniture...and, hoping there are no bear traps,...:dazzled:
...I feel like maybe I should check into rehab for the rest of the summer,...:icon_eek:
Zen

TouEnMasm


Have you try to disassemble the EnumProcessModules api with windbg ?
windbg give many symbols and a clear code.
Fa is a musical note to play with CL

Zen

Hi, TOUTENMASM,   
Quote from: TOUTENMASMHave you try to disassemble the EnumProcessModules api with windbg ?

...No,...I haven't,...but, I figured out what the problem is: iterating through the PEB's InMemoryOrderModuleList List reads the LDR_DATA_TABLE_ENTRY Structure for each loaded module in the process,...whereas,...iterating through the loaded modules with EnumProcessModules reads all the HMODULEs of the loaded modules (mostly DLLs),...plus the HMODULE of the executable itself. So,...the count is off by one when you compare the two methods. EnumProcessModules will always produce a module count of one more than iterating through the PEBs' InMemoryOrderModuleList.       
I have assembled the source code into a simple windows program, that prints out all the data to a text file. It finally works perfectly. :bgrin:
I wrote the code and tested it on Windows Seven Professional,...and, I would be interested to know if the code iterates through the InMemoryOrderModuleList without crashing, on other Windows operating system versions,...:dazzled:
Zen