使用 Windows API

静态链接 (Static Linking) :

  • 在编译时将 DLL 中的函数链接到可执行文件中。
  • 应用程序在运行时不需要额外加载 DLL。
  • 优点是加载和执行速度快,缺点是可执行文件体积较大。

动态链接 (Dynamic Linking) :

  • 在运行时动态加载 DLL,并调用 DLL 中的函数。
  • 应用程序需要使用 LoadLibrary​ 和 GetProcAddress​ 等 Windows API 函数来加载和访问 DLL 中的函数。
  • 优点是可执行文件体积较小,缺点是加载和执行速度稍慢,并且需要确保 DLL 文件在运行时可用。

隐式链接 (Implicit Linking) :

  • 在编译时使用 #include​ 或 #pragma comment​ 指令,将 DLL 中的函数声明添加到应用程序的源代码中。
  • 编译器会自动生成调用 DLL 函数的代码,并在链接时将其链接到可执行文件中。
  • 这种方式对开发人员来说更加简单和方便,但缺点是可执行文件体积较大。

显式链接 (Explicit Linking) :

  • 在运行时使用 LoadLibrary​ 和 GetProcAddress​ 等 Windows API 函数动态加载 DLL,并调用 DLL 中的函数。
  • 这种方式给开发人员更多的灵活性和控制权,但需要编写更多的代码。

常用win api函数(列举了两种)

LoadLibraryEx​函数

1
2
3
4
5
HMODULE LoadLibraryEx(
LPCTSTR lpLibFileName,
HANDLE hFile,
DWORD dwFlags
);

参数说明:

  1. lpLibFileName​: 要加载的 DLL 文件的完整路径。

  2. hFile​: 可选参数,用于指定加载 DLL 的文件句柄。通常设置为 NULL​。

  3. dwFlags​: 用于指定加载 DLL 的行为方式,可以是以下值的组合:

    • LOAD_LIBRARY_AS_DATAFILE​: 以数据文件的方式加载 DLL,不执行任何 DLL 初始化代码。
    • LOAD_LIBRARY_AS_IMAGE_RESOURCE​: 将 DLL 加载为映像资源,而不是作为可执行模块。
    • LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE​: 以独占方式加载 DLL 为数据文件。
    • LOAD_LIBRARY_REQUIRE_SIGNED_TARGET​: 要求目标 DLL 必须是经过数字签名的。
    • LOAD_LIBRARY_SEARCH_APPLICATION_DIR​: 在应用程序目录中搜索 DLL。
    • LOAD_LIBRARY_SEARCH_DEFAULT_DIRS​: 在默认的搜索目录中搜索 DLL。
    • 等等。

函数返回值:

  • 成功返回加载的 DLL 模块句柄(HMODULE)。
  • 失败返回 NULL​。可以通过 GetLastError()​ 函数获取错误代码。

进程调用 LoadLibraryLoadLibraryEx 以显式链接到 DLL。如果函数执行成功,它会将指定的 DLL 映射到调用进程的地址空间中并返回该 DLL 的句柄。 此句柄可以与其他函数(如 GetProcAddress​ 和 FreeLibrary​)一起在显式链接中使用。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <Windows.h>
#include <stdio.h>

typedef int (*MYFUNCTION)(int, int);

int main()
{
// 加载 DLL
HMODULE hModule = LoadLibraryEx(L"MyDLL.dll", NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
if (hModule == NULL)
{
printf("LoadLibraryEx failed, error code: %lu\n", GetLastError());
return 1;
}

// 获取 DLL 中的函数地址
MYFUNCTION myFunction = (MYFUNCTION)GetProcAddress(hModule, "MyFunction");
if (myFunction == NULL)
{
printf("GetProcAddress failed, error code: %lu\n", GetLastError());
FreeLibrary(hModule);
return 1;
}

// 调用 DLL 中的函数
int result = myFunction(10, 20);
printf("MyFunction result: %d\n", result);

// 释放 DLL
FreeLibrary(hModule);

return 0;
}

使用了 LoadLibraryEx()​ 函数来加载 MyDLL.dll​ 文件;加载成功后,使用 GetProcAddress()​ 函数获取 DLL 中 MyFunction​ 函数的地址,并将其转换为我们自定义的 MYFUNCTION​ 类型的函数指针。

最后,调用 MyFunction()​ 并打印结果,然后使用 FreeLibrary()​ 函数释放 DLL。

VirtualQueryEx()​ 和 ReadProcessMemory()​ 函数

VirtualQueryEx()

1
2
3
4
5
6
SIZE_T VirtualQueryEx(
HANDLE hProcess,
LPCVOID lpAddress,
PMEMORY_BASIC_INFORMATION lpBuffer,
SIZE_T dwLength
);
  • 作用:

    • 查询指定进程中某个虚拟地址范围的内存基本信息,如内存状态、保护属性、分配类型等。
  • 参数:

    • hProcess​: 要查询的进程句柄。
    • lpAddress​: 要查询的虚拟地址。
    • lpBuffer​: 指向 MEMORY_BASIC_INFORMATION​ 结构体的指针,用于接收查询结果。
    • dwLength​: lpBuffer​ 结构体的大小。
  • 返回值:

    • 成功时返回 lpBuffer​ 中填充的字节数。
    • 失败时返回 0,可以通过 GetLastError()​ 获取错误代码。

ReadProcessMemory()

1
2
3
4
5
6
7
BOOL ReadProcessMemory(
HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesRead
);
  • 作用:

    • 从指定进程的虚拟地址空间中读取数据。
  • 参数:

    • hProcess​: 要读取的进程句柄。
    • lpBaseAddress​: 要读取的虚拟地址。
    • lpBuffer​: 指向接收读取数据的缓冲区。
    • nSize​: 要读取的字节数。
    • lpNumberOfBytesRead​: 指针,用于接收实际读取的字节数。
  • 返回值:

    • 成功时返回 TRUE​。
    • 失败时返回 FALSE​,可以通过 GetLastError()​ 获取错误代码。

加载dll示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <Windows.h>
#include <stdio.h>

// 定义 DLL 的结构体
typedef struct _DLL_INFO {
DWORD Magic;
DWORD Version;
DWORD Reserved[2];
} DLL_INFO, *PDLL_INFO;

int main()
{
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, GetCurrentProcessId());
if (hProcess == NULL)
{
printf("OpenProcess failed, error code: %lu\n", GetLastError());
return 1;
}

// 使用 VirtualQueryEx() 查找 DLL 的基地址
MEMORY_BASIC_INFORMATION mbi;
PVOID baseAddress = NULL;
while (VirtualQueryEx(hProcess, baseAddress, &mbi, sizeof(mbi)) == sizeof(mbi))
{
if (mbi.Type == MEM_IMAGE && (mbi.Protect & PAGE_EXECUTE_READ))
{
// 找到一个可执行的内存区域,检查是否是 DLL
DWORD_PTR moduleBase = (DWORD_PTR)mbi.BaseAddress;
PDLL_INFO dllInfo = (PDLL_INFO)moduleBase;
if (dllInfo->Magic == 0x00000000 && dllInfo->Version == 0x00010000)
{
// 找到 DLL 的基地址
printf("DLL found at address: %p\n", (PVOID)moduleBase);
break;
}
}
baseAddress = (PVOID)((DWORD_PTR)mbi.BaseAddress + mbi.RegionSize);
}

if (baseAddress == NULL)
{
printf("DLL not found in the process.\n");
CloseHandle(hProcess);
return 1;
}

// 使用 ReadProcessMemory() 读取 DLL 的信息
DLL_INFO dllInfo;
if (!ReadProcessMemory(hProcess, baseAddress, &dllInfo, sizeof(dllInfo), NULL))
{
printf("ReadProcessMemory failed, error code: %lu\n", GetLastError());
CloseHandle(hProcess);
return 1;
}

printf("DLL Magic: 0x%08X\n", dllInfo.Magic);
printf("DLL Version: 0x%08X\n", dllInfo.Version);

CloseHandle(hProcess);
return 0;
}

这里使用 VirtualQueryEx()​ 函数来查找进程中可执行的内存区域,并检查是否存在 DLL 的标志信息。一旦找到 DLL 的基地址,我们就使用 ReadProcessMemory()​ 函数来读取 DLL 的头部信息。

win api调用dll的优\缺点

优:

  • 使用简单,API 函数如 LoadLibrary​ 和 GetProcAddress​ 易于使用。
  • 可以实现动态和静态链接两种方式,提供更多的灵活性。
  • 可以在运行时检查 DLL 是否已经被加载,避免重复加载。

缺:

  • 需要手动编写加载和卸载 DLL 的代码。
  • 需要处理 DLL 依赖关系和版本兼容性问题。
  • 如果 DLL 文件不存在或无法访问,会导致应用程序崩溃。

使用遍历 PEB 的模块链表加载

通过PEB来遍历进程模块没有Win API的使用痕迹,在某些场合更加好用

遍历 PEB的模块链表加载 DLL 的详细过程

获取PEB地址

32位应用程序的 PEB 的地址可以通过 fs:[0x30]获取,fs:[0]为TEB结构的地址

在 64 位 Windows 上, TEB 地址存储在 gs:0x30​ 寄存器中PEB 地址存储在 TEB 结构的 0x60​ 偏移处。因此, 可以通过先获取 TEB 地址, 然后读取 0x60​ 偏移处的值来获取 PEB 地址。

遍历模块链表:

PEB 中包含一个指向模块列表头部的指针, 通过遍历这个模块链表, 可以获取到进程中加载的所有模块(包括 DLL)的信息。

每个模块在链表中都由一个 LDR_DATA_TABLE_ENTRY​ 结构体来表示, 其中包含了模块的文件路径、基地址、入口点等信息。

获取 DLL 的基地址和入口点:

遍历模块链表时, 可以根据 DLL 的文件名或路径, 找到对应的 LDR_DATA_TABLE_ENTRY​ 结构体, 从中获取 DLL 的基地址和入口点地址。

TEB结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// TEB 结构体定义
typedef struct _TEB {
NT_TIB NtTib; // 0x000
PVOID EnvironmentPointer; // 0x038
CLIENT_ID ClientId; // 0x040
PVOID ActiveRpcHandle; // 0x050
PVOID ThreadLocalStoragePointer; // 0x058
PEB* ProcessEnvironmentBlock; // 0x060 √
ULONG LastErrorValue; // 0x068
ULONG CountOfOwnedCriticalSections; // 0x06C
PVOID CsrClientThread; // 0x070
PVOID Win32ThreadInfo; // 0x078
ULONG User32Reserved[26]; // 0x080
ULONG UserReserved[5]; // 0x0E8
PVOID WOW32Reserved; // 0x100
LCID CurrentLocale; // 0x108
ULONG FpSoftwareStatusRegister; // 0x10C
PVOID SystemReserved1[54]; // 0x110
NTSTATUS ExceptionCode; // 0x200
PVOID ActivationContextStackPointer; // 0x204
UCHAR SpareBytes[36]; // 0x208
ULONG TxFsContext; // 0x22C
PIO_EXCEPTION_BLOCK IoSb; // 0x230
ULONG IosbInfo; // 0x238
PVOID WaitingOnLoaderLock; // 0x23C
PVOID RequestedIrql; // 0x240
PVOID CantBlock; // 0x244
ULONG CantBlockCounter; // 0x248
PVOID FadingFunction; // 0x24C
ULONG FadingCounter; // 0x250
PVOID IdealProcessor; // 0x254
ULONG GuaranteedStackBytes; // 0x258
PVOID ReservedForPerf; // 0x25C
PVOID ReservedForOle; // 0x260
ULONG WaitingOnCount; // 0x264
ULONG Spare4; // 0x268
PVOID ReservedForDebugger; // 0x26C
} TEB, *PTEB;

TEB结构的偏移0x60就是PEB结构

PEB结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
typedef struct _PEB {
BOOLEAN InheritedAddressSpace; // Offset: 0x00
BOOLEAN ReadImageFileExecOptions; // Offset: 0x01
BOOLEAN BeingDebugged; // Offset: 0x02
union {
BOOLEAN BitField; // Offset: 0x03
struct {
BOOLEAN ImageUsesLargePages : 1; // Offset: 0x03
BOOLEAN IsProtectedProcess : 1; // Offset: 0x03
BOOLEAN IsImageDynamicallyRelocated : 1; // Offset: 0x03
BOOLEAN SkipPatchingUser32Forwarders : 1; // Offset: 0x03
BOOLEAN IsPackagedProcess : 1; // Offset: 0x03
BOOLEAN IsAppContainer : 1; // Offset: 0x03
BOOLEAN IsProtectedProcessLight : 1; // Offset: 0x03
BOOLEAN IsLongPathAwareProcess : 1; // Offset: 0x03
};
};
HANDLE Mutant; // Offset: 0x08
PVOID ImageBaseAddress; // Offset: 0x10
PPEB_LDR_DATA Ldr; // Offset: 0x18
PVOID ProcessParameters; // Offset: 0x20
PVOID SubSystemData; // Offset: 0x28
PVOID ProcessHeap; // Offset: 0x30
PRTL_CRITICAL_SECTION FastPebLock; // Offset: 0x38
PVOID AtlThunkSListPtr; // Offset: 0x40
PVOID IFEOKey; // Offset: 0x48
union {
ULONG CrossProcessFlags; // Offset: 0x50
struct {
ULONG ProcessInJob : 1; // Offset: 0x50
ULONG ProcessInitializing : 1; // Offset: 0x50
ULONG ProcessUsingVEH : 1; // Offset: 0x50
ULONG ProcessUsingVCH : 1; // Offset: 0x50
ULONG ProcessUsingFTH : 1; // Offset: 0x50
ULONG ReservedBits0 : 27; // Offset: 0x50
};
};
union {
PVOID KernelCallbackTable; // Offset: 0x54
PVOID UserSharedInfoPtr; // Offset: 0x54
};
ULONG SystemReserved; // Offset: 0x58
ULONG AtlThunkSListPtr32; // Offset: 0x5C
PVOID ApiSetMap; // Offset: 0x60
ULONG TlsExpansionCounter; // Offset: 0x68
PVOID TlsBitmap; // Offset: 0x70
ULONG TlsBitmapBits[2]; // Offset: 0x78
PVOID ReadOnlySharedMemoryBase; // Offset: 0x80
PVOID HotpatchInformation; // Offset: 0x88
PVOID *ReadOnlyStaticServerData; // Offset: 0x90
PVOID AnsiCodePageData; // Offset: 0x98
PVOID OemCodePageData; // Offset: 0xA0
PVOID UnicodeCaseTableData; // Offset: 0xA8
ULONG NumberOfProcessors; // Offset: 0xB0
ULONG NtGlobalFlag; // Offset: 0xB4
ULARGE_INTEGER CriticalSectionTimeout; // Offset: 0xB8
ULONG HeapSegmentReserve; // Offset: 0xC0
ULONG HeapSegmentCommit; // Offset: 0xC4
ULONG HeapDeCommitTotalFreeThreshold; // Offset: 0xC8
ULONG HeapDeCommitFreeBlockThreshold; // Offset: 0xCC
ULONG NumberOfHeaps; // Offset: 0xD0
ULONG MaximumNumberOfHeaps; // Offset: 0xD4
PVOID *ProcessHeaps; // Offset: 0xD8
PVOID GdiSharedHandleTable; // Offset: 0xE0
PVOID ProcessStarterHelper; // Offset: 0xE8
ULONG GdiDCAttributeList; // Offset: 0xF0
PVOID LoaderLock; // Offset: 0xF4
ULONG OSMajorVersion; // Offset: 0xF8
ULONG OSMinorVersion; // Offset: 0xFC
USHORT OSBuildNumber; // Offset: 0x100
USHORT OSCSDVersion; // Offset: 0x102
ULONG OSPlatformId; // Offset: 0x104
ULONG ImageSubsystem; // Offset: 0x108
ULONG ImageSubsystemMajorVersion; // Offset: 0x10C
ULONG ImageSubsystemMinorVersion; // Offset: 0x110
ULONG_PTR ImageProcessAffinityMask; // Offset: 0x114
ULONG GdiHandleBuffer[60]; // Offset: 0x118
PVOID PostProcessInitRoutine; // Offset: 0x2F8
PVOID TlsExpansionBitmap; // Offset: 0x300
ULONG TlsExpansionBitmapBits[32]; // Offset: 0x308
ULONG SessionId; // Offset: 0x388
ULARGE_INTEGER AppCompatFlags; // Offset: 0x390
ULARGE_INTEGER AppCompatFlagsUser; // Offset: 0x398
PVOID pShimData; // Offset: 0x3A0
PVOID AppCompatInfo; // Offset: 0x3A8
UNICODE_STRING CSDVersion; // Offset: 0x3B0
PVOID ActivationContextData; // Offset: 0x3C0
PVOID ProcessAssemblyStorageMap; // Offset: 0x3C8
PVOID SystemDefaultActivationContextData; // Offset: 0x3D0
PVOID SystemAssemblyStorageMap; // Offset: 0x3D8
ULONG MinimumStackCommit; // Offset: 0x3E0
PVOID FlsCallback; // Offset: 0x3E8
LIST_ENTRY FlsListHead; // Offset: 0x3F0
PVOID FlsBitmap; // Offset: 0x400
ULONG FlsBitmapBits[FLS_MAXIMUM_AVAILABLE / (sizeof(ULONG) * 8)]; // Offset: 0x408
ULONG FlsHighIndex; // Offset: 0x448
PVOID WerRegistrationData; // Offset: 0x450
PVOID WerShipAssertPtr; // Offset: 0x458
PVOID pContextData; // Offset: 0x460
PVOID pImageHeaderHash; // Offset: 0x468
union {
ULONG TracingFlags; // Offset: 0x470
struct {
ULONG HeapTracingEnabled : 1; // Offset: 0x470
ULONG CriticalSectionTracingEnabled : 1; // Offset: 0x470
ULONG LibraryTracingEnabled : 1; // Offset: 0x470
ULONG reserved : 29; // Offset: 0x470
};
};
ULONGLONG CsrServerReadOnlySharedMemoryBase; // Offset: 0x478
} PEB, *PPEB;

偏移0x18处找到Ldr

PPEB_LDR_DATA结构体

1
2
3
4
5
6
7
8
9
10
11
typedef struct _PEB_LDR_DATA {
ULONG Length; // 0x000
BOOLEAN Initialized; // 0x004
PVOID SsHandle; // 0x008
LIST_ENTRY InLoadOrderModuleList; // 0x010
LIST_ENTRY InMemoryOrderModuleList; // 0x020
LIST_ENTRY InInitializationOrderModuleList; // 0x030
PVOID EntryInProgress; // 0x040
BOOLEAN ShutdownInProgress; // 0x048
HANDLE ShutdownThreadId; // 0x050
} PEB_LDR_DATA, *PPEB_LDR_DATA;

LIST_ENTRY InLoadOrderModuleList; // 按照加载顺序组织的模块链表
LIST_ENTRY InMemoryOrderModuleList; // 按照内存地址顺序组织的模块链表

LIST_ENTRY InInitializationOrderModuleList; // 按照初始化顺序组织的模块链表

LIST_ENTRY结构体

1
2
3
4
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink; //0x00
struct _LIST_ENTRY *Blink; //0x04
} LIST_ENTRY, *PLIST_ENTRY;

Flink​: 指向下一个链表节点的指针

Blink​: 指向上一个链表节点的指针

可以看出来是个双向链表,这个结构体中的Flink​指向真正的模块链表,而模块链表的每一个成员都是一个LDR_DATA_TABLE_ENTRY​结构

_LDR_DATA_TABLE_ENTRY​结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks; // 0x00 - 链接到加载顺序双向链表
LIST_ENTRY InMemoryOrderLinks; // 0x10 - 链接到内存地址顺序双向链表
LIST_ENTRY InInitializationOrderLinks; // 0x20 - 链接到初始化顺序双向链表
PVOID DllBase; // 0x30 - DLL基地址
PVOID EntryPoint; // 0x38 - DLL入口点
ULONG SizeOfImage; // 0x40 - DLL映像大小
UNICODE_STRING FullDllName; // 0x48 - 完整DLL名称
UNICODE_STRING BaseDllName; // 0x60 - 基本DLL名称
ULONG Flags; // 0x78 - 标志位
USHORT LoadCount; // 0x7C - 加载计数
USHORT TlsIndex; // 0x7E - TLS索引
union {
LIST_ENTRY HashLinks; // 0x80 - 哈希链表链接
struct {
PVOID SectionPointer; // 0x80 - 内存映射指针
ULONG CheckSum; // 0x88 - 校验和
};
};
union {
ULONG TimeDateStamp; // 0x90 - 时间戳
PVOID LoadedImports; // 0x90 - 已加载的导入模块
};
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

PEB中链表的Flink指向 _LDR_DATA_TABLE_ENTRY结构体

image

(借了大佬一张图)

具体实现就不写了,不太会写,让chat生成了一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <windows.h>
#include <winternl.h>
#include <stdio.h>

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;

typedef struct _PEB {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
union {
BOOLEAN BitField;
struct {
BOOLEAN ImageUsesLargePages : 1;
BOOLEAN IsProtectedProcess : 1;
BOOLEAN IsImageDynamicallyRelocated : 1;
BOOLEAN SkipPatchingUser32Forwarders : 1;
BOOLEAN IsPackagedProcess : 1;
BOOLEAN IsAppContainer : 1;
BOOLEAN IsProtectedProcessLight : 1;
BOOLEAN IsLongPathAwareProcess : 1;
};
};
HANDLE Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr;
// ... (other fields omitted)
} PEB, *PPEB;

int main() {
PPEB pPeb;
PPEB_LDR_DATA pLdr;
PLIST_ENTRY pListEntry;
PLDR_DATA_TABLE_ENTRY pModuleEntry;

// 获取进程环境块 (PEB)
__asm {
mov rax, gs:[0x60]
mov pPeb, rax
}

// 获取 PEB 的 Ldr 字段
pLdr = pPeb->Ldr;

// 遍历 Ldr 的 InLoadOrderModuleList 链表
pListEntry = pLdr->InLoadOrderModuleList.Flink;
while (pListEntry != &pLdr->InLoadOrderModuleList) {
pModuleEntry = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);

// 输出模块名称
printf("Module: %ws\n", pModuleEntry->BaseDllName.Buffer);

// 加载 DLL
HMODULE hModule = LoadLibraryW(pModuleEntry->BaseDllName.Buffer);
if (hModule) {
printf("Loaded DLL: %p\n", hModule);
} else {
printf("Failed to load DLL: %lu\n", GetLastError());
}

pListEntry = pListEntry->Flink;
}

return 0;
}

遍历 PEB 的模块链表加载dll的优缺点

优:

  • 无需手动编写加载和卸载 DLL 的代码,可以自动加载所需的 DLL。
  • 可以在不知道 DLL 文件路径的情况下加载 DLL。
  • 可以避免 DLL 文件不存在或无法访问导致的应用程序崩溃。

缺:

  • 实现相对复杂,需要了解 PEB 的结构和遍历模块链表的方法。
  • 可能无法检测到某些 DLL 的加载状态,比如动态加载的 DLL。
  • 需要处理 DLL 依赖关系和版本兼容性问题。
  • 可能无法提供与 Windows API 调用 DLL 相同的灵活性和控制力。