什么是TLS?
TLS是 Thread Local Storage的缩写 线程局部存储
。主要是为了解决多线程中变量同步的问题。
先说传统的全局变量或者静态变量:
进程中的全局变量与函数内定义的静态变量,是各个线程都可以访问的共享变量
。在一个线程修改的内存内容,对所有线程都生效。
这是一个优点也是一个缺点。
如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量(被称为static memory local to a thread 线程局部静态变量),就需要新的机制来实现。这就是TLS。
线程A去修改TLS变量时 线程B是不会受影响的,因为每个线程都拥有一个TLS变量的副本。
创建TLS变量:
_declspec(thread) int g_a = 100;
全局变量的线程共享问题:
一 . 不使用TLS变量。
#include
#include
HANDLE Event;
HANDLE Thread1, Thread2;
int num = 100;
DWORD WINAPI ThreadProc1(
LPVOID lpParam
){
num = 200;
printf("1. num= %d\n",num);
SetEvent(Event);
return 1;
}
DWORD WINAPI ThreadProc2(
LPVOID lpParam
){
WaitForSingleObject(Event, INFINITE);
printf("2. num= %d\n", num);
SetEvent(Event);
return 1;
}
int main()
{
Event = CreateEvent(NULL, FALSE, FALSE, NULL); //手动复位,无信号
Thread1 = CreateThread(NULL,NULL,ThreadProc1,NULL,NULL,NULL);
Thread2 = CreateThread(NULL,NULL,ThreadProc2,NULL,NULL,NULL);
WaitForSingleObject(Thread1, INFINITE);
WaitForSingleObject(Thread2, INFINITE);
system("pause");
return 0;
}
如图:我们有一个全局的变量 int num =100 。
二 . 使用TLS变量:
把这个全局变量的声明改为:
__declspec(thread) int num = 100;
其他的代码都不变,我们就会发现,我们的线程都拥有了这个TLS变量的一份副本
,对一个变量的线程中的修改不会改变另一个线程的值:
在安全领域中,TLS常被用来处理诸如反调试、抢占执行等操作。
TLS回调函数
实现方法:
#pragma comment(linker,"/INCLUDE:__tls_used")
/*
* 注册TLS回调函数
* ".CRT$XLB"的含义是:
* CRT表明使用C RunTime机制
* X表示标识名随机
* L表示TLS callback section
* B其实也可以为B - Y的任意一个字母
*/
#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = //存储回调函数地址
{ TLS_CALLBACK1, TLS_CALLBACK2, 0 };
#pragma data_seg()
这是TLS回调函数的结构体:
我们创建的TLS函数必须是这个结构体的。
typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (
PVOID DllHandle, //
DWORD Reason,
PVOID Reserved
);
TLS函数何时被调用
#define DLL_PROCESS_ATTACH 1//进程创建时
#define DLL_THREAD_ATTACH 2 //线程创建时
#define DLL_THREAD_DETACH 3//线程销毁时
#define DLL_PROCESS_DETACH 0//进程销毁时
在具有TLS回调函数的程序中,在程序加载入内存时,首先执行TLS回调函数
,然后再进入ImageBase+EntryPoint 进入程序入口(OEP)
示例:
#include
#include
#pragma comment(linker,"/INCLUDE:__tls_used")
HANDLE Event;
HANDLE Thread1, Thread2;
__declspec(thread) int num = 100;
/*
我们定义的TLS回调函数体
*/
VOID NTAPI Tls_CallBack(PVOID DllHandle,DWORD Reason,PVOID Reserved)
{
if (DLL_PROCESS_ATTACH == Reason)
{
printf("进程创建!\n");
}
if (DLL_PROCESS_DETACH == Reason)
{
printf("进程销毁!\n");
}
if (DLL_THREAD_ATTACH == Reason)
{
printf("线程创建!\n");
}
if (DLL_THREAD_DETACH == Reason)
{
printf("线程销毁!\n");
}
}
//首先注册TLS函数,用PIMAGE_TLS_CALLBACK 类型的数组来接受,最后一个参数必须是0
#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTlsHeader[] = { Tls_CallBack,0 };
#pragma data_seg()
DWORD WINAPI ThreadProc1(
LPVOID lpParam
)
{
num = 200;
printf("1. num= %d\n",num);
SetEvent(Event);
return 1;
}
DWORD WINAPI ThreadProc2(
LPVOID lpParam
)
{
WaitForSingleObject(Event, INFINITE);
printf("2. num= %d\n", num);
SetEvent(Event);
return 1;
}
int main()
{
Event = CreateEvent(NULL, FALSE, FALSE, NULL); //手动复位,无信号
Thread1 = CreateThread(NULL,NULL,ThreadProc1,NULL,NULL,NULL);
Thread2 = CreateThread( NULL, NULL, ThreadProc2,NULL, NULL, NULL);
WaitForSingleObject(Thread1, INFINITE);
WaitForSingleObject(Thread2, INFINITE);
system("pause");
return 0;
}
运行如下:
可以发现: 在整个程序执行前,我们最先执行的是TLS函数的部分!!!!!!
TLS函数在程序执行时会首先被执行,所以它可以被应用于反调试领域,你发布的一个exe程序,你不想要别人调试你的程序,你就可以使用整个方法:
#pragma comment(linker,"/INCLUDE:__tls_used")
VOID NTAPI tls_callback(
PVOID DllHandle,
DWORD Reason,
PVOID Reserved
)
{
BOOL qqql = FALSE;
HANDLE Process = GetCurrentProcess();
//如果处于调试,则直接退出
CheckRemoteDebuggerPresent(Process, &qqql);
if (qqql)
{
MessageBoxA(NULL, "禁止调试!", "警告", MB_OK);
TerminateProcess(Process,NULL);
}
return;
}
#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTlsfun[] = { tls_callback ,0 };
#pragma data_seg()
int main()
{
printf("你好!\n");
system("pause");
return 0;
}
运行如下:在我们debug模式下运行,会触发中断:
同理,使用debug x64/x86调式也是如此,触发禁止调试。
但是在Od里没用,因为Od具有反反调试的功能!!!