基础概念
- 注入定义:在第三方进程不知情 / 未授权的前提下,把代码 / 模块写入目标进程虚拟地址空间并执行
安全领域:注入与反注入持续对抗
常见注入:远程线程注入、APC 注入、钩子注入、输入法注入、导入表注入 - 远程线程注入核心思路 不直接在目标进程执行自定义函数,远程线程入口设为LoadLibraryA; 在目标进程内存存放 DLL 路径字符串,将字符串地址作为LoadLibraryA参数
原因:LoadLibraryA原型刚好匹配线程函数格式:HMODULE LoadLibraryA(LPCTSTR lpFileName),1 个 4 字节参数、返回 4 字节返回值,满足CreateRemoteThread入口要求
关键点:所有进程的kernel32.dll基地址一致,LoadLibraryA地址在各个进程相同
完整注入步骤(重点)
- OpenProcess:打开目标进程,获取进程句柄,需要PROCESS_ALL_ACCESS(或对应分项权限),传入目标 PID
- VirtualAllocEx:在目标进程虚拟空间分配内存,PAGE_READWRITE,用来存放 DLL 完整路径(内存大小 = strlen+1,预留\0结束符)
- WriteProcessMemory:跨进程写内存,把 DLL 路径字符串写入刚才分配的远程内存
- GetModuleHandleA("kernel32.dll") + GetProcAddress:拿到LoadLibraryA地址
- CreateRemoteThread:创建远程线程
○ 入口地址:LoadLibraryA
○ 线程参数:远程内存中 DLL 路径字符串的地址 - 远程线程执行LoadLibraryA → 目标进程加载 DLL,触发DllMain
- 资源释放:关闭线程句柄、进程句柄
DLL 端逻辑(DllMain)
c
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
//DLL被加载进来,新建线程持续执行代码
CreateThread(NULL,0,ThreadFunc,NULL,0,NULL);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
重要:CreateRemoteThread创建的远程线程执行完 LoadLibrary 就直接结束,如果要长时间运行代码,不能在 DllMain 里面写死循环!
规范做法:DLL_PROCESS_ATTACH 里创建新线程,业务代码放到新线程
用到的核心 API 清单
| API | 作用 |
|---|---|
| OpenProcess | 打开目标进程,拿到 hProcess |
| VirtualAllocEx | 在别的进程分配虚拟内存 (VirtualAlloc:仅本进程) |
| WriteProcessMemory | 向目标进程内存写入数据 |
| GetProcAddress | 获取 LoadLibraryA 函数地址 |
| CreateRemoteThread | 在目标进程创建远程线程 |
易错点 & 限制
- DLL 路径:必须使用完整绝对路径;相对路径容易加载失败
- 位数匹配:32 位注入器只能注入 32 位目标进程;64 位注入器只能注入 64 位目标,32/64 不能交叉注入
- 权限:OpenProcess 权限不足直接失败;高版本 Windows 有注入防护
- 内存属性:VirtualAllocEx 分配 PAGE_READWRITE;如果后续需要执行代码才改成 PAGE_EXECUTE_READWRITE(本案例只存字符串,读写即可)
- DllMain禁止写死循环代码,会导致进程卡死,要新建子线程
最简注入器代码骨架
c
#include <windows.h>
#include <stdio.h>
int InjectDll(DWORD dwPID, const char* szDllPath)
{
//1.打开目标进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
if (hProcess == NULL)
{
printf("OpenProcess fail\n");
return 0;
}
//2.远程分配内存
LPVOID pRemoteMem = VirtualAllocEx(hProcess, NULL, strlen(szDllPath)+1, MEM_COMMIT, PAGE_READWRITE);
//3.写入DLL路径字符串
WriteProcessMemory(hProcess, pRemoteMem, szDllPath, strlen(szDllPath)+1, NULL);
//4.获取LoadLibraryA地址
LPVOID pLoadLib = (LPVOID)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
//5.创建远程线程
HANDLE hRemoteThd = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLib, pRemoteMem,0,NULL);
WaitForSingleObject(hRemoteThd, INFINITE);
//资源释放
CloseHandle(hRemoteThd);
CloseHandle(hProcess);
return 1;
}
int main()
{
InjectDll(1048, "C:\\TestDll.dll");
getchar();
return 0;
}
总结
- 远程线程注入本质:CreateRemoteThread在目标进程调用LoadLibraryA加载 DLL
- 4 个核心跨进程 API:OpenProcess、VirtualAllocEx、WriteProcessMemory、CreateRemoteThread
- LoadLibrary 地址所有进程相同,所以不用在目标进程内解析
- 远程线程只负责加载 DLL;DLL 要持续运行代码,必须在 DllMain 里新建线程
- 注入前置条件:目标进程正在运行,注入程序拥有足够权限,位数一致