PE檔案格式學習(十二):TLS表
1.介紹
TLS全稱執行緒區域性儲存器,它用來儲存變數或回撥函式。
TLS裡面的變數和回撥函式都在程式入口點(AddressOfEntry)之前執行,也就是說程式在被除錯時,還沒有在入口點處斷下來之前,TLS中的變數和回撥函式就已經執行完了,所以TLS可以用作反除錯之類的操作。
TLS中的變數單獨存在於每個獨立的執行緒當中,每個執行緒中對該變數的操作都不會影響到其他執行緒中的TLS變數。
TLS變數的建立方法有兩種方式,分別是動態方式和靜態方式,動態方法會用到TlsAlloc、TlsFree、TlsSetValue、TlsGetValue這幾個函式來操作變數,靜態方法會用宣告__declspec (thread) int xx = 1;這樣的方式來建立。需要注意的是靜態建立的TLS變數不能用於DLL動態庫中。
理論說的多了未免有點枯燥,先寫個例項吧,說明變數和回撥函式的建立,這樣明白的更透徹一點。
2.TLS例項
__declspec (thread)int g_nNum = 0x11111111; __declspec (thread)char g_szStr[] = "TLS g_nNum = 0x%p ...\r\n"; void NTAPI t_TlsCallBack_A(PVOID DllHandle, DWORD Reason, PVOID Red) { if (DLL_THREAD_DETACH == Reason) { printf("t_TlsCallBack_A -> ThreadDetach!\r\n"); return; } } void NTAPI t_TlsCallBack_B(PVOID DllHandle, DWORD Reason, PVOID Red) { if (DLL_THREAD_DETACH == Reason) { printf("t_TlsCallBack_B -> ThreadDetach!\r\n"); return; } } #pragma data_seg(".CRT$XLB") PIMAGE_TLS_CALLBACK p_thread_callback[] = { t_TlsCallBack_A, t_TlsCallBack_B, NULL }; #pragma data_seg() DWORD WINAPI t_ThreadFun(PVOID pParam) { printf("t_Thread -> first printf:"); printf(g_szStr, g_nNum); g_nNum = 0x2222222; printf("t_Thread -> second printf:"); printf(g_szStr, g_nNum); return 0; } int _tmain() { printf("_tmain -> TlsDemo.exe is running...\r\n\r\n"); CreateThread(NULL, 0, t_ThreadFun, NULL, 0, 0); Sleep(100); printf("\r\n"); CreateThread(NULL, 0, t_ThreadFun, NULL, 0, 0); system("pause"); return 0; }
首先我們看註冊TLS變數的方式,本例子使用的是靜態方法也就是__declspec (thread)int xx = 1;這樣的方式來建立,而註冊回撥函式相對比較麻煩點,我們要宣告一個#pragma data_seg(".CRT$XLB")這樣的巨集定義將回調函式包起來,CRT表明使用C RunTime機制,X表示標識名隨機,L表示TLS callback section,B可以是B-Y之間任意的字母。
我們還看見回撥函式會線上程被終止時呼叫,並且呼叫的順序跟註冊回撥函式時相關,其實回撥函式不止會線上程終止時呼叫,還有以下幾種情況會被呼叫。
具體的參見程式碼吧,列印結果如下:
可以看見兩個執行緒對g_nNum的操作其實是互不影響的,在程式執行到入口點之前,g_nNum已經被初始化了,以後每開闢一條新的執行緒,系統都會拷貝一份TLS變數的副本到該執行緒中。
3.TLS解析
接下來對TLS在PE檔案中的結構進行解析。TLS在PE中資料目錄表的第10位。
通常一個包含了TLS表的程式,它就會擁有.tls段,這個段裡面儲存了變數和回撥函式的資料,但是TLS表本身的結構體一般存在於.rdata段內。本文的例子程式的TLS表RVA是0x2200,通過轉換為offset得到0x1000,我們到0x1000處看看十六進位制再對比結構體欄位就可以解析出TLS表了。
typedef struct _IMAGE_TLS_DIRECTORY32
{
DWORD StartAddressOfRawData;
DWORD EndAddressOfRawData;
PDWORD AddressOfIndex;
PIMAGE_TLS_CALLBACK *AddressOfCallBacks;
DWORD SizeOfZeroFill;
DWORD Characteristics;
} IMAGE_TLS_DIRECTORY32
需要注意的是,這個結構體裡的欄位都是VA,也就是起始虛擬地址。
StartAddressOfRawData:tls模板在記憶體中的起始VA,模板是用於建立執行緒時初始化TLS資料的,對應上圖中的0x404000,因為是VA,所以我們將0x4000轉換成offset得到0x1800,我們看到0x1800處的資料如下,可以看到模板中的內容其實就是TLS中建立的變數:
EndAddressOfRawDataL:tls模板在記憶體中的結束VA,對應上圖中的0x404020
AddressOfIndex:儲存TLS索引的位置,對應上圖中的0x40337c,這裡為0
AddressOfCallBacks:指向TLS註冊的回撥函式的函式指標陣列,對應上圖中的0x4020d0,轉換成offset後可以看到這個陣列有兩個值,分別是0x401000與0x401020,再將這兩個VA轉成offset,可以看到是函式的內容:
SizeOfZeroFill:用於指定非零初始化資料後面的空白空間的大小,對應上圖中的0x00000000
Characteristics:保留,對應上圖中的0x00000000