PE文件格式学习(十二):TLS表

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tutucoo/article/details/83855757

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

猜你喜欢

转载自blog.csdn.net/tutucoo/article/details/83855757