PEB中的Ldr部分包含有当前进程所加载的模块信息.
lkd> dt _peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 SpareBool        : UChar
   +0x004 Mutant           : Ptr32 Void
   +0x008 ImageBaseAddress : Ptr32 Void
   +0x00c Ldr              : Ptr32 _PEB_LDR_DATA
.....
其结构定义如下:
typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList; //按加载顺序
LIST_ENTRY InMemoryOrderModuleList; //按内存顺序
LIST_ENTRY InInitializationOrderModuleList; //按初始化顺序
} PEB_LDR_DATA,*PPEB_LDR_DATA;

另一个重要结构就是存储每个模块信息的LDR_MODULE部分,其定义如下:
typedef struct _LDR_MODULE
{
LIST_ENTRY          InLoadOrderModuleList;
LIST_ENTRY          InMemoryOrderModuleList;
LIST_ENTRY          InInitializationOrderModuleList;
void*               BaseAddress;
void*               EntryPoint;  
ULONG               SizeOfImage;
UNICODE_STRING   FullDllName;
UNICODE_STRING      BaseDllName;
ULONG               Flags;
SHORT               LoadCount;
SHORT               TlsIndex;
HANDLE              SectionHandle;
ULONG               CheckSum;
ULONG               TimeDateStamp;
} LDR_MODULE, *PLDR_MODULE;

这两者的关系呢,引用别人的两张图来说明一下吧,看了图应该一目了然.



第二张图太大了,放个缩略图吧.
结构搞清楚之后,要遍历就很简单了,主要就是遍历一个双向环状链表.
PEB_LDR_DATA *pPEBLDR;
LDR_MODULE *pLdrMod;
LIST_ENTRY *pListEntry,*pStart;
void *p,*BaseAddress,*FullDllName;
_asm
{
   mov eax,fs:[0x30] //TEB->PEB
   mov eax,[eax+0xC] //PEB->Ldr
   mov pPEBLDR,eax
}
printf("PEB_LDR_DATA:0x%08x\n",pPEBLDR);
printf("LDR->Length:0x%08x\n",pPEBLDR->Length);
printf("LDR->InLoadOrderModuleList:\t\t0x%08x\n",pPEBLDR->InLoadOrderModuleList);
printf("LDR->InMemoryOrderModuleList:\t\t0x%08x\n",pPEBLDR->InMemoryOrderModuleList);
printf("LDR->InInitializationOrderModuleList:\t0x%08x\n",pPEBLDR->InInitializationOrderModuleList);
//遍历双链表
pListEntry=(LIST_ENTRY*)(PUCHAR)&(pPEBLDR->InLoadOrderModuleList);
pStart=pListEntry;
printf("BaseAddress\t\tFullDllName\n====================================\n");
do
{
   pListEntry=pListEntry->Flink;
   pLdrMod=(LDR_MODULE*)pListEntry;
   printf("0x%08x\t\t",pLdrMod->BaseAddress);
   wprintf(L"%s\n",pLdrMod->FullDllName.Buffer);
  
} while(pListEntry!=pStart);

 

继续研究PEB,这时候才发现之前写《从PEB获取内存中模块列表》一文时出现 的错误.
下面是一个结构:
typedef struct _LDR_MODULE
{
LIST_ENTRY          InLoadOrderModuleList;
LIST_ENTRY          InMemoryOrderModuleList;
LIST_ENTRY          InInitializationOrderModuleList;
void*               BaseAddress;
void*               EntryPoint;  
ULONG               SizeOfImage;
UNICODE_STRING   FullDllName;
UNICODE_STRING      BaseDllName;
ULONG               Flags;
SHORT               LoadCount;
SHORT               TlsIndex;
HANDLE              SectionHandle;
ULONG               CheckSum;
ULONG               TimeDateStamp;
} LDR_MODULE, *PLDR_MODULE;

从这个结构可以知道,如果按InLoadOrderModuleList遍历,那么InLoadOrderModuleList这个结构的地址就是 LDR_MODULE结构的地址,但是如果按InMemoryOrderModuleList这个链表来遍历时,得到的下一个链表地址必须减去前面 InLoadOrderModuleList这个结构所占的空间,得到的地址才是LDR_MODULE的地址,相应的,如果用第三个链表遍历的话,就要减 去两个LIST_ENTRY的大小了.《从PEB获取内存中模块列表》一文中的代码和结果已修正.

正确写法如下:
pListEntry=(LIST_ENTRY*)(PUCHAR)&(pPEBLDR->InInitializationOrderModuleList);  //如果使用这个链表
pLdrMod=(LDR_MODULE*)( (char*) pListEntry-sizeof(LIST_ENTRY)*2); //要减去两个LIST_ENTRY大小

注意红色部分,之前就是因为没有写这个,导致一直得不到正确结果,长点教训哦~
PS: 好像遍历这个链表得到的模块列表并不全面...

 


效果图: