TLS及反调试机制

什么是TLS?
TLS是 Thread Local Storage的缩写 线程局部存储。主要是为了解决多线程中变量同步的问题。

先说传统的全局变量或者静态变量:
进程中的全局变量与函数内定义的静态变量,是各个线程都可以访问的共享变量。在一个线程修改的内存内容,对所有线程都生效。

这是一个优点也是一个缺点。

  • 优点: 线程的数据交换变得非常快捷。
  • 缺点: 多个线程访问共享数据,需要昂贵的同步开销,也容易造成同步相关的BUG

如果需要在一个线程内部的各个函数调用都能访问、但其它线程不能访问的变量(被称为static memory local to a thread 线程局部静态变量),就需要新的机制来实现。这就是TLS。


TLS变量

线程A去修改TLS变量时 线程B是不会受影响的,因为每个线程都拥有一个TLS变量的副本。


创建TLS变量:
_declspec(thread) int g_a = 100;


全局变量的线程共享问题:

一 . 不使用TLS变量。

#include <windows.h>
#include <iostream>
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 。

  1. 由于事件的先手接收顺序,Thread1 一定比 Thread2 先执行。
  2. 在线程一中改变这个 全局变量,让他变成200。
  3. 我们就会发现在线程二中这个变量也会发生改变,如果我们想让这个变量产生局部变量的效果,可以发现我们无法实现这一需求。
    在这里插入图片描述

二 . 使用TLS变量:
把这个全局变量的声明改为:

__declspec(thread) int num = 100;

其他的代码都不变,我们就会发现,我们的线程都拥有了这个TLS变量的一份副本,对一个变量的线程中的修改不会改变另一个线程的值:
在这里插入图片描述


TLS回调函数

在安全领域中,TLS常被用来处理诸如反调试、抢占执行等操作。

TLS回调函数

实现方法:

  1. 首先加上一个编译选项
#pragma comment(linker,"/INCLUDE:__tls_used")
  1. 注册TLS函数
/*
* 注册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回调函数的程序中,在程序加载入内存时,首先执行TLS回调函数,然后再进入ImageBase+EntryPoint 进入程序入口(OEP)

示例:

#include <windows.h>
#include <iostream>
#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函数的部分!!!!!!

  • 程序执行: 进程创建 ,DLL_PROCESS_ATTACH
  • 第一个线程创建: DLL_THREAD_ATTACH
  • 第一个线程结束:DLL_THREAD_DETACH
  • 第二个线程创建:DLL_THREAD_ATTACH
  • 第二个线程销毁:DLL_THREAD_DETACH

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具有反反调试的功能!!!

posted @ 2022-11-05 21:28  hugeYlh  阅读(240)  评论(0编辑  收藏  举报  来源