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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM