(1)首先通过段选择器FS在内存中找到当前的线程环境块TEB。
(2)线程环境块中找到进程环境块PEB的指针。
(3)进程环境块中找到指向PEB_LDR_DATA结构体的指针,其中存放着已经被进程装载的动态链接库的信息。
(4)PEB_LDR_DATA结构体中找到指向模块信息LIST_ENTRY链表的头指针。
(5)遍历_LIST_ENTRY链表,找到想要获取DLL的基地址。
1.TEB
内存中FS:0是当前的线程环境块TEB的基地址。用WinDbg验证了一下:
通过WinDbg查看TEB数据结构:
TEB偏移0x30处存放的是进程环境块PEB的指针。
2.PEB
通过WinDbg查看PEB数据结构:
PEB偏移0x0c处存放的是PEB_LDR_DATA结构体的指针。
3.PEB_LDR_DATA
通过WinDbg查看PEB_LDR_DATA数据结构:
发现有三个模块链表,按变量名字来看应该是按初始化顺序的模块链表、按装载顺序的模块链表和按在内存中顺序的模块链表。
_LIST_ENTRY结构如下,是一个双向链表:
链表实际指向的内容是LDR_DATA_TABLE_ENTRY结构体:
LDR_DATA_TABLE_ENTRY偏移0x18就是DLL基地址。同时还看到FullDllName和BaseDllName可以帮助我们区分是哪个DLL。
代码遍历模块链表
模块链表是一个双向循环链表,以下以遍历InLoadOrderModuleList举例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
typedef struct _LIST_ENTRY
{
_LIST_ENTRY* Flink;
_LIST_ENTRY* Blink;
}_LIST_ENTRY;
typedef struct _UNICODE_STRING
{
unsigned short Length;
unsigned short MaximumLength;
wchar_t* Buffer;
};
int main()
{
int* pPEB = NULL;
__asm
{
push eax
mov eax,fs:[0x30]
mov pPEB,eax
pop eax
}
int* pIDR = (int*)(*(pPEB + 0x03)); //0x03 * 4 = 0x0c
_LIST_ENTRY* pInLoadOrderModuleList = (_LIST_ENTRY*)(pIDR + 0x03); //0x03 * 4 = 0x0c
_LIST_ENTRY* pHead, *p;
p = pHead = pInLoadOrderModuleList->Flink;
int count = 0;
do
{
printf("%d: ", count++);
_UNICODE_STRING* pBaseName = (_UNICODE_STRING*)(((int)p) + 0x2c);
if (pBaseName->Buffer)
wprintf(L"%s ", pBaseName->Buffer);
printf("0x%x\n", *((int*)(((int)p) + 0x18)));
//
p = p->Flink;
} while (p != pHead);
return 0;
}
代码的输出如下:
0: ASM.exe 0x1230000
1: ntdll.dll 0x77c10000
2: KERNEL32.DLL 0x749a0000
3: KERNELBASE.dll 0x746d0000
4: VCRUNTIME140D.dll 0x5f580000
5: ucrtbased.dll 0xf840000
6: 0x0
对照VS里的 调试-窗口-模块 里的内容,读取正确: