• 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 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    如图:我们有一个全局的变量 int num =100 。

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

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

    __declspec(thread) int num = 100;
    
    • 1

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


    TLS回调函数

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

    TLS回调函数

    实现方法:

    1. 首先加上一个编译选项
    #pragma comment(linker,"/INCLUDE:__tls_used")
    
    • 1
    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这是TLS回调函数的结构体:
    我们创建的TLS函数必须是这个结构体的。

    typedef VOID
    (NTAPI *PIMAGE_TLS_CALLBACK) (
        PVOID DllHandle,	//
        DWORD Reason,
        PVOID Reserved
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    TLS函数何时被调用

    #define DLL_PROCESS_ATTACH 1//进程创建时
    #define DLL_THREAD_ATTACH 2 //线程创建时
    #define DLL_THREAD_DETACH 3//线程销毁时
    #define DLL_PROCESS_DETACH 0//进程销毁时
    
    • 1
    • 2
    • 3
    • 4

    TLS函数的执行

    在具有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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    运行如下:
    在这里插入图片描述
    可以发现: 在整个程序执行前,我们最先执行的是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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    运行如下:在我们debug模式下运行,会触发中断:
    在这里插入图片描述
    同理,使用debug x64/x86调式也是如此,触发禁止调试。

    但是在Od里没用,因为Od具有反反调试的功能!!!

  • 相关阅读:
    再获Gartner认可!持安科技获评ZTNA领域代表供应商
    股票量化交易接口的功能逻辑
    泛型和包装类
    java基于springboot+vue的旅游博客旅游经验分享系统
    【无标题】
    AW2013芯片讲解
    Echarts 实现X轴多维效果
    单源最短路的建图
    银行家算法——C语言实现
    5款.NET开源、免费、功能强大的图表库
  • 原文地址:https://blog.csdn.net/jj6666djdbbd/article/details/127708482