爲何要把DLL搜索路徑(DLL ORDER)和DLL劫持(DLL Hajack)拿到一塊兒講呢?呵呵,其實沒啥深意,僅僅是兩者有因果關係而已。能夠講正是由於Windows系統下面DLL的搜索路徑存在的漏洞纔有了後來的一段時間的DLL劫持大肆流行。ios
最近(其實不是最近,哈,是之前分析過,斷斷續續的……)簡單分析了一個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搜索模式):網絡
注:在vista和win7下沒有作過實驗,有興趣的能夠本身作作實驗。app
注:上面說到經過註冊表開啓是指將HKLM\System\CurrentControlSet\Control\Session Manager鍵值下的屬性SafeDllSearchMode的值設置爲1(若是沒有SafeDllSearchMode就本身手動建立)。函數
在安全DLL搜索模式開啓的狀況下,搜索順序是:測試
一、應用程序EXE所在的路徑。編碼
二、系統目錄。spa
三、16位系統目錄插件
四、Windows目錄
五、當前目錄
六、PATH環境變量指定的目錄
若是安全DLL搜索模式不支持或者被禁用,那麼搜索順序是:
一、應用程序EXE所在的路徑。
二、當前目錄
三、系統目錄。
四、16位系統目錄
五、Windows目錄
六、PATH環境變量指定的目錄
說了這麼多?咱們怎麼校驗本身的系統的DLL的搜索順序呢?實際上是很簡單的,咱們首先構造兩個簡單的程序,一個DLL程序一個EXE程序,代碼很簡單,以下:
DLL程序:
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:
break;
case DLL_PROCESS_DETACH:
break;
default:
break;
}
return TRUE;
}
EXE程序:
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
實驗結果如圖:
根據結果,我想已經很明確了,個人系統是啓用了安全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劫持的利用,劫持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文件導出):
// 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)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
char szDllPath[MAX_PATH] = {0};
GetModuleFileNameA(hModule, szDllPath, MAX_PATH);
cout << "ORC DLL - DLL PATH: " << szDllPath << endl;
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
default:
break;
}
return TRUE;
}
int Add(int a, int b)
{
return a + b;
}
咱們的應用程序的代碼以下(名字是DLLHijackApp.exe,加載這個DLL,而後調用它的Add函數):
// DLLHijackApp.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
SetDllDirectoryW(TEXT(""));
HMODULE hDll = LoadLibrary(TEXT("DLLHijackSO.dll"));
if (!hDll)
{
cout << "DLLHijackSO.dll Load Faild!" << endl;
}
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;
return 0;
}
咱們先測試一下,把DLLHijackSO.dll放入system32下,而後應用程序DLLHijackApp.exe放在任意位置,運行結果以下:嗯,是沒有問題的。
如今咱們寫劫持DLL,其實也很簡單,它編譯出來的DLL名字也是DLLHijackSO.dll,也經過def文件導出了Add函數:
// DLLHijackHijack.cpp : Defines the entry point for the DLL application.
//
#include "stdafx.h"
#include <iostream>
#include <tchar.h>
using namespace std;
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)
{
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
char szDllPath[MAX_PATH] = {0};
GetModuleFileNameA(hModule, szDllPath, MAX_PATH);
cout << "DLL Hijack - DLL PATH: " << szDllPath << endl;
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
DLLHijackName::Free();
break;
default:
break;
}
return TRUE;
}
typedef int (*PFUNADD)(int , int );
int Add(int a, int b)
{
cout << "DLL Hijack - Add(int a, int b)" << endl;
PFUNADD pfnAdd = (PFUNADD)DLLHijackName::GetOrgAddress("Add");
if (pfnAdd)
{
return pfnAdd(a, b);
}
return 0;
}
好,如今咱們把這個劫持的DLLHijackSO.dll放在DLLHijackApp.exe同目錄下,運行:
劫持成功!
固然,咱們的重點仍是要放在避免咱們編寫的軟件被DLL劫持,通常有如下一些針對DLL劫持的安全編碼的規範(其實你們也應該能夠從上述的DLL劫持的原理本身總結出來)::
1)調用LoadLibrary,LoadLibraryEx,CreateProcess,ShellExecute等等會進行模塊加載操做的函數時,指明模塊的完整(全)路徑,禁止使用相對路徑(這樣基本就能夠防死上面所講的第二種DLL劫持了)。
2)在應用程序的開頭調用SetDllDirectory(TEXT(""));從而將當前目錄從DLL的搜索列表中刪除,也就是搜索時不搜索當前目錄。
3)打上最新的系統補丁,確保安全DLL搜索模式是開啓狀態。
4)對於安全軟件來說要確保用戶的機器上面的KnownDLLs下是完整的。
5)DLL的重定向等須要注意的問題。---------------------