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 @   hugeYlh  阅读(364)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示