1. 程式人生 > >PE檔案格式學習(十二):TLS表

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之間任意的字母。

我們還看見回撥函式會線上程被終止時呼叫,並且呼叫的順序跟註冊回撥函式時相關,其實回撥函式不止會線上程終止時呼叫,還有以下幾種情況會被呼叫。

PE檔案格式學習(十二):TLS表

具體的參見程式碼吧,列印結果如下:

可以看見兩個執行緒對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