TLS
什么是TLS?
TLS是 Thread Local Storage 的缩写,线程局部存储。主要是为了解决多线程中变量同步的问题
如果需要要一个线程内部的各个函数调用都能访问,但其它线程不能访问的变量(被称为static memory local to a thread 线程局部静态变量),就需要新的机制来实现,这就是TLS
用途1:
TLS变量
线程A去修改TLS变量时,线程B不会受影响,因为每个线程都拥有一个TLS变量的副本
创建TLS变量
__declspe(thread) int g_tls = 1000;
用途2:
在安全领域中,TLS常被用于处理如反调试,抢占执行等操作
TLS回调函数
cpp
#include<iostream>
#include<Windows.h>
// 首先加上编译选项
_declspec(thread) int g_tlsNum = 100;
#ifdef _WIN64
#pragma comment(linker, "/INCLUDE:_tls_used")
#else
#pragma comment(linker, "/INCLUDE:__tls_used")
#endif
DWORD WINAPI threadProc(LPVOID lparam) {
g_tlsNum = 300;
printf("g_tlsNum=%d\n",g_tlsNum);
return 0;
}
void NTAPI t_TlsCallBack_A(PVOID DllHandle, DWORD Reason, PVOID Reserved);
/*
注册TLS函数,.CRT$XLX的作用
CRT表示使用C Runtime库
X表示标识名随机
L表示 TLS Callback section
X也可以换成B~Y任意一个字符
*/
// 注册 TLS 回调
#ifdef _WIN64
#pragma const_seg(".CRT$XLX") // x64 下用 const_seg(只读段)
EXTERN_C const // 禁用 C++ 的名称修饰
#else
#pragma data_seg(".CRT$XLX") // x86 下用 data_seg(可读写段)
#endif
//存储回调函数地址 PIMAGE_TLS_CALLBACK pTLS_CALLBACKs,写了几个回调函就要往里面添加几个,最后必须要有一个0
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { t_TlsCallBack_A,0 };
#ifdef _WIN64
#pragma const_seg()
#else
#pragma data_seg()
#endif
// 编写Tls回调函数 参数1:模块加载基址 参数2:调用的原因 参数3:保留
void NTAPI t_TlsCallBack_A(PVOID DllHandle, DWORD Reason, PVOID Reserved) {
switch (Reason) {
case DLL_PROCESS_ATTACH:
printf("Hello Tls\n");
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
}
int main() {
// 创建线程
CreateThread(NULL, NULL, threadProc,NULL,NULL,NULL);
return 0;
}
何时被调用
- #define DLL_PROCESS_ATTACH 1 // 进程创建时
- #define DLL_THREAD_ATTACH 2 // 线程创建时
- #define DLL_THREAD_DETACH 3 // 线程销毁时
- #define DLL_PROCESS_DETACH 0 // 进程销毁时

查看执行结果,我们会发现TLS是最先执行的,这样我们就可以用这个回调函数来反调试一些调试器的加载,一般来说调试器在加载一个程序的时候,程序最先执行的代码是OEP(Original Entry Point),但TLS在OPE之前执行
我们来修改一下代码来写一个简单的反调试程序
cpp
void NTAPI t_TlsCallBack_A(PVOID DllHandle, DWORD Reason, PVOID Reserved) {
switch (Reason) {
case DLL_PROCESS_ATTACH:
{
BOOL result = FALSE;
HANDLE hRealProcess = NULL;
DuplicateHandle(
GetCurrentProcess(), // 当前进程
GetCurrentProcess(), // 伪句柄 (HANDLE)-1
GetCurrentProcess(), // 目标进程(仍为当前进程)
&hRealProcess, // 存储真实句柄
NULL, FALSE, DUPLICATE_SAME_ACCESS
);
CheckRemoteDebuggerPresent(hRealProcess, &result); // 这种只是最简单的,现代调试器都会有反反调试的手段
if (result) {
MessageBox(NULL, L"检测到有调试器加载", L"Warning", MB_OK | MB_ICONWARNING);
ExitProcess(0);
}
break;
}
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
}
直接双击运行程序发现是没有问题的

我们来测试下有调试器加载的情况
(由于目前市面上常用的调试器都有反反调试的功能,我们这个简单的反调试肯定是不会被检测出来的,所以我们用Visual Studio自带的调试器来看一下)
可以看到我们main函数还没执行之前就已经触发了检测
TLS表
在我们的pe文件当中,有这么一张表,就是用来告诉Tls函数和变量在哪里存放着
在我们16张表中第10张表就是我们的Tls表对应存放的虚拟地址
cpp
// IMAGE_TLS_DIRECTORY64结构体
typedef struct _IMAGE_TLS_DIRECTORY64 {
ULONGLONG StartAddressOfRawData; // Tls初始化数据的起始地址
ULONGLONG EndAddressOfRawData; // Tls初始化数据的结束地址 (这个范围存放初始化的值)
ULONGLONG AddressOfIndex; // Tls索引的位置
ULONGLONG AddressOfCallBacks; // PIMAGE_TLS_CALLBACK * (Tls回调函数的数组指针)
DWORD SizeOfZeroFill; // 填充0的个数
union {
DWORD Characteristics;
struct {
DWORD Reserved0 : 20;
DWORD Alignment : 4;
DWORD Reserved1 : 8;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
} IMAGE_TLS_DIRECTORY64;
cpp
// 获取Tls表信息
void getTlsInfo(const char* peFileBuffer) {
// 获取Tls表地址
TableAddress repositionAddress = g_tableAddress[IMAGE_DIRECTORY_ENTRY_TLS];
// 通过Rva得到文件地址
DWORD fileAddress = rvaToFoa(repositionAddress.myVirtualAddress);
// 解析结构体
PIMAGE_TLS_DIRECTORY64 tlsDirectory = (PIMAGE_TLS_DIRECTORY64)(peFileBuffer + fileAddress);
printf("Tls初始化数据的起始地址:0x%llX\n", tlsDirectory->StartAddressOfRawData);
printf("Tls初始化数据的结束地址:0x%llX\n", tlsDirectory->EndAddressOfRawData);
printf("Tls索引的位置:0x%llX\n", tlsDirectory->AddressOfIndex);
printf("Tls回调函数的数组指针:0x%llX\n", tlsDirectory->AddressOfCallBacks);
printf("填充0的个数:%d\n", tlsDirectory->SizeOfZeroFill);
}

