第45章:TLS回撥函式
TLS(Thread Local Storage,執行緒區域性儲存)回撥函式(Callback Function)常用於反除錯。
TLS 回撥函式的呼叫執行要先於 EP 程式碼的執行。它是各執行緒獨立的資料儲存空間,可修改程序的全域性/靜態資料。
若在程式設計中啟用了 TLS,PE 標頭檔案中會設定 TLS 專案,即:IMAGE_TLS_Directory
其中比較重要的成員是:AddressOfCallBacks 它指向回撥函式陣列地址。
自己找一下試試:
同時獲取到相應的節區資訊,計算: 9310 - 8000 + 6600 = 7910 .
第四個元素即為 AddressOfCallBacks .
408114 明顯超出了檔案的大小,因此判斷是加上了 ImageBase (即 VA ),在節區頭檢視 ImageBase 大小為 400000 .
因此 RVA 為 8114 ,8114 - 8000 + 6600 = 6714 .
引數 Reason 表示呼叫 TLS 回撥函式的原因:
第二個程式的程式碼( TlsTest.cpp ):
#include <windows.h> #pragma comment(linker, "/INCLUDE:__tls_used")//通過#pragma comment(Linker,"INCLUDE:_tls_used")顯示的申明crt庫中定義的_tls_used變數 // 以便向_tls_used!AddressOfCallBacks域添加回調函式。 void print_console(char* szMsg) { HANDLE hStdout= GetStdHandle(STD_OUTPUT_HANDLE); WriteConsoleA(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") //CRT 表明使用 CRunTime 機制,X 表示標識名隨機,L 表示 TLScallback section, X 為 B-Y 之間的任意一個字母 PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1, TLS_CALLBACK2, 0 }; // 注意,此處將自定義函式放入了 TLS 表中。 #pragma data_seg() DWORD WINAPI ThreadProc(LPVOID lParam) { 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"); return 0; }
程式執行截圖:
① DLL_Process_ATTACH 在主執行緒呼叫 Main()函式前,已經註冊的兩個函式會被呼叫執行。
② DLL_Thread_ATTACH 在 TLS 函式完成後,main()函式開始執行。在建立使用者執行緒前,TLS 回撥函式會再次被執行。
③ DLL_Thread_Detach TLS 函式執行完後,執行緒函式開始呼叫執行,執行緒函式結束後再次呼叫 TLS 函式。
④DLL_Process_Detach main()函式結束後,再次呼叫 TLS 函式。
使用 win xp sp3 進行實際除錯:
使用 OD ,將斷點設定為 TLS 斷點,程式會斷在此處:
對比程式碼,並觀察程式名可知,程式被斷在了 TLS_CallBack1 處。繼續執行程式至返回:
可以看到,通過間接呼叫,實現了 TLS CallBack1 的呼叫。繼續執行程式:
可以看到,函式執行到了第一次執行的位置,表明會迴圈執行,直至函式執行完:
手工新增 TLS 回撥函式
①將 TLS 結構體及 TLS 回撥函式放在最後一個節區 or 其它節區的空白地區 or 新增新節區。
屬性增加了 Write (方便在除錯時編寫程式碼),Execute(可執行),CNT_CODE(區段包含程式碼)。
②設定 TLS 表
③設定 IMAGE_TLS_DIRECTORY 結構體
StartAddressOfRawData:tls模板在記憶體中的起始VA,模板是用於建立執行緒時初始化TLS資料的;
EndAddressOfRawDataL:tls模板在記憶體中的結束VA;
AddressOfIndex:儲存TLS索引的位置;
這三個值都可以指向 NULL 區域。
第四個值 CallBack 指向一個數組,以 四個位元組的 NULL 結尾。陣列每四個位元組指向一個地址,儲存著函式。
有意思的是,修改後,在 win xp sp3 上無法執行。與作者手工修改的檔案進行比較: