C++ DLL注入工具完整源码与原理详解

宋时安
宋时安
发布时间:2026-10-16 | 7人浏览过
原创

本文提供了一套完整的C++ DLL注入工具源码,深入解析了如何利用远程线程(CreateRemoteThread)和标准Windows API实现DLL的动态加载与卸载。文章详细说明了从进程识别、内存分配到执行加载的完整逻辑链条,是理解Windows底层模块挂钩技术的实用参考。

核心原理:远程线程注入

DLL注入的核心在于利用目标进程的上下文执行特定代码。由于 Kernel32.dll 是所有Windows进程的基座,其内部的API函数地址在所有进程中保持一致。因此,可以通过在目标进程中创建一个新线程,强制该线程执行 LoadLibraryA 函数,从而将指定路径的DLL载入目标进程内存。

关键步骤与代码实现

第一步:查找目标进程

首先需要获取目标可执行文件的进程ID(PID)。这通常通过遍历系统进程快照实现:

  1. 创建快照:使用 CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL) 获取当前所有进程信息。
  2. 遍历比对:使用 Process32NextW 循环读取进程名称,与目标名称进行字符串匹配。
  3. 返回结果:匹配成功后返回进程ID;若未找到或遍历结束,返回0。

第二步:打开目标进程并获取权限

获取PID后,必须获得操作该进程内存的句柄:

  • 调用 OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId)。
  • 若返回NULL,说明权限不足或进程不存在,此时应终止注入流程。

第三步:在目标进程空间开辟内存并写入路径

直接将本地内存地址传递给目标进程是无效的,因为每个进程的虚拟地址空间是隔离的。必须将DLL的路径字符串复制到目标进程的内存中:

  1. 分配内存:调用 VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE) 在目标进程申请可读写的内存块,得到远程地址指针 DLLAddress。
  2. 写入数据:使用 WriteProcessMemory(hProcess, DLLAddress, szPath, dwSize, NULL) 将本机的DLL文件路径字符串拷贝到刚才申请的远程地址中。

第四步:创建远程线程执行加载

这是注入动作的最终一步:

  1. 获取函数地址:
    - 获取 Kernel32.dll 的模块句柄:GetModuleHandle(TEXT("Kernel32.dll"))。
    - 获取 LoadLibraryA 函数的入口点地址:GetProcAddress(k32, "LoadLibraryA")。
  2. 启动线程:调用 CreateRemoteThread,参数设定如下:
    - hProcess:目标进程句柄。
    - lpStartAddress:LoadLibraryA 的地址(强制类型转换为 LPTHREAD_START_ROUTINE)。
    - lpParameter:之前在目标进程中分配的 DLLAddress(即DLL路径)。
  3. 清理:线程启动后,关闭句柄 CloseHandle。此时目标进程已完成DLL加载。

完整源码示例

以下代码展示了包含注入、卸载及对话框界面的完整实现逻辑。请注意,实际使用时需根据具体项目调整包含的头文件和依赖项。

#include "Inject_Main.h"
#include "resource.h"
#include <Windows.h>
#include <TlHelp32.h>
#include <string>
#include <TCHAR.H>

using namespace std;

/// <summary>
/// 通过进程名称获取该进程句柄(实际上是进程ID)
/// </summary>
DWORD GetProcessByName(CONST TCHAR* processName) {
    HANDLE processALL = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
    PROCESSENTRY32W processInfo = { 0 };
    processInfo.dwSize = sizeof(PROCESSENTRY32W);

    do {
        if (_tcscmp(processInfo.szExeFile, processName) == 0) {
            CloseHandle(processALL);
            return processInfo.th32ProcessID;
        }
    } while (Process32Next(processALL, &processInfo));
    
    CloseHandle(processALL);
    return 0;
}

/// <summary>
/// 把指定DLL注入到指定进程中
/// </summary>
void InjectDll(const wchar_t* processName, const char* dllPath) {
    DWORD dword = GetProcessByName(processName);
    if (dword == 0) {
        MessageBox(NULL, TEXT("没有找到指定进程"), TEXT("错误"), 0);
        return;
    }

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dword);
    if (hProcess == NULL) {
        MessageBox(NULL, TEXT("指定进程打开失败"), TEXT("错误"), 0);
        return;
    }

    // 1. 在目标进程中开辟内存
    LPVOID DLLAddress = VirtualAllocEx(hProcess, NULL, strlen(dllPath), MEM_COMMIT, PAGE_READWRITE);
    
    // 2. 将DLL路径写入目标进程内存
    if (WriteProcessMemory(hProcess, DLLAddress, dllPath, strlen(dllPath), NULL) == 0) {
        MessageBox(NULL, TEXT("路径写入失败"), TEXT("错误"), 0);
        return;
    }

    // 3. 获取 LoadLibraryA 地址
    HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
    LPVOID loadADD = GetProcAddress(k32, "LoadLibraryA");

    // 4. 创建远程线程执行 LoadLibraryA
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadADD, DLLAddress, 0, NULL);

    CloseHandle(hThread);
    CloseHandle(hProcess);
}

/// <summary>
/// 把指定进程中的DLL卸载掉
/// </summary>
void UnInjectDll(const wchar_t* processName) {
    DWORD dword = GetProcessByName(processName);
    if (dword == 0) return;

    // 获取指定模块句柄
    HMODULE hmodule = /* 此处需补充遍历模块获取指定DLL句柄的逻辑 */ 0; 
    
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dword);
    if (hProcess == NULL) return;

    HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
    LPVOID loadADD = GetProcAddress(k32, "FreeLibrary"); // 注意:卸载使用 FreeLibrary

    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadADD, (LPVOID)hmodule, 0, NULL);
    CloseHandle(hThread);
    CloseHandle(hProcess);
}

验证注入状态

注入完成后,需要确认DLL是否已成功进入目标进程。常用的工具有以下两种:

方式一:使用 Process Explorer (Procexp)

打开Procexp,定位到目标进程,查看“Modules”(模块)标签页。检查列表中是否出现了目标DLL的文件名。如果在列表中出现,且其基址并非0,则说明注入成功。

方式二:使用 Cheat Engine (CE)

Cheat Engine也具备进程模块分析功能。通过附加目标进程,展开进程树中的DLL列表,同样可以观察到新注入的DLL条目。

原理验证:Kernel32.dll 地址一致性

为了确信跨进程调用 LoadLibrary 的有效性,我们可以验证不同进程中 Kernel32.dll 的地址是否相同。在CE中分别查看不同进程的模块列表,会发现 Kernel32.dll 的基地址是一致的。进而查看其中 FreeLibrary 和 LoadLibraryA 函数的入口地址也是完全相同的。这正是该技术可行的基础。

注意事项

  • 权限问题:如果被注入进程具有高完整性级别(如系统进程或管理员权限程序),普通用户权限运行的注入器可能无法打开进程,需要以管理员身份运行。
  • 字符集差异:LoadLibrary 是宏,根据编译配置自动指向 A 或 W 版本。代码中明确使用 LoadLibraryA 可以确保处理的是 ANSI 字符串路径。
  • 安全软件拦截:此类基于远程线程的操作常被杀毒软件视为恶意行为并拦截,测试时请添加白名单。
以上就是C++ DLL注入工具的详细内容,更多关于C++ Windows底层开发及进程操控的资料请关注本站其它相关文章!
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表