获取DLL的基地址

时间:2024-04-08 07:44:00

(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验证了一下:
获取DLL的基地址
    通过WinDbg查看TEB数据结构:
获取DLL的基地址
    TEB偏移0x30处存放的是进程环境块PEB的指针。

2.PEB

    通过WinDbg查看PEB数据结构:
获取DLL的基地址
    PEB偏移0x0c处存放的是PEB_LDR_DATA结构体的指针。

3.PEB_LDR_DATA

    通过WinDbg查看PEB_LDR_DATA数据结构:
获取DLL的基地址

    发现有三个模块链表,按变量名字来看应该是按初始化顺序的模块链表、按装载顺序的模块链表和按在内存中顺序的模块链表。
    _LIST_ENTRY结构如下,是一个双向链表:
获取DLL的基地址
    链表实际指向的内容是LDR_DATA_TABLE_ENTRY结构体:
获取DLL的基地址
    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里的 调试-窗口-模块 里的内容,读取正确:
获取DLL的基地址