TLS回调函数(1)
簡(jiǎn)述
代碼逆向分析領(lǐng)域中,TLS(Thread Local Storage,線程局部存儲(chǔ))回調(diào)函數(shù)(Callback Function)常用反調(diào)試。TLS回調(diào)函數(shù)的調(diào)用運(yùn)行要先于EP代碼的執(zhí)行,該特征使它可以作為一種反調(diào)試技術(shù)的使用。
TLS
TLS是各線程的獨(dú)立的數(shù)據(jù)存儲(chǔ)空間,使用TLS技術(shù)可在線程內(nèi)部獨(dú)立使用或修改進(jìn)程的全局?jǐn)?shù)據(jù)或靜態(tài)數(shù)據(jù),就像對(duì)待自身的局部變量一樣。
IMAGE_DATA_DIRECTORY[9]
若在編程中啟用了TLS功能,PE頭文件中就會(huì)設(shè)置TLS表(TLS Table)項(xiàng)目,如下(IMAGE_NT_HEADERS-IMAGE_OPTIONAL_HEADER-IMAGE_DATA_DIRECTORY[9])
_IMAGE_TLS_DIRECTORY32結(jié)構(gòu)體有2種版本,分別為32位版本和64位版本。
代碼逆向分析中涉及的比較重要的成員AddressOfCallbacks,該值指向含有TLS回調(diào)函數(shù)地方(VA)的數(shù)組。這意味著可以向同一程序注冊(cè)多個(gè)TLS回調(diào)函數(shù)
回調(diào)函數(shù)地址數(shù)組
該數(shù)組中實(shí)際存儲(chǔ)的就是TLS回調(diào)函數(shù)的地址。進(jìn)程啟動(dòng)運(yùn)行時(shí),(執(zhí)行EP代碼前)系統(tǒng)會(huì)逐一調(diào)用存儲(chǔ)在該數(shù)組中的函數(shù)。
可以看出此數(shù)組有兩個(gè)回調(diào)函數(shù)(地址為4113B1和41129E),我們可以通過修改程序注冊(cè)多個(gè)TLS函數(shù)
TLS回調(diào)函數(shù)
接下來就是從技術(shù)層面簡(jiǎn)單整理之前介紹的TLS回調(diào)函數(shù)相關(guān)內(nèi)容。
所謂TLS回調(diào)函數(shù)是指,每當(dāng)創(chuàng)建/終止進(jìn)程的線程時(shí)會(huì)自動(dòng)調(diào)用執(zhí)行的函數(shù)。有意思的是,創(chuàng)建進(jìn)程的主線程時(shí),也會(huì)自動(dòng)調(diào)用回調(diào)函數(shù),且其調(diào)用執(zhí)行先于EP代碼。反調(diào)試技術(shù)利用的就是TLS回調(diào)函數(shù)的這一特征。
請(qǐng)注意,創(chuàng)建或終止某線程時(shí),TLS回調(diào)函數(shù)都會(huì)自動(dòng)調(diào)用執(zhí)行,前后共2次(原意即為此)。
執(zhí)行進(jìn)程的主線程(運(yùn)行線程的EP代碼)前,TLS回調(diào)函數(shù)會(huì)被先調(diào)用執(zhí)行,許多逆向分析人員將該特征應(yīng)用于程序的反調(diào)試技術(shù)。
IMAGE_TLS_CALLBACK
typedef VOID (NTAPI *PIMAGE_TLS_CALLBACK) (PVOID DllHandle,DWORD Reason,PVOID Reserved);仔細(xì)觀察TLS回調(diào)函數(shù)的定義可以發(fā)現(xiàn),它與DllMain的定義類似。
BOOL WINAPI DllMain(__in HINSTANCE hModule,__in DWORD fdwReason,__in LPVOID lpReserved);觀察以上2個(gè)函數(shù)可以發(fā)現(xiàn),它們的參數(shù)順序與含義都是一樣的。其實(shí),參數(shù)DllMain為模塊句柄(即加載地址),參數(shù)fdwReason表示調(diào)用TLS回調(diào)函數(shù)的原因,具體原因有四種,如下所示
#define DLL_PROCESS_ATTACH 1 #define DLL_THREAD_ATTACH 2 #define DLL_THREAD_DETACH 3 #define DLL_PROCESS_ATTACH 0接下來用例子講解這四種原因
#include<Windows.h>#pragma comment(linker,"/INCLUDE:__tls_used")void print_console(const char* szMsg) {HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);WriteConsole(hStdout, szMsg, strlen(szMsg), NULL, NULL);}void NTAPI TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved) {char szMsg[80] = { 0, };wsprintfA(szMsg, "TLS_CALLBACK1():DllHandle=%X,Reason=%d\n", DllHandle, Reason);print_console(szMsg); }void NTAPI TLS_CALLBACK2(PVOID DllHandle, DWORD Reason, PVOID Reserved) {char szMsg[80] = { 0, };wsprintfA(szMsg, "TLS_CALLBACK2():DllHandle=%X,Reason=%d\n", DllHandle, Reason);print_console(szMsg); }#pragma data_seg(".CRT$XLX") PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1,TLS_CALLBACK2,0 };#pragma data_seg()DWORD WINAPI ThreadProc(LPVOID lPram) {print_console("ThreadProc() start\n");print_console("ThreadProc() end\n");return 0; }int main(void) {HANDLE hThread = NULL;print_console("main()start\n");hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);WaitForSingleObject(hThread, 60 * 1000);CloseHandle(hThread);print_console("main() end\n");system("pause");return 0; }代碼講解:
#pragma comment(linker,"/INCLUDE:__tls_used")告知鏈接器使用TLS
#pragma data_seg(".CRT$XLX") PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1,TLS_CALLBACK2,0 }; #pragma data_seg()注冊(cè)TLS函數(shù),.CRT$XLX的作用:CRT表示使用C Runtime 機(jī)制,X表示表示名隨機(jī),L表示TLS Callback section,X也可以換成B~Y任意一個(gè)字符
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1,TLS_CALLBACK2,0 };存儲(chǔ)回調(diào)函數(shù)地址
DLL_PROCESS_ATTACH
進(jìn)程的主線程調(diào)用main函數(shù)前,已經(jīng)注冊(cè)的TLS回調(diào)函數(shù)(TLS_CALLBACK1,TLS_CALLBACK2)會(huì)先被調(diào)用執(zhí)行,此時(shí)Reason的值為1(DLL_PROCESS_ATTACH)
DLL_THREAD_ATTACH
所有的TLS回調(diào)函數(shù)執(zhí)行完后,main()函數(shù)開始調(diào)用執(zhí)行,創(chuàng)建用戶線程(ThreadProc)前,TLS回調(diào)函數(shù)會(huì)被再次調(diào)用執(zhí)行,此時(shí)Reason的值為2(DLL_THREAD_ATTACH)
DLL_THREAD_DETACH
TLS回調(diào)函數(shù)全部 執(zhí)行完畢后,ThreadProc()線程函數(shù)開始調(diào)用執(zhí)行。其執(zhí)行完畢后Reason=3(DLL_THREAD_DETACH),TLS回調(diào)函數(shù)被調(diào)用執(zhí)行
DLL_PROCESS_DETACH
ThreadProc()線程函數(shù)執(zhí)行完畢后,一直在等待線程終止的main函數(shù)(主線程)也會(huì)終止。此時(shí)Reason=0(DLL_PROCESS_ATTACH),TLS回調(diào)函數(shù)最后一次被調(diào)用執(zhí)行。
補(bǔ)充:
源文件中并未使用printf()函數(shù),因?yàn)殚_啟特定編譯選項(xiàng)(/MT)編譯源程序,先于主程序調(diào)用執(zhí)行的TLS回調(diào)函數(shù)中可能發(fā)生Run-time Error(運(yùn)行時(shí)錯(cuò)誤)。此時(shí)可以直接調(diào)用WriteConsole() API來以防萬一。
調(diào)試TLS回調(diào)函數(shù)
若直接使用調(diào)試器打開帶有TLS回調(diào)函數(shù)的程序,則無法調(diào)試TLS回調(diào)函數(shù),因?yàn)門LS在EP代碼之前就被調(diào)用執(zhí)行了,如果TLS函數(shù)內(nèi)部含有反調(diào)試代碼,這使程序直接無法繼續(xù)。需要如下操作選項(xiàng)----->調(diào)試設(shè)置---->事件------->點(diǎn)擊 系統(tǒng)斷點(diǎn)
調(diào)試器暫停的位置即是系統(tǒng)啟動(dòng)斷點(diǎn),在OD調(diào)試器的默認(rèn)設(shè)置下,調(diào)試器會(huì)在EP處暫停,而WinDbg調(diào)試器默認(rèn)在系統(tǒng)啟動(dòng)斷點(diǎn)暫停。
補(bǔ)充
DLL程序入口點(diǎn)函數(shù):DllMain,注意:大小寫是區(qū)別的(僅導(dǎo)出資源的DLL可以沒有DllMain函數(shù))
函數(shù)原型:
BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) {return TRUE; }hModule參數(shù):指向DLL本身的實(shí)例句柄;
ul_reason_for_call參數(shù):指明了DLL被調(diào)用的原因,可以有以下4個(gè)取值:
1.DLL_PROCESS_ATTACH:
當(dāng)DLL被進(jìn)程 <<第一次>> 調(diào)用時(shí),導(dǎo)致DllMain函數(shù)被調(diào)用,
同時(shí)ul_reason_for_call的值為DLL_PROCESS_ATTACH,
如果同一個(gè)進(jìn)程后來再次調(diào)用此DLL時(shí),操作系統(tǒng)只會(huì)增加DLL的使用次數(shù),
不會(huì)再用DLL_PROCESS_ATTACH調(diào)用DLL的DllMain函數(shù)。
2.DLL_PROCESS_DETACH:
當(dāng)DLL被從進(jìn)程的地址空間解除映射時(shí),系統(tǒng)調(diào)用了它的DllMain,傳遞的ul_reason_for_call值是DLL_PROCESS_DETACH。
★如果進(jìn)程的終結(jié)是因?yàn)檎{(diào)用了TerminateProcess,系統(tǒng)就不會(huì)用DLL_PROCESS_DETACH來調(diào)用DLL的DllMain函數(shù)。這就意味著DLL在進(jìn)程結(jié)束前沒有機(jī)會(huì)執(zhí)行任何清理工作。
3.DLL_THREAD_ATTACH:
當(dāng)進(jìn)程創(chuàng)建一線程時(shí),系統(tǒng)查看當(dāng)前映射到進(jìn)程地址空間中的所有DLL文件映像,
并用值DLL_THREAD_ATTACH調(diào)用DLL的DllMain函數(shù)。
新創(chuàng)建的線程負(fù)責(zé)執(zhí)行這次的DLL的DllMain函數(shù),
只有當(dāng)所有的DLL都處理完這一通知后,系統(tǒng)才允許線程開始執(zhí)行它的線程函數(shù)。
4.DLL_THREAD_DETACH:
如果線程調(diào)用了ExitThread來結(jié)束線程(線程函數(shù)返回時(shí),系統(tǒng)也會(huì)自動(dòng)調(diào)用ExitThread),
系統(tǒng)查看當(dāng)前映射到進(jìn)程空間中的所有DLL文件映像,
并用DLL_THREAD_DETACH來調(diào)用DllMain函數(shù),
通知所有的DLL去執(zhí)行線程級(jí)的清理工作。
★注意:如果線程的結(jié)束是因?yàn)橄到y(tǒng)中的一個(gè)線程調(diào)用了TerminateThread,
系統(tǒng)就不會(huì)用值DLL_THREAD_DETACH來調(diào)用所有DLL的DllMain函數(shù)。
③lpReserved參數(shù):保留,目前沒什么意義。
TLS系列
TLS回調(diào)函數(shù)(1)
TLS回調(diào)函數(shù)(2)
總結(jié)
以上是生活随笔為你收集整理的TLS回调函数(1)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OD软件断点原理
- 下一篇: 静态反调试技术(3)