使用 Windows API
静态链接 (Static Linking) :
- 在编译时将 DLL 中的函数链接到可执行文件中。
- 应用程序在运行时不需要额外加载 DLL。
- 优点是加载和执行速度快,缺点是可执行文件体积较大。
动态链接 (Dynamic Linking) :
- 在运行时动态加载 DLL,并调用 DLL 中的函数。
- 应用程序需要使用
LoadLibrary 和 GetProcAddress 等 Windows API 函数来加载和访问 DLL 中的函数。
- 优点是可执行文件体积较小,缺点是加载和执行速度稍慢,并且需要确保 DLL 文件在运行时可用。
- 在编译时使用
#include 或 #pragma comment 指令,将 DLL 中的函数声明添加到应用程序的源代码中。
- 编译器会自动生成调用 DLL 函数的代码,并在链接时将其链接到可执行文件中。
- 这种方式对开发人员来说更加简单和方便,但缺点是可执行文件体积较大。
- 在运行时使用
LoadLibrary 和 GetProcAddress 等 Windows API 函数动态加载 DLL,并调用 DLL 中的函数。
- 这种方式给开发人员更多的灵活性和控制权,但需要编写更多的代码。
常用win api函数(列举了两种)
LoadLibraryEx函数
1 2 3 4 5
| HMODULE LoadLibraryEx( LPCTSTR lpLibFileName, HANDLE hFile, DWORD dwFlags );
|
参数说明:
lpLibFileName: 要加载的 DLL 文件的完整路径。
hFile: 可选参数,用于指定加载 DLL 的文件句柄。通常设置为 NULL。
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() 函数获取错误代码。
进程调用 LoadLibrary 或 LoadLibraryEx 以显式链接到 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() { 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; }
MYFUNCTION myFunction = (MYFUNCTION)GetProcAddress(hModule, "MyFunction"); if (myFunction == NULL) { printf("GetProcAddress failed, error code: %lu\n", GetLastError()); FreeLibrary(hModule); return 1; }
int result = myFunction(10, 20); printf("MyFunction result: %d\n", result);
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>
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; }
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)) { DWORD_PTR moduleBase = (DWORD_PTR)mbi.BaseAddress; PDLL_INFO dllInfo = (PDLL_INFO)moduleBase; if (dllInfo->Magic == 0x00000000 && dllInfo->Version == 0x00010000) { 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; }
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
| typedef struct _TEB { NT_TIB NtTib; PVOID EnvironmentPointer; CLIENT_ID ClientId; PVOID ActiveRpcHandle; PVOID ThreadLocalStoragePointer; PEB* ProcessEnvironmentBlock; ULONG LastErrorValue; ULONG CountOfOwnedCriticalSections; PVOID CsrClientThread; PVOID Win32ThreadInfo; ULONG User32Reserved[26]; ULONG UserReserved[5]; PVOID WOW32Reserved; LCID CurrentLocale; ULONG FpSoftwareStatusRegister; PVOID SystemReserved1[54]; NTSTATUS ExceptionCode; PVOID ActivationContextStackPointer; UCHAR SpareBytes[36]; ULONG TxFsContext; PIO_EXCEPTION_BLOCK IoSb; ULONG IosbInfo; PVOID WaitingOnLoaderLock; PVOID RequestedIrql; PVOID CantBlock; ULONG CantBlockCounter; PVOID FadingFunction; ULONG FadingCounter; PVOID IdealProcessor; ULONG GuaranteedStackBytes; PVOID ReservedForPerf; PVOID ReservedForOle; ULONG WaitingOnCount; ULONG Spare4; PVOID ReservedForDebugger; } 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; 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; PVOID ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PRTL_CRITICAL_SECTION FastPebLock; PVOID AtlThunkSListPtr; PVOID IFEOKey; union { ULONG CrossProcessFlags; struct { ULONG ProcessInJob : 1; ULONG ProcessInitializing : 1; ULONG ProcessUsingVEH : 1; ULONG ProcessUsingVCH : 1; ULONG ProcessUsingFTH : 1; ULONG ReservedBits0 : 27; }; }; union { PVOID KernelCallbackTable; PVOID UserSharedInfoPtr; }; ULONG SystemReserved; ULONG AtlThunkSListPtr32; PVOID ApiSetMap; ULONG TlsExpansionCounter; PVOID TlsBitmap; ULONG TlsBitmapBits[2]; PVOID ReadOnlySharedMemoryBase; PVOID HotpatchInformation; PVOID *ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; ULONG NumberOfProcessors; ULONG NtGlobalFlag; ULARGE_INTEGER CriticalSectionTimeout; ULONG HeapSegmentReserve; ULONG HeapSegmentCommit; ULONG HeapDeCommitTotalFreeThreshold; ULONG HeapDeCommitFreeBlockThreshold; ULONG NumberOfHeaps; ULONG MaximumNumberOfHeaps; PVOID *ProcessHeaps; PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; ULONG GdiDCAttributeList; PVOID LoaderLock; ULONG OSMajorVersion; ULONG OSMinorVersion; USHORT OSBuildNumber; USHORT OSCSDVersion; ULONG OSPlatformId; ULONG ImageSubsystem; ULONG ImageSubsystemMajorVersion; ULONG ImageSubsystemMinorVersion; ULONG_PTR ImageProcessAffinityMask; ULONG GdiHandleBuffer[60]; PVOID PostProcessInitRoutine; PVOID TlsExpansionBitmap; ULONG TlsExpansionBitmapBits[32]; ULONG SessionId; ULARGE_INTEGER AppCompatFlags; ULARGE_INTEGER AppCompatFlagsUser; PVOID pShimData; PVOID AppCompatInfo; UNICODE_STRING CSDVersion; PVOID ActivationContextData; PVOID ProcessAssemblyStorageMap; PVOID SystemDefaultActivationContextData; PVOID SystemAssemblyStorageMap; ULONG MinimumStackCommit; PVOID FlsCallback; LIST_ENTRY FlsListHead; PVOID FlsBitmap; ULONG FlsBitmapBits[FLS_MAXIMUM_AVAILABLE / (sizeof(ULONG) * 8)]; ULONG FlsHighIndex; PVOID WerRegistrationData; PVOID WerShipAssertPtr; PVOID pContextData; PVOID pImageHeaderHash; union { ULONG TracingFlags; struct { ULONG HeapTracingEnabled : 1; ULONG CriticalSectionTracingEnabled : 1; ULONG LibraryTracingEnabled : 1; ULONG reserved : 29; }; }; ULONGLONG CsrServerReadOnlySharedMemoryBase; } 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; BOOLEAN Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID EntryInProgress; BOOLEAN ShutdownInProgress; HANDLE ShutdownThreadId; } 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; struct _LIST_ENTRY *Blink; } 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; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; union { LIST_ENTRY HashLinks; struct { PVOID SectionPointer; ULONG CheckSum; }; }; union { ULONG TimeDateStamp; PVOID LoadedImports; }; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
|
PEB中链表的Flink指向 _LDR_DATA_TABLE_ENTRY结构体
勘误:这部分更准确地说是在枚举已加载模块,而不是直接加载模块;示例中的 LoadLibraryW(pModuleEntry->BaseDllName.Buffer) 仍然属于 WinAPI 调用;另外 __asm 在 x64 MSVC 下不能直接这样使用。下面的代码仅作思路示意。
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; } PEB, *PPEB;
int main() { PPEB pPeb; PPEB_LDR_DATA pLdr; PLIST_ENTRY pListEntry; PLDR_DATA_TABLE_ENTRY pModuleEntry;
__asm { mov rax, gs:[0x60] mov pPeb, rax }
pLdr = pPeb->Ldr;
pListEntry = pLdr->InLoadOrderModuleList.Flink; while (pListEntry != &pLdr->InLoadOrderModuleList) { pModuleEntry = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
printf("Module: %ws\n", pModuleEntry->BaseDllName.Buffer);
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 相同的灵活性和控制力。