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 。
- 由于事件的先手接收顺序,Thread1 一定比 Thread2 先执行。
- 在线程一中改变这个 全局变量,让他变成200。
- 我们就会发现在线程二中这个变量也会发生改变,如果我们想让这个变量产生局部变量的效果,可以发现我们无法实现这一需求。
二 . 使用TLS变量:
把这个全局变量的声明改为:
__declspec(thread) int num = 100;
其他的代码都不变,我们就会发现,我们的线程都拥有了这个TLS变量的一份副本
,对一个变量的线程中的修改不会改变另一个线程的值:
TLS回调函数
在安全领域中,TLS常被用来处理诸如反调试、抢占执行等操作。
TLS回调函数
实现方法:
- 首先加上一个编译选项
#pragma comment(linker,"/INCLUDE:__tls_used")
- 注册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具有反反调试的功能!!!
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209669.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)