PE文件:TLS表(線程局部儲存)

0x00 線程局部存儲

線程局部存儲,它實現了線程內局部變量的存儲訪問。該技術下定義的變量能被同一個線程內部的各個函數調用,同時杜絕了其他線程對這些變量的訪問。
(線程局部存儲設計Windows進程和線程,所以在研究TLS之前,要先了解windows進程和線程相關的內容。)

作用:TLS解決了多線程程序設計中同步變量問題。

實現:解決同步變量的問題,也就是幾個線程共用一個變量X,TLS的解決方法是,每個線程,開闢一個空間當對A線程進行操作的時候,操作的是A線程的X,當對B線程進行操作的時候,是對B線程的X進行操作。
具體實現就是在進程中建立一個全局表,通過現成的ID去查詢相應的數據結構,因爲每個線程的ID是不一樣的,所以查到的數據也自然不一樣了。

實現的方法有倆種:

  • 動態線程局部存儲技術
  • 靜態線程局部存儲技術

0x01 動態線程局部存儲技術

主要通過四個函數TlsAlloc( )、TlsSetValue( )、TlsGetValue( )、TlsFree( )來實現,同時生成的PE文件中沒有.tls表。

TlsAlloc( ):分配線程局部存儲空間/索引,該進程任何線程都可以通過該索引來存儲和檢索線程中的值。
TlsFree( ): 釋放線程局部存儲空間/索引。
TlsGetValue( ): 獲得線程局部存儲空間裏面的值,按索引取值。
TlsSetValue( ): 設置線程局部存儲空間的值,按索引存儲。

如下圖所示,線程1對進程索引3操作,操作的是線程1的內容;線程2對進程的索引3操作,操作的也只是線程2的內容。
在這裏插入圖片描述
測試用例:
有倆個線程對同一個變量進行操作,發現他們互不影響,並且運行到最後,變量__Number還是最初分配的1,沒有被線程改變。
在這裏插入圖片描述
附上代碼:

#include"TLSDynamic.h"

/*
動態tls其實就是爲每個線程創建一個與其關聯的內存塊,這個內存塊可以當數組使用TlsAlloc可以分配一個沒有用過的索引給你,讓你在裏面寫入東西 
*/
DWORD WINAPI ThreadProcedure(LPVOID parameter);

DWORD __Number = 0;//動態使用(存放索引)



int _tmain(int argc, TCHAR** argv, TCHAR* envp[])
{
	setlocale(LC_ALL, "Chinese-simplified");
	_tprintf(_T("main start\r\n"));
	__Number = TlsAlloc();//使用之前先分配一個索引
	_tprintf(_T("__Number=%d\r\n"), __Number);
	HANDLE ThreadHandle[2];
	//TLS中的變量單獨存在於每個獨立的線程當中,每個線程中對該變量的操作都不會影響到其他線程中的TLS變量。

	for (int i = 0; i < 2; i++)
	{
		ThreadHandle[i] = CreateThread(NULL, 0, ThreadProcedure, NULL, 0, NULL);
	}
	//2個線程
	WaitForMultipleObjects(2, ThreadHandle, true, INFINITE);


	_tprintf(_T("__Number=%d\r\n"), __Number);

	TlsFree(__Number);

	_tprintf(_T("動態Tls    End\r\n"));

	

	_tprintf(_T("Input AnyKey To Exit\r\n"));
	_gettchar();
	return 0;
}



DWORD WINAPI ThreadProcedure(LPVOID parameter)
{
	//獲得tls的空間,然後對齊。
	TlsSetValue(__Number, 0);//分別在	空間的某個索引處儲存某個數據

	for (int i = 0; i < 10; i++)
	{ 
		int n = (int)TlsGetValue(__Number);
		
		Sleep(1);

		_tprintf(_T("PID=%d __Number=%d\n"), GetCurrentThreadId(), n);
		//設置tls局部空間的值
		TlsSetValue(__Number, (LPVOID)(++n));//分別在	空間的某個索引處儲存某個數據
	}
	return 0;
}



0x02 靜態線程局部存儲技術

靜態方法會用聲明__declspec (thread) int xx = 1;這樣的方式來創建。需要注意的是靜態創建的TLS變量不能用於DLL動態庫中。靜態方法預先將變量定義儲存在PE的.tls表中。
PE文件的.tls節中會包括:初始化數據、用於每個線程初始化和終止的回調函數、TLS索引。

0x03 TLS表結構解析

Tls表在數據目錄的第十位,通常一個包含了TLS表的程序,它就會擁有.tls段,這個段裏面保存了變量和回調函數的數據,但是TLS表本身的結構體一般存在於.rdata段內。

typedef struct _IMAGE_TLS_DIRECTORY32 
{
    DWORD   StartAddressOfRawData; //  TLS初始化數據的起始地址
    DWORD   EndAddressOfRawData;// TLS初始化數據的結束地址  兩個正好定位一個範圍,範圍放初始化的值
    PDWORD  AddressOfIndex;// TLS 索引的位置
    PIMAGE_TLS_CALLBACK *AddressOfCallBacks;// Tls回調函數的數組指針
    DWORD   SizeOfZeroFill;// 填充0的個數
    DWORD   Characteristics;
} IMAGE_TLS_DIRECTORY32

其中AddressOfCallBacks回調函數的數組指針比較重要,我們可以自己註冊回調函數進行使用,回調函數在程序入口點代碼之前執行。(嘿嘿,可以用來做反調試)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章