一、几个重要的数据结构,可以通过windbg的dt命令查看其详细信息
_PEB、_PEB_LDR_DATA、_LDR_DATA_TABLE_ENTRY
二、技术原理
1、通过fs:[30h]获取当前进程的_PEB结构
2、通过_PEB的Ldr成员获取_PEB_LDR_DATA结构
3、通过_PEB_LDR_DATA的InMemoryOrderModuleList成员获取_LIST_ENTRY结构
4、通过_LIST_ENTRY的Flink成员获取_LDR_DATA_TABLE_ENTRY结构,注意:这里的Flink指向的是_LDR_DATA_TABLE_ENTRY结构中的InMemoryOrderLinks成员,因此需要计算真正的_LDR_DATA_TABLE_ENTRY起始地址,我们可以用CONTAINING_RECORD来计算。
5、输出_LDR_DATA_TABLE_ENTRY的BaseDllName或FullDllName成员信息。
三、代码实现(基于XP sp2 系统)
- //EnumInLoadModule.c
- //compile:cl EnumInLoadModule.c
- #include <windows.h>
- #define CONTAINING_RECORD(address, type, field) ((type *)( /
- (PCHAR)(address) - /
- (ULONG_PTR)(&((type *)0)->field)))
- typedef struct _UNICODE_STRING {
- USHORT Length;
- USHORT MaximumLength;
- PWSTR Buffer;
- } UNICODE_STRING, *PUNICODE_STRING;
- typedef struct _PEB_LDR_DATA
- {
- DWORD Length;
- UCHAR Initialized;
- PVOID SsHandle;
- LIST_ENTRY InLoadOrderModuleList;
- LIST_ENTRY InMemoryOrderModuleList;
- LIST_ENTRY InInitializationOrderModuleList;
- PVOID EntryInProgress;
- }PEB_LDR_DATA,*PPEB_LDR_DATA;
- typedef struct _LDR_DATA_TABLE_ENTRY
- {
- LIST_ENTRY InLoadOrderLinks;
- LIST_ENTRY InMemoryOrderLinks;
- LIST_ENTRY InInitializationOrderLinks;
- PVOID DllBase;
- PVOID EntryPoint;
- DWORD SizeOfImage;
- UNICODE_STRING FullDllName;
- UNICODE_STRING BaseDllName;
- DWORD Flags;
- WORD LoadCount;
- WORD TlsIndex;
- LIST_ENTRY HashLinks;
- PVOID SectionPointer;
- DWORD CheckSum;
- DWORD TimeDateStamp;
- PVOID LoadedImports;
- PVOID EntryPointActivationContext;
- PVOID PatchInformation;
- }LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY;
- typedef struct _PEB
- {
- UCHAR InheritedAddressSpace;
- UCHAR ReadImageFileExecOptions;
- UCHAR BeingDebugged;
- UCHAR SpareBool;
- PVOID Mutant;
- PVOID ImageBaseAddress;
- PPEB_LDR_DATA Ldr;
- }PEB,*PPEB;
- int main(void)
- {
- PLDR_DATA_TABLE_ENTRY pLdrDataEntry = NULL;
- PLIST_ENTRY pListEntryStart = NULL,pListEntryEnd = NULL;
- PPEB_LDR_DATA pPebLdrData = NULL;
- PPEB pPeb = NULL;
- //故意加载一些DLL,以便测试!
- LoadLibrary("ResLibDemo");
- __asm
- {
- //1、通过fs:[30h]获取当前进程的_PEB结构
- mov eax,dword ptr fs:[30h];
- mov pPeb,eax
- }
- //2、通过_PEB的Ldr成员获取_PEB_LDR_DATA结构
- pPebLdrData = pPeb->Ldr;
- //3、通过_PEB_LDR_DATA的InMemoryOrderModuleList成员获取_LIST_ENTRY结构
- pListEntryStart = pListEntryEnd = pPebLdrData->InMemoryOrderModuleList.Flink;
- //查找所有已载入到内存中的模块
- do
- {
- //4、通过_LIST_ENTRY的Flink成员获取_LDR_DATA_TABLE_ENTRY结构
- pLdrDataEntry = (PLDR_DATA_TABLE_ENTRY)CONTAINING_RECORD(pListEntryStart,LDR_DATA_TABLE_ENTRY,InMemoryOrderLinks);
- //5、输出_LDR_DATA_TABLE_ENTRY的BaseDllName或FullDllName成员信息
- printf("%S/n",pLdrDataEntry->BaseDllName.Buffer);
- pListEntryStart = pListEntryStart->Flink;
- }while(pListEntryStart != pListEntryEnd);
- }
- /*
- output:
- EnumInLoadModule.exe
- ntdll.dll
- kernel32.dll
- ResLibDemo.dll
- ...
- */
四、总结
枚举模块有多种方式,随着深入的学习,希望能总结越来越来的方法。在这里感谢sai 的文章:http://www.debugman.com/read.php?tid=4255,虽然他的文章还没细看,但他里面的_LDR_DATA_TABLE_ENTRY结构促发了我编写测试的想法并总结了本文。