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