DLL注入实战:3招轻松攻破进程

DLL(动态链接库) 是一种包含可由多个程序同时使用的代码和数据的库。它们最早出现在 Windows 3.1 操作系统中,旨在解决内存资源不足的问题。DLL 文件的主要作用包括提高程序的可重用性、促进代码重用和内存的有效使用。例如,Windows 中的 Comdlg32 DLL 执行与对话框有关的常见函数,允许多个程序共享这些功能。

DLL 注入实战指南(可直接上手版)

DLL 注入是 Windows 环境下将自定义动态链接库(DLL)强制加载到目标进程地址空间的技术,常用于进程监控、功能扩展、调试分析等场景。本文基于《深入浅出 Windows API 程序设计:核心编程篇》的底层原理,提供3 种可直接上手的 DLL 注入方案,包含完整代码、操作步骤及注意事项,确保新手也能快速实现。

一、核心前置知识

在动手前需明确 2 个关键概念,避免操作出错:

DLL 加载原理:进程通过
LoadLibrary
函数加载 DLL 时,会将 DLL 文件映射到自身虚拟地址空间,同时执行 DLL 的
DllMain
初始化函数。注入的本质就是 “让目标进程主动调用
LoadLibrary
加载我们的 DLL”。权限要求:注入需获取目标进程的有效句柄,且句柄需包含
PROCESS_CREATE_THREAD
(创建线程)、
PROCESS_QUERY_INFORMATION
(查询信息)、
PROCESS_VM_OPERATION
(内存操作)等权限,否则会失败。

二、方案 1:远程线程注入(最通用,推荐新手)

原理

通过
CreateRemoteThread
函数在目标进程中创建一个新线程,让该线程调用
LoadLibrary
加载指定 DLL。这是 Windows 最经典、兼容性最强的注入方案,支持 32/64 位进程(需注意 DLL 与目标进程位数一致)。

操作步骤(共 5 步)

步骤 1:编写待注入的 DLL(核心功能代码)

创建一个 C++ 项目(选择 “动态链接库 (DLL)” 模板),实现注入后要执行的功能(示例:弹出消息框 + 监控目标进程窗口)。



// InjectDll.cpp
#include <windows.h>
#include <tchar.h>
 
// DLL加载时执行(注入成功后触发)
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        // 1. 弹出注入成功提示(仅作演示,实际可替换为监控/挂钩等逻辑)
        MessageBox(NULL, TEXT("DLL注入成功!"), TEXT("注入提示"), MB_OK | MB_ICONINFORMATION);
        
        // 2. (可选)监控目标进程窗口(示例:获取目标窗口标题)
        HWND hTargetWnd = GetForegroundWindow(); // 获取当前激活窗口(即目标进程窗口)
        TCHAR szWndTitle[256] = {0};
        GetWindowText(hTargetWnd, szWndTitle, 256);
        TCHAR szMsg[512] = {0};
        _stprintf_s(szMsg, TEXT("目标进程窗口标题:%s"), szWndTitle);
        MessageBox(NULL, szMsg, TEXT("进程监控"), MB_OK);
        
        // 注意:DllMain中避免复杂操作(如创建窗口、长时间循环),否则可能导致目标进程崩溃
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
步骤 2:编译 DLL(关键:匹配目标进程位数)

若目标进程是 32 位(如记事本):在 VS 中设置 “解决方案平台” 为
x86
,编译生成
InjectDll.dll
。若目标进程是 64 位(如 64 位浏览器):设置平台为
x64
,编译生成 64 位 DLL。编译后记录 DLL 的完整路径(如
C:ToolsInjectDll.dll
),后续注入时需用到。

步骤 3:编写注入器程序(核心注入逻辑)

创建一个 C++ 控制台项目,实现 “找到目标进程→申请内存→写入 DLL 路径→创建远程线程” 的完整流程。



// Injector.cpp
#include <windows.h>
#include <TlHelp32.h>
#include <tchar.h>
#include <iostream>
using namespace std;
 
// 1. 根据进程名找到目标进程ID(如“notepad.exe”)
DWORD FindTargetProcessId(LPCTSTR lpProcessName) {
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE) {
        _tprintf(TEXT("创建进程快照失败,错误码:%d
"), GetLastError());
        return 0;
    }
 
    PROCESSENTRY32 pe = { sizeof(PROCESSENTRY32) };
    if (Process32First(hSnapshot, &pe)) {
        do {
            // 比较进程名(不区分大小写)
            if (_tcsicmp(pe.szExeFile, lpProcessName) == 0) {
                CloseHandle(hSnapshot);
                return pe.th32ProcessID; // 返回目标进程ID
            }
        } while (Process32Next(hSnapshot, &pe));
    }
 
    _tprintf(TEXT("未找到进程:%s
"), lpProcessName);
    CloseHandle(hSnapshot);
    return 0;
}
 
// 2. 核心注入函数
BOOL RemoteThreadInject(DWORD dwProcessId, LPCTSTR lpDllPath) {
    // 步骤1:打开目标进程,获取带权限的句柄
    HANDLE hProcess = OpenProcess(
        PROCESS_CREATE_THREAD |    // 允许创建远程线程
        PROCESS_QUERY_INFORMATION |// 允许查询进程信息
        PROCESS_VM_OPERATION |    // 允许内存操作(申请/写入)
        PROCESS_VM_WRITE |        // 允许写入内存
        PROCESS_VM_READ,          // 允许读取内存
        FALSE, 
        dwProcessId
    );
    if (hProcess == NULL) {
        _tprintf(TEXT("打开目标进程失败,错误码:%d
"), GetLastError());
        return FALSE;
    }
 
    // 步骤2:在目标进程中申请内存,用于存放DLL路径(需包含字符串终止符'')
    DWORD dwDllPathSize = (_tcslen(lpDllPath) + 1) * sizeof(TCHAR); // 计算路径总字节数
    LPVOID lpRemoteAddr = VirtualAllocEx(
        hProcess,
        NULL,                   // 让系统自动分配内存地址
        dwDllPathSize,          // 内存大小
        MEM_COMMIT | MEM_RESERVE, // 提交+预订内存
        PAGE_READWRITE           // 内存权限:可读可写
    );
    if (lpRemoteAddr == NULL) {
        _tprintf(TEXT("申请远程内存失败,错误码:%d
"), GetLastError());
        CloseHandle(hProcess);
        return FALSE;
    }
 
    // 步骤3:将DLL路径写入目标进程的远程内存
    BOOL bWrite = WriteProcessMemory(
        hProcess,
        lpRemoteAddr,           // 目标进程的内存地址
        lpDllPath,              // 本地DLL路径缓冲区
        dwDllPathSize,          // 写入字节数
        NULL                    // 不需要返回实际写入字节数
    );
    if (!bWrite) {
        _tprintf(TEXT("写入远程内存失败,错误码:%d
"), GetLastError());
        VirtualFreeEx(hProcess, lpRemoteAddr, 0, MEM_RELEASE); // 释放申请的内存
        CloseHandle(hProcess);
        return FALSE;
    }
 
    // 步骤4:获取LoadLibrary函数地址(系统函数地址在所有进程中相同)
    LPVOID lpLoadLibAddr = (LPVOID)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryW");
    if (lpLoadLibAddr == NULL) {
        _tprintf(TEXT("获取LoadLibrary地址失败,错误码:%d
"), GetLastError());
        VirtualFreeEx(hProcess, lpRemoteAddr, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }
 
    // 步骤5:在目标进程中创建远程线程,执行LoadLibrary(lpRemoteAddr)
    HANDLE hRemoteThread = CreateRemoteThread(
        hProcess,
        NULL,                   // 默认安全属性
        0,                      // 线程栈大小(默认)
        (LPTHREAD_START_ROUTINE)lpLoadLibAddr, // 线程函数(LoadLibrary)
        lpRemoteAddr,           // 线程参数(DLL路径)
        0,                      // 立即运行线程
        NULL                    // 不需要返回线程ID
    );
    if (hRemoteThread == NULL) {
        _tprintf(TEXT("创建远程线程失败,错误码:%d
"), GetLastError());
        VirtualFreeEx(hProcess, lpRemoteAddr, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return FALSE;
    }
 
    // 步骤6:等待远程线程执行完成(可选,避免资源泄漏)
    WaitForSingleObject(hRemoteThread, INFINITE);
 
    // 步骤7:释放资源
    CloseHandle(hRemoteThread);
    VirtualFreeEx(hProcess, lpRemoteAddr, 0, MEM_RELEASE);
    CloseHandle(hProcess);
 
    _tprintf(TEXT("注入成功!
"));
    return TRUE;
}
 
// 主函数(入口)
int _tmain(int argc, TCHAR* argv[]) {
    // 配置:目标进程名 + 待注入DLL路径
    LPCTSTR lpTargetProcess = TEXT("notepad.exe"); // 目标进程(如记事本)
    LPCTSTR lpDllPath = TEXT("C:\Tools\InjectDll.dll"); // 替换为你的DLL路径
 
    // 1. 找到目标进程ID
    DWORD dwProcessId = FindTargetProcessId(lpTargetProcess);
    if (dwProcessId == 0) {
        system("pause");
        return 1;
    }
    _tprintf(TEXT("找到目标进程,ID:%d
"), dwProcessId);
 
    // 2. 执行注入
    BOOL bSuccess = RemoteThreadInject(dwProcessId, lpDllPath);
    if (!bSuccess) {
        system("pause");
        return 1;
    }
 
    system("pause");
    return 0;
}
步骤 4:编译注入器

同样需匹配目标进程位数(与 DLL 位数一致),编译生成
Injector.exe

步骤 5:执行注入(实际操作)

启动目标进程:打开记事本(
notepad.exe
),确保进程正在运行。运行注入器:双击
Injector.exe
,若控制台输出 “注入成功!”,记事本会弹出 “DLL 注入成功!” 的消息框,说明注入完成。

三、方案 2:进程创建时注入(通过修改启动参数)

原理

利用
CreateProcess
函数创建目标进程时,通过
LPSTARTUPINFO
结构体的
hStdInput
/
hStdOutput
等句柄,强制目标进程加载指定 DLL(适用于 “从启动开始就监控目标进程” 的场景)。

核心代码(注入器)



// CreateProcessInject.cpp
#include <windows.h>
#include <tchar.h>
 
BOOL CreateProcessInject(LPCTSTR lpExePath, LPCTSTR lpDllPath) {
    // 1. 构造“加载DLL”的启动参数(通过环境变量传递DLL路径)
    TCHAR szEnv[1024] = {0};
    _stprintf_s(szEnv, TEXT("INJECT_DLL=%s"), lpDllPath);
    if (!SetEnvironmentVariable(TEXT("INJECT_DLL"), szEnv)) {
        _tprintf(TEXT("设置环境变量失败,错误码:%d
"), GetLastError());
        return FALSE;
    }
 
    // 2. 创建目标进程(挂起模式)
    STARTUPINFO si = { sizeof(STARTUPINFO) };
    PROCESS_INFORMATION pi = {0};
    BOOL bCreate = CreateProcess(
        lpExePath,              // 目标进程路径(如“C:\Windows\notepad.exe”)
        NULL,                   // 命令行参数(默认)
        NULL,                   // 进程安全属性
        NULL,                   // 线程安全属性
        TRUE,                   // 继承句柄
        CREATE_SUSPENDED,       // 挂起进程(注入完成后再恢复)
        NULL,                   // 继承父进程环境变量
        NULL,                   // 当前目录
        &si,
        &pi
    );
    if (!bCreate) {
        _tprintf(TEXT("创建进程失败,错误码:%d
"), GetLastError());
        return FALSE;
    }
 
    // 3. 在目标进程中申请内存并写入DLL路径(逻辑同方案1的步骤2-3)
    DWORD dwDllPathSize = (_tcslen(lpDllPath) + 1) * sizeof(TCHAR);
    LPVOID lpRemoteAddr = VirtualAllocEx(pi.hProcess, NULL, dwDllPathSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (lpRemoteAddr == NULL) {
        _tprintf(TEXT("申请内存失败,错误码:%d
"), GetLastError());
        TerminateProcess(pi.hProcess, 0);
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
        return FALSE;
    }
 
    WriteProcessMemory(pi.hProcess, lpRemoteAddr, lpDllPath, dwDllPathSize, NULL);
 
    // 4. 创建远程线程加载DLL(逻辑同方案1的步骤4-5)
    LPVOID lpLoadLibAddr = (LPVOID)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryW");
    HANDLE hRemoteThread = CreateRemoteThread(pi.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpLoadLibAddr, lpRemoteAddr, 0, NULL);
    if (hRemoteThread == NULL) {
        _tprintf(TEXT("创建线程失败,错误码:%d
"), GetLastError());
        VirtualFreeEx(pi.hProcess, lpRemoteAddr, 0, MEM_RELEASE);
        TerminateProcess(pi.hProcess, 0);
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
        return FALSE;
    }
 
    // 5. 等待线程完成,恢复目标进程
    WaitForSingleObject(hRemoteThread, INFINITE);
    ResumeThread(pi.hThread); // 恢复进程运行
 
    // 6. 释放资源
    CloseHandle(hRemoteThread);
    VirtualFreeEx(pi.hProcess, lpRemoteAddr, 0, MEM_RELEASE);
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
 
    _tprintf(TEXT("进程创建时注入成功!
"));
    return TRUE;
}
 
// 主函数
int _tmain() {
    LPCTSTR lpExePath = TEXT("C:\Windows\notepad.exe"); // 目标进程路径
    LPCTSTR lpDllPath = TEXT("C:\Tools\InjectDll.dll");  // 你的DLL路径
    CreateProcessInject(lpExePath, lpDllPath);
    system("pause");
    return 0;
}

操作步骤

无需提前启动目标进程,直接运行注入器。注入器会自动创建目标进程(如记事本),并在进程启动时加载 DLL,适用于监控进程初始化过程的场景。

四、方案 3:注册表注入(开机 / 进程启动自动注入)

原理

利用 Windows 注册表的
AppInit_DLLs
键值,让系统在加载
user32.dll
时(几乎所有带 UI 的进程都会加载
user32.dll
),自动加载该键值中配置的 DLL。适用于全局注入(所有进程启动时自动加载),但需管理员权限。

操作步骤(无需编写注入器,仅需配置注册表)

步骤 1:准备 DLL

使用方案 1 中的
InjectDll.dll
(确保 DLL 能兼容大多数进程,避免崩溃)。

步骤 2:修改注册表(需管理员权限)

按下
Win+R
,输入
regedit
打开注册表编辑器。定位到以下路径(32 位系统与 64 位系统路径不同):
32 位系统:
HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionWindows
64 位系统:
HKEY_LOCAL_MACHINESOFTWAREWow6432NodeMicrosoftWindows NTCurrentVersionWindows
(32 位 DLL);
HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionWindows
(64 位 DLL) 找到
AppInit_DLLs
键值(类型为
REG_SZ
),双击修改其值为你的 DLL 完整路径(如
C:ToolsInjectDll.dll
)。找到
LoadAppInit_DLLs
键值,确保其值为
1
(启用自动加载)。重启电脑或重启目标进程,进程启动时会自动加载 DLL。

步骤 3:恢复注册表(避免全局影响)

测试完成后,需将
AppInit_DLLs
值清空,
LoadAppInit_DLLs
设为
0
,否则会导致所有进程都加载你的 DLL,可能引发系统不稳定。

五、关键注意事项(避坑指南)

位数一致性:DLL、注入器、目标进程三者的位数必须一致(32 位→32 位,64 位→64 位),否则注入会失败(错误码通常为
ERROR_BAD_EXE_FORMAT
)。权限问题
注入系统进程(如
svchost.exe

explorer.exe
)需以管理员身份运行注入器,否则
OpenProcess
会失败(错误码
ERROR_ACCESS_DENIED
)。Windows 10/11 的 “用户账户控制(UAC)” 会阻止普通权限进程注入高权限进程,需关闭 UAC 或提升注入器权限。 DLL 稳定性

DllMain
中禁止执行复杂逻辑(如创建窗口、循环、调用耗时 API),否则会导致目标进程卡死或崩溃。避免在 DLL 中使用全局变量(多线程可能冲突),如需共享数据需用线程安全的结构(如
CriticalSection
)。 杀毒软件拦截:部分杀毒软件会将 DLL 注入判定为恶意行为,测试时需临时关闭杀毒软件,或添加信任。注入后验证:可通过 “任务管理器→详细信息→右键目标进程→打开文件位置→查看是否加载你的 DLL”,或使用
Process Explorer
工具查看进程的模块列表。

六、工具推荐(辅助调试)

Process Explorer:查看进程加载的 DLL 列表,验证注入是否成功(下载地址:微软官网)。OllyDbg/x64dbg:调试目标进程,查看 DLL 的
DllMain
是否正常执行(新手推荐 x64dbg,支持 32/64 位)。Dependency Walker:检查 DLL 的依赖项(如是否缺少
kernel32.dll

user32.dll
等系统库),避免 DLL 加载失败。

通过以上方案,你可以快速实现 DLL 注入并验证效果。建议从 “远程线程注入” 开始练习,熟悉后再尝试其他方案。实际应用中需遵守法律法规,不得用于未经授权的进程操作。

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...