跳到主要内容

进程执行 DLL 的原理

学习 DLL 注入时,不可避免会产生以下疑问:

加载器 是怎么工作的?为什么远程线程注入后就能执行 DllMain? 目标进程能否不执行 DllMain? 反射式 DLL 又是怎么工作的呢? 为什么注入后就会执行自定义的 加载器?

下面以普通的远程线程注入,以及反射式 DLL 注入为例,来解答这些问题。

好的,让我们深入探讨一下加载器的工作原理,并解释远程线程注入和反射式 DLL 注入的机制。

远程线程注入的加载器工作原理

远程线程注入的基本步骤

  1. 获取目标进程的句柄:使用 OpenProcess 打开目标进程并获取其句柄。
  2. 分配内存:使用 VirtualAllocEx 在目标进程的内存空间中分配一块内存,用于存储 DLL 的路径。
  3. 写入 DLL 路径:使用 WriteProcessMemory 将 DLL 的路径写入到目标进程分配的内存中。
  4. 创建远程线程:使用 CreateRemoteThread 在目标进程中创建一个新线程,并使用 LoadLibrary 函数加载 DLL。

加载器是如何工作的

当我们调用 CreateRemoteThread 并指定 LoadLibrary 函数作为新线程的入口点时,目标进程会创建一个新线程,执行 LoadLibrary 函数。

这个 LoadLibrary 函数的作用是加载指定的 DLL 并执行其 DllMain 函数(Windows 标准)。DllMain 是 DLL 的入口点,当 DLL 被加载时,操作系统会调用 DllMain,执行其中的代码。

为什么远程线程注入后会执行 DllMain

远程线程注入通过创建一个新线程并调用 LoadLibrary 来实现 DLL 的加载。LoadLibrary 函数负责:

  1. 加载 DLL:将 DLL 映像加载到目标进程的地址空间中。
  2. 调用 DllMain:一旦 DLL 被加载,操作系统会自动调用 DLL 的入口点函数 DllMain,传递适当的参数(如 DLL_PROCESS_ATTACH)。

目标进程是否可以不执行 DllMain

在常规的远程线程注入方法中,目标进程会执行 DllMain。然而,可以通过一些高级技术避免执行 DllMain,例如:

  1. 手动映射 DLL:通过手动加载 DLL 的方式,将 DLL 映像加载到目标进程中,并手动解析和执行其各个部分,而不是依赖于 LoadLibrary
  2. 修改 DLL 的入口点:修改 DLL 的 PE 头信息,使其入口点指向其他位置,或者在入口点内添加条件判断以跳过 DllMain 的某些代码。

反射式 DLL 注入的工作原理

反射式 DLL 注入的基本步骤

  1. 准备反射式 DLL:需要对 DLL 进行特殊处理,使其包含自我加载的功能,通常是通过在 DLL 中包含加载器代码来实现的。
  2. 分配内存:在目标进程中分配足够的内存,用于存储整个 DLL 映像。
  3. 写入 DLL 映像:将 DLL 映像写入目标进程的内存。
  4. 执行加载器代码:在目标进程中创建一个远程线程,执行注入的 DLL 映像中的加载器代码。

加载器是如何工作的

反射式 DLL 的加载器(如 ReflectiveLoader)包含在 DLL 中,并负责完成以下任务:

  1. 解析 DLL 头:手动解析 DLL 的 PE 头信息,确定 DLL 各个部分在内存中的布局。
  2. 加载依赖项:手动加载 DLL 所需的所有依赖库。
  3. 修复重定位:处理重定位表,以确保 DLL 能够在目标内存地址上正确运行。
  4. 调用入口点:手动调用 DLL 的入口点函数 DllMain 或者其他自定义的初始化函数。

为什么注入后会执行自定义的加载器

反射式 DLL 注入的关键在于注入的 DLL 包含自我加载的功能。当我们将 DLL 映像写入目标进程并创建一个远程线程来执行 DLL 映像中的加载器代码时,加载器代码会:

  1. 初始化加载环境:设置必要的环境和上下文。
  2. 加载自身:解析和加载 DLL 的各个部分,就像 LoadLibrary 函数所做的那样。
  3. 执行入口点:在解析和加载完成后,手动调用 DLL 的入口点函数或其他自定义初始化代码。

示例代码

远程线程注入

#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>

void InjectRemoteThread(DWORD dwProcessId, const char* dllPath) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess == NULL) {
printf("OpenProcess failed\n");
return;
}

SIZE_T dllPathSize = strlen(dllPath) + 1;
LPVOID pDllPathRemote = VirtualAllocEx(hProcess, NULL, dllPathSize, MEM_COMMIT, PAGE_READWRITE);
if (pDllPathRemote == NULL) {
printf("VirtualAllocEx failed\n");
CloseHandle(hProcess);
return;
}

if (!WriteProcessMemory(hProcess, pDllPathRemote, dllPath, dllPathSize, NULL)) {
printf("WriteProcessMemory failed\n");
VirtualFreeEx(hProcess, pDllPathRemote, 0, MEM_RELEASE);
CloseHandle(hProcess);
return;
}

// 可以看到这里是直接调用 LoadLibraryA 函数
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, pDllPathRemote, 0, NULL);
if (hThread == NULL) {
printf("CreateRemoteThread failed\n");
VirtualFreeEx(hProcess, pDllPathRemote, 0, MEM_RELEASE);
CloseHandle(hProcess);
return;
}

WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
}

int main() {
DWORD dwProcessId = 1234; // 目标进程的 PID
const char* dllPath = "C:\\path\\to\\your\\dll.dll";

InjectRemoteThread(dwProcessId, dllPath);

return 0;
}

反射式 DLL 注入

反射式 DLL 的代码(ReflectiveDLL.c)

#include <windows.h>

// 定义 ReflectiveLoader 函数
__declspec(dllexport) void ReflectiveLoader() {
// 自我加载的实现代码
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
ReflectiveLoader();
break;
}
return TRUE;
}

注入器代码(ReflectiveInjector.cpp)

#include <windows.h>
#include <stdio.h>
#include <TlHelp32.h>

// 获取目标进程的 PID
DWORD GetProcessId(const char* processName) {
PROCESSENTRY32 processEntry;
processEntry.dwSize = sizeof(PROCESSENTRY32);

HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return 0;

Process32First(hSnapshot, &processEntry);
if (!_stricmp(processEntry.szExeFile, processName)) {
CloseHandle(hSnapshot);
return processEntry.th32ProcessID;
}

while (Process32Next(hSnapshot, &processEntry)) {
if (!_stricmp(processEntry.szExeFile, processName)) {
CloseHandle(hSnapshot);
return processEntry.th32ProcessID;
}
}

CloseHandle(hSnapshot);
return 0;
}

// 反射式 DLL 注入函数
BOOL InjectReflectiveDLL(DWORD processId, const char* dllPath) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
if (hProcess == NULL) {
printf("OpenProcess failed\n");
return FALSE;
}

// 读取 DLL 文件
HANDLE hFile = CreateFile(dllPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("CreateFile failed\n");
CloseHandle(hProcess);
return FALSE;
}

DWORD fileSize = GetFileSize(hFile, NULL);
BYTE* dllBuffer = new BYTE[fileSize];
DWORD bytesRead;
if (!ReadFile(hFile, dllBuffer, fileSize, &bytesRead, NULL)) {
printf("ReadFile failed\n");
CloseHandle(hFile);
CloseHandle(hProcess);
delete[] dllBuffer;
return FALSE;
}
CloseHandle(hFile);

// 在目标进程中分配内存
LPVOID pRemoteMemory = VirtualAllocEx(hProcess, NULL, fileSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (pRemoteMemory == NULL) {
printf("VirtualAllocEx failed\n");
CloseHandle(hProcess);
delete[] dllBuffer;
return FALSE;
}

// 写入 DLL 映像到目标进程
if (!WriteProcessMemory(hProcess, pRemoteMemory, dllBuffer, fileSize, NULL

)) {
printf("WriteProcessMemory failed\n");
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);
delete[] dllBuffer;
return FALSE;
}
delete[] dllBuffer;

// 获取 ReflectiveLoader 的偏移地址
DWORD reflectiveLoaderOffset = 0x1000; // 需要根据实际情况调整

// 相较于远程线程注入,这里直接调用 ReflectiveLoader 函数
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)((BYTE*)pRemoteMemory + reflectiveLoaderOffset), pRemoteMemory, 0, NULL);
if (hThread == NULL) {
printf("CreateRemoteThread failed\n");
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}

WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}

int main() {
const char* targetProcessName = "target.exe";
const char* dllPath = "path\\to\\reflective.dll";

DWORD processId = GetProcessId(targetProcessName);
if (processId == 0) {
printf("Target process not found\n");
return 1;
}

if (InjectReflectiveDLL(processId, dllPath)) {
printf("DLL injected successfully\n");
} else {
printf("DLL injection failed\n");
}

return 0;
}

通过以上代码示例和解释,我们可以更清晰地理解加载器的工作原理,以及为什么不同的 DLL 注入方法在注入后能够执行自定义的加载器代码。