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