1.概覽
.構(gòu)造DLL
(1)僅導(dǎo)出函數(shù)
DLL可以導(dǎo)出全局變量和類,但我們不建議這么做,建議導(dǎo)出函數(shù)。
(2).lib
每個(gè)DLL都有與之相對(duì)應(yīng)的.lib文件,該文件中列出了DLL中導(dǎo)出的函數(shù)和變量的符號(hào)名
(3)指定要導(dǎo)出的函數(shù)名
因?yàn)椴煌幾g器的Name mangle規(guī)則不同,這就導(dǎo)致DLL不能跨編譯器使用。
有以下兩種方法可以解決這個(gè)問題:
1.在.def文件中指定要導(dǎo)出的函數(shù)名
2.在編譯指中指定要導(dǎo)出的函數(shù)名:
#pragma comment(linker, "/export:MyFunc=_MyFunc@8")
.DLL加載路徑
當(dāng)需要加載一個(gè)DLL時(shí),系統(tǒng)會(huì)依照下面的順序去尋找所需DLL直到找到為止,然后加載,否則加載失敗。
(1)當(dāng)前可執(zhí)行文件路徑
(2)GetWindowsDirectory返回的Windows系統(tǒng)路徑
(3)16位系統(tǒng)的路徑 windows"system
(4)GetSystemDirectory返回的Windows系統(tǒng)路徑
(5)當(dāng)前進(jìn)程所在路徑
(6)PATH環(huán)境中所指定的路徑
.創(chuàng)建\使用動(dòng)態(tài)鏈接庫
首先必須創(chuàng)建一個(gè)包含需要導(dǎo)出的符號(hào)的頭文件,以便其他程序鏈接到該dll上:
// dllexample.h
#ifdef DLLEXAMPLE_EXPORTS // 在編譯命令中已定義,所以實(shí)際用的是 __declspec(dllexport)
#define DLLEXAMPLE_API __declspec(dllexport)
#else
#define DLLEXAMPLE_API __declspec(dllimport)
#endif
DLLEXAMPLE_API int fnDllexample(void);
當(dāng)其他應(yīng)用包含該頭文件,意圖使用該dll的導(dǎo)出符號(hào)時(shí),因?yàn)闆]有定義DLLEXAMPLE_EXPORTS,所以使用的是__declspec(dllimport),這樣編譯器編譯時(shí)便知道這是從外部引入的函數(shù)。在鏈接時(shí),鏈接程序?qū)⑸蓪?dǎo)入表(ImportAddressTable),該表羅列了所有調(diào)用到的函數(shù),以及一個(gè)空白的對(duì)應(yīng)地址。在程序執(zhí)行時(shí),加載器將動(dòng)態(tài)的填入每個(gè)函數(shù)符號(hào)在本進(jìn)程中的地址,使得程序能正確的調(diào)用到dll中的函數(shù)上。
這種通過dll提供的.h和.lib文件進(jìn)行鏈接dll的使用方式,稱為隱式鏈接。用vc開發(fā)程序時(shí),幾乎所有的系統(tǒng)API調(diào)用都用了隱式鏈接。
.顯式鏈接
在exe創(chuàng)建時(shí)不引用.lib文件中的符號(hào),當(dāng)然也不必包含.h頭文件,而是由程序調(diào)用LoadLibrary(Ex)以及GetProcAddress函數(shù)來獲取每個(gè)需要使用的函數(shù)地址,從而進(jìn)行dll中的函數(shù)調(diào)用,這種dll使用方法稱為顯式鏈接。顯式鏈接時(shí)不生成對(duì)應(yīng)dll的IAT.
當(dāng)決定不再使用該dll時(shí),通過調(diào)用FreeLibrary來卸載。需要注意的是,同一個(gè)進(jìn)程中共計(jì)調(diào)用LoadLibrary的次數(shù)要和調(diào)用FreeLibrary的次數(shù)相等,因?yàn)橄到y(tǒng)維護(hù)了一個(gè)使用計(jì)數(shù),當(dāng)計(jì)數(shù)為0時(shí),才會(huì)真正的卸載該dll.
如果想確認(rèn)一個(gè)dll是否已經(jīng)被映射到進(jìn)程空間中,盡量使用GetModuleHandle,最好不要冒然使用LoadLibrary(Ex).
GetProcAddress可以傳遞函數(shù)名或者序號(hào)(通過MAKEINTRESOURCE(2)來"制作"序號(hào)).
1.1動(dòng)態(tài)加載DLL文件 LoadLibraryEx
HMODULE LoadLibraryEx( //返回DLL加載到進(jìn)程空間原首地址。
PCTSTR pszDLLPathName,
HANDLE hFile,
DWORD dwFlags);
dwFlags 可以有以下幾個(gè)值
(1) DONT_RESOLVE_DLL_REFERENCES
建議永遠(yuǎn)不要使有這個(gè)值,它的存在僅僅是為了向后兼容、
(2) LOAD_LIBRARY_AS_DATAFILE
把要加載的DLL文件以數(shù)據(jù)文件的形式加載到進(jìn)程中。
GetModuleHandle和GetProcAddress返回NULL
(3) LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE
與前者相同,不同的時(shí)獨(dú)占打開,禁止其它進(jìn)程訪問和修改該DLL中的內(nèi)容。
(4) LOAD_LIBRARY_AS_IMAGE_RESOURCE
不修改DLL中的RVA,以image的形式加載到進(jìn)程中。常與LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE一起使用。
(5) LOAD_WITH_ALTERED_SEARCH_PATH
修改DLL的加載路徑
1.2 DLL的加載與卸載
(1)加載
不要在同一進(jìn)程中,同時(shí)使用LoadLIbrary和LoadLibraryEx加載同一DLL文件。
DLL的引用計(jì)數(shù)是以進(jìn)程為單位的。LoadLibrary會(huì)把DLL文件加載到內(nèi)存,然后映射到進(jìn)程空間中。
多次加載同一DLL只會(huì)增加引用計(jì)數(shù)而不會(huì)多次映射。當(dāng)所有進(jìn)程對(duì)DLL的引用計(jì)數(shù)都為0時(shí),系統(tǒng)會(huì)在內(nèi)存中釋放該DLL。
(2)卸載
FreeLibrary,FreeLibraryAndExitThread對(duì)當(dāng)前進(jìn)程的DLL的引用計(jì)數(shù)減1
(3) GetProcAddress
取得函數(shù)地址。它只接受ANSI字符串。
2.DLL的入口函數(shù)
2.1 DllMain
BOOL WINAPI DllMain(
HINSTANCE hInstDll, ""加載后在進(jìn)程中的虛擬地址
DWORD fdwReason, ""系統(tǒng)因何而調(diào)用該函數(shù)
PVOID fImpLoad ""查看是隱工還是動(dòng)態(tài)加載該DLL
DLLs用DllMain方法來初始化他們自已。DllMain中的代碼應(yīng)盡量簡單,只做一些簡單的初始化工作。
不要在DllMain中調(diào)用LoadLibrary,FreeLibrary及Shell, ODBC, COM, RPC, 和 socket 函數(shù),從而避免不可預(yù)期的錯(cuò)誤。
2.2 fdwReason的值
(1)DLL_PROCESS_ATTACH
系統(tǒng)在為每個(gè)進(jìn)程第一次加載該DLL時(shí)會(huì),執(zhí)行DLL_PROCESS_ATTACH后面的語句來初始化DLL,DllMain的返回值僅由它決定。
系統(tǒng)會(huì)忽略DLL_THREAD_ATTACH等執(zhí)行后DllMain的返回值。
如果DllMain返回FALSE,系統(tǒng)會(huì)自動(dòng)調(diào)用DLL_PROCESS_DETACH的代碼并解除DLL文件中進(jìn)程中的內(nèi)存映射。
(2)DLL_PROCESS_DETACH
如果DLL是因進(jìn)程終止而卸載其在進(jìn)程中的映射,那么負(fù)責(zé)調(diào)用ExitProcess的線程會(huì)調(diào)用DllMain中DLL_PROCESS_DETACH所對(duì)應(yīng)的代碼。
如果DLL是因FreeLibrary或FreeLibraryAndExitThread,而卸載其在進(jìn)程中的映射, 那么FreeLibrary或FreeLibraryAndExitThread會(huì)負(fù)責(zé)調(diào)用DllMain中DLL_PROCESS_DETACH所對(duì)應(yīng)的代碼。
如果DLL是因TerminateProcess而卸載其在進(jìn)程中的映射,系統(tǒng)不會(huì)調(diào)用DllMain中DLL_PROCESS_DETACH所對(duì)應(yīng)的代碼。
(3) DLL_THREAD_ATTACH
若進(jìn)程是先加載的DLL,后創(chuàng)建的線程
那么在進(jìn)程中創(chuàng)建新線程時(shí)(主線程除外),系統(tǒng)會(huì)執(zhí)行該進(jìn)程已載的所有DLL的DllMain中DLL_THREAD_ATTACH對(duì)應(yīng)的代碼。
若進(jìn)程是先創(chuàng)建的線程,后加載的DLL
那么系統(tǒng)不會(huì)調(diào)用DLL的DllMain中的代碼。
(4) DLL_THREAD_DETACH
進(jìn)程中的線程退出時(shí),會(huì)先執(zhí)行所有已加載DLL的DllMain中DLL_THREAD_DETACH所對(duì)應(yīng)的代碼。若該代碼中有死循環(huán),線程不會(huì)退出。
2.3 同步化DllMain的調(diào)用
同一時(shí)間只能有一個(gè)線程調(diào)用DllMain中的代碼,所以下面的代碼會(huì)導(dǎo)致死循環(huán)
BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, PVOID fImpLoad) {
HANDLE hThread;
DWORD dwThreadId;
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
// The DLL is being mapped into the process' address space.
// Create a thread to do some stuff.
hThread = CreateThread(NULL, 0, SomeFunction, NULL,
0, &dwThreadId);// CreateThread會(huì)DLL_THREAD_ATTACH中的代碼,但是由于當(dāng)前線程并未執(zhí)行完畢,
//所以DLL_THREAD_ATTACH 中的代碼不會(huì)被執(zhí)行,且CreateThread永無不會(huì)返回。
// Suspend our thread until the new thread terminates.
WaitForSingleObject(hThread, INFINITE);
// We no longer need access to the new thread.
CloseHandle(hThread);
break;
case DLL_THREAD_ATTACH:
// A thread is being created.
break;
case DLL_THREAD_DETACH:
// A thread is exiting cleanly.
break;
case DLL_PROCESS_DETACH:
// The DLL is being unmapped from the process' address space.
break;
}
return(TRUE);
}
3.延時(shí)加載DLL
(1)延時(shí)加載DLL的限制
延遲加載的D L L是個(gè)隱含鏈接的D L L,它實(shí)際上要等到你的代碼試圖引用D L L中包含的一個(gè)符號(hào)時(shí)才進(jìn)行加載,它與動(dòng)態(tài)加載不同。
4.已知的DLL (Known DLLs)
位置:HKEY_LOCAL_MACHINE"SYSTEM"CurrentControlSet"Control"Session Manager"KnownDLLs
LoadLibrary在查找DLL會(huì)先去該位置查找有無相應(yīng)的鍵值與DLL要對(duì)應(yīng),若有則根據(jù)鏈值去%SystemRoot%"System32加載鍵值對(duì)應(yīng)的DLL
若無則根據(jù)默認(rèn)規(guī)去尋找DLL
5.Bind and Rebase Module
它可以程序啟動(dòng)的速度。ReBaseImage
DLL 注入和API鉤(DLL Injection and API Hooking)
1.概覽
每個(gè)進(jìn)程都有自已獨(dú)立的地址空間,一個(gè)進(jìn)程不可能創(chuàng)建一個(gè)指向其它進(jìn)程地址空間的指針。
然而如果我們把自已的DLL注射到另一個(gè)進(jìn)程的地址空間去,我們就可以在那個(gè)被注入的進(jìn)程里為所欲為了。
2.用注冊(cè)表注入DLL
該方法適用于給GUI的程序注入DLL
所有的GUI應(yīng)用程序在啟動(dòng)時(shí)都會(huì)加載User32.dll,而在User32.dll的DLL_PROCESS_ATTACH代碼根據(jù)注冊(cè)表中的信息
來注入用戶指定的DLL
注冊(cè)表項(xiàng) HKEY_LOCAL_MACHINE"Software"Microsoft"Windows NT"CurrentVersion"Windows"
中有兩個(gè)值:
LoadAppInit_Dlls:鍵值中指定要注入的DLL 如:c:"inject.dll
AppInit_Dlls:若其鍵值為1,則注入LoadAppInit_Dlls中指定的DLL,否則若為0則不注入。
注:
(1)LoadAppInit_Dlls中的值是以空格或分號(hào)分隔的,所以DLL的路徑中最好不要有空格,最后不指定路徑,直接將DLL放到windows系統(tǒng)目錄中。
(2) 用注冊(cè)表注入DLL的方式有很大的局限性,Kernel32.dll和Ntdll.dll中有的函數(shù)才能調(diào)用
一.注入dll
1.通過注冊(cè)表項(xiàng) HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs 來指定你的dll的路徑,那么當(dāng)一個(gè)GUI程序啟動(dòng)時(shí)就要加載User32.dll,而User32.dll將會(huì)檢查這個(gè)值,如果有的話就LoadLibrary該Dll。這個(gè)方法不好,因?yàn)榇蠖鄶?shù)情況我們只需要針對(duì)性的注入,并且沒辦法注入到不使用User32.dll的進(jìn)程中;
2.用SetWindowsHookEx函數(shù),并傳遞目標(biāo)線程ID、需要掛載的Dll在本進(jìn)程中的映射地址(hInstance)、替換函數(shù)在本進(jìn)程中的地址。這樣,當(dāng)被掛載進(jìn)程的這個(gè)線程要執(zhí)行相應(yīng)的操作時(shí)(GETMESSAGE、鍵盤消息之類的),就會(huì)發(fā)現(xiàn)已經(jīng)安裝了WH_XX,The system checks to see whether the DLL containing the GetMsgProc function is mapped into Process B's address space,如果還未映射該Dll,則強(qiáng)制LoadLibrary。然后系統(tǒng)調(diào)用hThisInstance + (GetMsgProc - hInstance),從而實(shí)現(xiàn)了事件的通知。這種方法的好處是可以針對(duì)某個(gè)進(jìn)程安裝Hook,缺點(diǎn)是容易被目標(biāo)進(jìn)程發(fā)現(xiàn)、同樣只適用于GUI進(jìn)程。如果不再想使用掛鉤了,那么需要調(diào)用UnhookWindowsHookEx,卸載Hook。
3.使用遠(yuǎn)程線程注入Dll(Injecting a DLL Using Remote Threads)
這個(gè)方法比較好。流程是這樣的:
?調(diào)用VirtualAllocEx,在目標(biāo)進(jìn)程保留一塊內(nèi)存,并提交,其長度是你要注入Dll的全路徑長度nLen + 1,返回地址pv;
?調(diào)用WriteProcessMemory,在目標(biāo)進(jìn)程的pv處寫入Dll的全路徑,注意要添加\0結(jié)束符;
?獲取本進(jìn)程的LoadLibrary函數(shù)的地址,方法是調(diào)用pfn = GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA")——之所以獲取本進(jìn)程的地址,是因?yàn)閗ernel32.dll在每個(gè)進(jìn)程的映射地址都相同,倘若不同,那么此方法則無效;
?調(diào)用HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, pfn, pv, 0, NULL)來創(chuàng)建遠(yuǎn)程線程,其實(shí)這個(gè)線程函數(shù)就是LoadLibrary函數(shù),因此將執(zhí)行映射Dll到目標(biāo)進(jìn)程的操作;
?調(diào)用VirtuallFreeEx(hProcessRemote, pv)釋放提交的內(nèi)存;
這便完成了dll注入。
缺點(diǎn)是不能用在windows98上。但是對(duì)于xp都要被微軟拋棄的年代,windows98地影響不大了。
4.披著羊皮的狼:使用特洛伊Dll來注入Dll(Injecting a DLL with a Trojan DLL)
其實(shí)就是替換某個(gè)目標(biāo)進(jìn)程要加載的a.dll,并把a(bǔ).dll的所有引出函數(shù)用函數(shù)轉(zhuǎn)發(fā)器在自己的dll引出。
5.用調(diào)試函數(shù)插入Dll
ReadProcessMemory和WriteProcessMemory是windows提供的調(diào)試函數(shù)。如果在方法3中調(diào)用WriteProcessMemory寫入的不是字串而是精心編排好的機(jī)器指令,并且寫在目標(biāo)進(jìn)程特定的地址空間,那么這段機(jī)器指令就有機(jī)會(huì)執(zhí)行——而這段機(jī)器指令恰好完成了LoadLibrary功能;
6.其他方法(略)
二.掛接API(API Hooking)
其實(shí),這是許多注入的Dll都愿意做的事情。
所謂掛接API就是在目標(biāo)進(jìn)程調(diào)用windows API之前,先執(zhí)行我們的仿API函數(shù),從而控制系統(tǒng)API的行為,達(dá)到特殊的目的。
我們的仿造函數(shù)必須與要替換的系統(tǒng)API有相同的型參表以及相同的返回值類型.
1.改寫系統(tǒng)API代碼的前幾個(gè)字節(jié),通過寫入jmp指令來跳轉(zhuǎn)到我們的函數(shù)。在我們的函數(shù)里執(zhí)行操作,可以直接返回一個(gè)值,也可以將系統(tǒng)API的前幾個(gè)字節(jié)復(fù)原,調(diào)用系統(tǒng)API,并返回系統(tǒng)API的值——隨便你想怎么做。
此方法的缺點(diǎn)是對(duì)于搶占式多線程的系統(tǒng)不太管用。
2.通過改寫目標(biāo)進(jìn)程IAT中要調(diào)用的函數(shù)地址來達(dá)到目的。具體操作見書中示例
線程本地存儲(chǔ)(Thread-Local Storage)
例子C / C + +運(yùn)行期庫要使用線程本地存儲(chǔ)器( T L S)。由于運(yùn)行期庫是在多線程應(yīng)用程序出現(xiàn)前的許多年設(shè)計(jì)的,因此運(yùn)行期庫中的大多數(shù)函數(shù)是用于單線程應(yīng)用程序的。函數(shù)s t r t o k就是個(gè)很好的例子。
盡可能避免使用全局變量和靜態(tài)變量
1.動(dòng)態(tài)TLS
圖21-1 用于管理T L S的內(nèi)部數(shù)據(jù)結(jié)構(gòu)
在創(chuàng)建線程時(shí),進(jìn)程會(huì)為當(dāng)前創(chuàng)建的線程分配一個(gè)void *的數(shù)組作為TLS用。它用于存儲(chǔ)只限當(dāng)前線程可見的全局變量。
從而使進(jìn)程中的每個(gè)線程都可以有自已的(不能其它線程訪問的)全局變量。
TlsAlloc在返回時(shí)會(huì)先把槽中的值置為0。每個(gè)線程至少有64個(gè)槽。
2.靜態(tài)TLS
__declspec(thread)關(guān)鍵字用于聲明,線程本地的全局變量。
要求聲明的變量必須是全局變量或靜態(tài)變量。
3.Common API:
TlsAlloc TlsFree
TlsSetValue TlsGetValue
__declspec(thread)