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