DLL搜尋路徑和DLL劫持
環境:XP SP3 VS2005
作者:magictong
為什麼要把DLL搜尋路徑(DLL ORDER)和DLL劫持(DLL Hajack)拿到一起講呢?呵呵,其實沒啥深意,僅僅是二者有因果關系而已。可以講正是因為Windows系統下面DLL的搜尋路徑存在的漏洞才有了後來的一段時間的DLL劫持大肆流行。
最近(其實不是最近,哈,是以前分析過,斷斷續續的……)簡單分析了一個DLL劫持下載下傳者的行為,感覺有必要寫點東西說明一下。其實DLL劫持是比較好預防的,從程式設計規範上我們可以進行規避(後面會專門講到),從實時防護的角度來講我們也可以想出一些辦法進行攔截。新的DLL劫持者基本都是通過目前路徑來入侵,一些老的DLL劫持者一般都是通過exe的安裝目錄來入侵的,為什麼會這樣,後面還會講到。
要搞清DLL劫持的原理,首先要搞清DLL搜尋路徑,到哪去搞清?當然是問微軟啦!MSDN上面有一篇專門講DLL搜尋順序的文章(Dynamic-Link Library Search Order http://msdn.microsoft.com/en-us/library/ms682586%28VS.85%29.aspx),雖然是英文的但是并不複雜講得很清楚,大家如果對這塊興趣大的話可以仔細研讀下,我就不翻譯了。
Dynamic-Link Library Search Order裡面主要講到一個安全DLL搜尋模式的問題,大家可以通過下面的表格來看一下不同系統對安全DLL搜尋模式的支援情況(下表中用SDS代表安全DLL搜尋模式):
系統
Win2k
Win2kSP4
XP
XPSP2
XPSP3
是否支援SDS
不支援
支援
SDS是否預設開啟
不适用
否
是
SDS是否可以通過系統資料庫開啟
是否支援SetDllDirectory
注:在vista和win7下沒有做過實驗,有興趣的可以自己做做實驗。
注:上面說到通過系統資料庫開啟是指将HKLM\System\CurrentControlSet\Control\Session Manager鍵值下的屬性SafeDllSearchMode的值設定為1(如果沒有SafeDllSearchMode就自己手動建立)。
在安全DLL搜尋模式開啟的情況下,搜尋順序是:
1、應用程式EXE所在的路徑。
2、系統目錄。
3、16位系統目錄
4、Windows目錄
5、目前目錄
6、PATH環境變量指定的目錄
如果安全DLL搜尋模式不支援或者被禁用,那麼搜尋順序是:
2、目前目錄
3、系統目錄。
4、16位系統目錄
5、Windows目錄
說了這麼多?我們怎麼校驗自己的系統的DLL的搜尋順序呢?其實是很簡單的,我們首先構造兩個簡單的程式,一個DLL程式一個EXE程式,代碼很簡單,如下:
DLL程式:
<span style="font-size:16px;">BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
char szDllPath[MAX_PATH] = {0};
GetModuleFileNameA(hModule, szDllPath, MAX_PATH);
cout << "DLL PATH: " << szDllPath << endl;
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
default:
}
return TRUE;
}</span>
EXE程式:
<span style="font-size:16px;">int _tmain(int argc, _TCHAR* argv[])
HMODULE hDll = LoadLibrary(TEXT("DLLHijackSO.dll"));
if (!hDll)
cout << "DLLHijackSO.dll Load Faild!" << endl;
return 0;
夠簡單了吧,思路也很明确,在DLL的DLLMAIN裡面會列印出DLL所在的路徑,在EXE程式裡面通過DLL的名字(不是全路徑)去加載這個DLL,如果加載失敗會列印出一條加載失敗資訊。然後根據上面提到的6個地方,分别放一個DLL程式編譯出來的DLL(我起的名字是DLLHijackSO.dll),EXE編譯出來的DLLHijackApp.exe是放在H:\Prj_N\DLLHijackSO\Release裡面的,然後把cmd啟動,cmd啟動之後,它的目前路徑一般都是設定的使用者目錄,譬如我的機器上面就是C:\Documents and Settings\magictong,通過CD指令對目前檔案夾的切換,目前路徑也随之改變。實驗的基本過程,因為整個系統放置了6個DLLHijackSO.dll,每運作DLLHijackApp.exe一次,如果成功加載DLL,那麼就把加載的那個DLL删除,持續進行,直到加載失敗。好,實驗開始……
我的系統的六個位置(其中最後一個PATH變量指定的路徑,你選取一個就可以了):
H:\Prj_N\DLLHijackSO\Release
C:\WINDOWS\system32
C:\WINDOWS\system
C:\WINDOWS
C:\Documents and Settings\magictong
C:\Python25
實驗結果如圖:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuEzYwEzNkFTN4AzM3Y2NmNjMyEDN3EzM4MGNiVzM0IDOfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
根據結果,我想已經很明确了,我的系統是啟用了安全DLL搜尋模式的,因為我的系統是XPSP3。另外就是關于目前路徑的問題,其實目前路徑是可以由程序的父程序來設定的,大家可以去看CreateProcess裡面的參數,有一項是設定目前路徑的,也就是為什麼CMD啟動你的程序的時候,目前路徑會在“你想不到的地方”,explorer啟動程序則是把目前路徑設定為應用程式所在的路徑(目前路徑可以通過GetCurrentDirectory這個API來擷取)。
我想如果DLL搜尋路徑搞清楚了,DLL劫持的原理就很簡單了。個人覺得一兩句話就可以說清楚:在進行DLL加載時,如果不是絕對路勁,系統會按照DLL的搜尋路徑依次進行目标DLL的搜尋直到找到目标DLL或者加載失敗,如果你在真實的DLL被找到之前的路徑放入你的劫持(同名)DLL,那麼應用程式将先加載到你的DLL,這樣就是DLL劫持的過程。
原理雖然簡單,你的劫持DLL的選取和編寫則要有些技巧,不是所有的DLL都可以被劫持的,有些DLL是受系統保護的,譬如user32.dll等,這些是不能劫持的。在一些老的DLL劫持病毒裡面一般是選取usp10.dll,lpk.dll等,原因很簡單,一般的應用程式都會加載它們,而且沒有被系統保護(好不好用,誰用誰知道,我反正沒用過)。
首先簡單總結下DLL劫持和DLL注入的差別:
DLL劫持
DLL注入
主動性
被動,等待目标運作
主動,目标必須已經在運作
是否需要跳闆
不需要
需要,由第三方來幫助注入
是否容易攔截
不容易
容易
是否容易免疫
DLL的編寫要求
僞造真實DLL一樣的導出函數表
可以按自己的流程寫導出函數
下面講一下新老兩種DLL劫持的攻擊原理和防禦方案:
之前提到過一種老的DLL劫持的利用,劫持usp10.dll,lpk.dll等等。這些DLL的實際目錄在system32下,病毒利用DLL的搜尋排名第一的是應用程式自身所在的目錄,釋放同名的劫持DLL到應用程式目錄下,這樣應用程式啟動時就會先加載了劫持DLL,達到不可告人目的。應用程式加載了劫持DLL之後又有兩種後續的攻擊方案,一種是轉發調用到正常的DLL,使應用程式毫無覺察,同時秘密在背景下載下傳更多的下載下傳器木馬等。另一種就是直接破壞造成程式無法運作,這種主要用于幹掉殺軟等安全軟體。
通用免疫方案:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs]在此系統資料庫項下定義一個“已知DLL名稱”,那麼凡是此項下的DLL檔案就會被禁止從EXE自身目錄下調用,而隻能從系統目錄,也就是system32目錄下調用。根據這個方案是不是可以寫一個簡單的DLL劫持免疫器了呢?當然對于一個安全軟體來說,這個地方也是要保護的地方之一。下圖是我機器上面該鍵值下面的一些DLL名稱:
之前看過講微點的一篇文章,講的是對這種老的DLL劫持實行的一種攔截方案:如果發現一個系統下的敏感DLL被加載,則通過堆棧回溯找到該敏感DLL的加載者的位址,如果是在一個同名的DLL裡面則認為是被DLL劫持了,報告發現病毒。從對抗的角度來講,這種方案很容被過掉,隻要修改棧幀指針可能就發現不了了。
新的DLL劫持的攻擊原理和防禦方案:
很多應用程式都是支援插件和擴充的,尤其是一些播放器軟體,支援解碼插件,第一次安裝的時候可能隻裝了常見的一些音視訊解碼插件,在遇到一些特殊的音視訊格式時就需要實時去網絡上拖取一個解碼插件下來進行解碼操作,當然軟體會首先嘗試加載這個解碼插件(通常是一個DLL),這個時候一些設計有缺陷的産品(譬如不是通過絕對路徑加載插件)在加載DLL時就會搜尋上面提到過的各個路徑。一般這種情況下,惡意攻擊者會在網絡上提供一些使用者感興趣名字的視訊圖檔神馬的,使用者下載下傳壓縮包解壓後,其實壓縮檔案中包含着兩個以上的檔案,使用者很難發現,解壓後,劫持DLL和視訊或者圖檔檔案是放在同一個目錄的,當然劫持DLL檔案的屬性是系統隐藏,然後使用者高高興興的去輕按兩下那個視訊或者圖檔檔案,杯具發生了……這實際上是利用的DLL搜尋時會搜尋目前目錄這個特點來進行DLL劫持的,為什麼目前目錄是視訊圖檔檔案存放的目錄呢?
這個可以做個實驗,與檔案關聯有關,在系統資料庫裡面注冊一個._magic字尾的檔案類型,打開這種檔案的應用程式是c盤下的DLLHijackApp.exe(檔案關聯這塊有興趣的自己可以查資料,因為與本文關系不大就不細講了,見下圖),DLLHijackApp.exe的作用就是彈出一個MessageBox打出目前目錄,如果直接輕按兩下運作DLLHijackApp.exe,目前目錄就是c盤,如果在桌面上建立一個x._magic檔案再輕按兩下運作,列印出的目前目錄則是桌面目錄(也就是x._magic檔案所在的目錄,見下面的圖)。
直接輕按兩下運作C槽下的DLLHijackApp.exe,目前目錄:
輕按兩下打開桌面上的x._magic的檔案,目前目錄:
現在大家應該明白為什麼目前目錄是檔案所在的目錄了吧。病毒正是利用這一點,把劫持DLL和音視訊,圖檔檔案捆綁在一起下載下傳,達到入侵的目的。
防禦方案:
暫無通用的防禦方案,因為劫持的都是一些第三方的DLL,暫時隻能通過下載下傳保護之類的途徑進行保護(這類攻擊最終還是會轉去下載下傳更多的盜号木馬或者下載下傳器之類的,然後進行一些盜号、破壞等等的事情)。
講了這麼多,來看一個DLL劫持的執行個體,是簡單寫的一個說明原理的小例子:
劫持DLL要保證應用程式運作正常,不被使用者發現,除了和原來的DLL有相同的名字之外還需要導出和原DLL一樣的函數。我們現在已經有了一個名字是DLLHijackSO.dll的DLL,他導出一個Add函數,這個函數原型是int Add(int a, int b),很簡單吧。假設這個DLL是一個系統DLL,是放在system32目錄下。
原DLL的代碼如下(Add函數通過def檔案導出):
<span style="font-size:16px;">// DLLHijackSO.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#include <iostream>
using namespace std;
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
cout << "ORC DLL - DLL PATH: " << szDllPath << endl;
}
int Add(int a, int b)
return a + b;
我們的應用程式的代碼如下(名字是DLLHijackApp.exe,加載這個DLL,然後調用它的Add函數):
<span style="font-size:16px;">// DLLHijackApp.cpp : Defines the entry point for the console application.
#include <Windows.h>
int _tmain(int argc, _TCHAR* argv[])
SetDllDirectoryW(TEXT(""));
else
typedef int (*PFUNADD)(int , int );
cout << "App - Add(int a, int b)" << endl;
HMODULE hMod = LoadLibrary(TEXT("DLLHijackSO.dll"));
if (hMod)
PFUNADD pfnAdd = (PFUNADD)GetProcAddress(hMod, "Add");
cout << "App 1022 + 1022 = " << pfnAdd(1022, 1022) << endl;
if (hDll)
FreeLibrary(hDll);
hDll = NULL;
char szCurrentDir[MAX_PATH] = {0};
GetCurrentDirectoryA(MAX_PATH, szCurrentDir);
MessageBoxA(NULL, szCurrentDir, "目前目錄", MB_OK);
cout << "CUR PATH: " << szCurrentDir << endl;
我們先測試一下,把DLLHijackSO.dll放入system32下,然後應用程式DLLHijackApp.exe放在任意位置,運作結果如下:嗯,是沒有問題的。
現在我們寫劫持DLL,其實也很簡單,它編譯出來的DLL名字也是DLLHijackSO.dll,也通過def檔案導出了Add函數:
<span style="font-size:16px;">// DLLHijackHijack.cpp : Defines the entry point for the DLL application.
#include <tchar.h>
namespace DLLHijackName
HMODULE m_hModule = NULL; //原始子產品句柄
// 加載原始子產品
inline BOOL WINAPI Load()
TCHAR tzPath[MAX_PATH] = {0};
GetSystemDirectory(tzPath, MAX_PATH);
_tcsncat_s(tzPath, MAX_PATH, TEXT("\\DLLHijackSO.dll"), _TRUNCATE);
m_hModule = LoadLibrary(tzPath);
if (!m_hModule)
cout << "無法加載DLLHijackSO.dll,程式無法正常運作。" << endl;
return (m_hModule != NULL);
// 釋放原始子產品
inline VOID WINAPI Free()
if (m_hModule)
FreeLibrary(m_hModule);
// 擷取原始函數位址
FARPROC WINAPI GetOrgAddress(PCSTR pszProcName)
FARPROC fpAddress;
if (m_hModule == NULL)
if (Load() == FALSE)
ExitProcess(-1);
fpAddress = GetProcAddress(m_hModule, pszProcName);
if (!fpAddress)
cout << "無法找到函數,程式無法正常運作。" << endl;
ExitProcess(-2);
return fpAddress;
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
cout << "DLL Hijack - DLL PATH: " << szDllPath << endl;
DLLHijackName::Free();
typedef int (*PFUNADD)(int , int );
cout << "DLL Hijack - Add(int a, int b)" << endl;
PFUNADD pfnAdd = (PFUNADD)DLLHijackName::GetOrgAddress("Add");
if (pfnAdd)
return pfnAdd(a, b);
好,現在我們把這個劫持的DLLHijackSO.dll放在DLLHijackApp.exe同目錄下,運作:
劫持成功!
當然,我們的重點還是要放在避免我們編寫的軟體被DLL劫持,一般有以下一些針對DLL劫持的安全編碼的規範(其實大家也應該可以從上述的DLL劫持的原理自己總結出來)::
1)調用LoadLibrary,LoadLibraryEx,CreateProcess,ShellExecute等等會進行子產品加載操作的函數時,指明子產品的完整(全)路徑,禁止使用相對路徑(這樣基本就可以防死上面所講的第二種DLL劫持了)。
2)在應用程式的開頭調用SetDllDirectory(TEXT(""));進而将目前目錄從DLL的搜尋清單中删除,也就是搜尋時不搜尋目前目錄。
3)打上最新的系統更新檔,確定安全DLL搜尋模式是開啟狀态。
4)對于安全軟體來講要確定使用者的機器上面的KnownDLLs下是完整的。
5)DLL的重定向等需要注意的問題。
<a href="http://download.csdn.net/detail/magictong/3750926" target="_blank">【DLL搜尋路徑和DLL劫持 示範源代碼下載下傳】</a>