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回调函数的数组指针比较重要,我们可以自己注册回调函数进行使用,回调函数在程序入口点代码之前执行。(嘿嘿,可以用来做反调试)。

发布了20 篇原创文章 · 获赞 23 · 访问量 1229

猜你喜欢

转载自blog.csdn.net/weixin_43742894/article/details/105235426