TLS反调试

news2024/11/27 15:27:00

一、TLS概念

线程局部存储(Thread Local Storage,TLS)是一种线程级别的存储机制,它允许每个线程在运行时都拥有自己的私有变量,这些变量只能被该线程访问,而不会被其他线程所共享

1、TLS的出现是为了解决什么问题呢?

》在多线程编程中,使用线程局部存储可以避免竞争条件和锁等同步机制的开销,提高程序的性能和并发能力。线程局部存储通常通过使用操作系统提供的API来实现,比如Windows平台上的TLS函数,或者POSIX线程库中的pthread_key_create和pthread_setspecific函数等。

线程局部存储通常被用于存储与线程相关的上下文信息,比如线程ID、日志记录器、线程池等资源的指针、临时变量等。在多线程环境中,使用线程局部存储可以避免共享全局变量带来的线程安全问题,同时也可以使代码更加简洁和可读,提高代码的可维护性。但是对于像飞机票抢票程序中,多个线程需要同时访问飞机票的数目,而且需要对飞机票数目进行修改和更新。如果使用TLS变量来进行管理,每个线程都会拥有自己的私有飞机票数目,无法实现多个线程之间对飞机票数目的共享和同步,也就无法实现正确的抢票功能。因此,使用全局变量或共享内存等线程间共享的方式来管理飞机票数目,是更为合适的选择。

2、为什么使用线程局部存储可以避免竞争条件和锁等同步机制的开销呢?

1)线程局部存储是每个线程私有的,不同线程之间互相独立,因此不需要进行同步机制的操作,避免了锁等同步机制的开销。而全局变量或静态变量则需要在多线程环境下进行同步操作,开销比较大。

2)在多线程环境中,使用线程局部存储可以避免竞争条件的出现,从而提高程序的并发能力。竞争条件是指多个线程同时对共享资源进行读写操作时可能会出现的不可预测的行为,如数据损坏、死锁等。线程局部存储使得每个线程都有自己的私有变量,避免了对共享资源的争用,从而避免了竞争条件的出现。

3)在某些情况下,线程局部存储还可以减少线程之间的上下文切换次数。当线程需要频繁地访问全局变量时,由于多个线程需要竞争同一个锁,导致线程之间频繁地切换上下文,降低了程序的性能。而使用线程局部存储可以将变量存储在每个线程的私有空间中,避免了线程之间的竞争,从而减少了上下文切换的次数。

3、那么既然是私有变量,不在线程之间共享,那我为什么不直接在线程里面定义一个私有变量存储?

》在多线程编程中,如果在线程函数内部直接定义一个私有变量,那么这个变量将会是在栈上分配的,每次调用线程函数时都会创建一个新的变量,而在线程函数返回后,这个变量将被销毁,这样就无法在多次调用线程函数时保持这个变量的状态。因此,线程局部存储提供了一种在线程之间共享变量的方法,同时又保证了每个线程都有自己的私有变量,可以在多次调用线程函数时保持变量的状态。

线程局部存储通过为每个线程创建一个唯一的指针来实现。线程在首次访问线程局部存储时,会根据这个指针来分配一个私有变量的存储空间,并将这个指针存储在当前线程的线程控制块(TCB)中。之后,每次访问线程局部存储时,都会通过这个指针来访问私有变量的存储空间。这样,每个线程都拥有自己的私有变量,并且可以在多次调用线程函数时保持变量的状态,同时又不会和其他线程的私有变量产生冲突。

二、TLS局部存储的应用场景

TLS变量主要应用于需要在线程之间保持状态的场景,这种状态可能是线程私有的,无法和其他线程共享。以下是一些适合使用TLS变量的场景:

  1. )线程池中的任务信息。在线程池中,每个线程需要处理多个任务,需要保存当前正在处理的任务信息,可以使用TLS变量来保存每个线程的任务信息。

  2. )线程特定的资源。例如,图形界面程序中,每个线程可能需要访问不同的窗口和控件,可以使用TLS变量来保存每个线程访问的窗口和控件信息。

  3. )线程相关的日志信息。在多线程程序中,日志输出需要记录线程信息,可以使用TLS变量来保存每个线程的日志信息。

  4. )线程局部的缓存。在一些高性能的应用程序中,为了避免频繁的内存分配和释放,可以使用线程局部的缓存来保存一些临时变量,可以使用TLS变量来实现。

总之,TLS变量适用于需要在线程之间保持状态的场景,而且这种状态需要是线程私有的,无法和其他线程共享。使用TLS变量可以避免使用锁等同步机制,从而提高程序的性能和并发能力。

不知道大家有没有理解,重点就是,在有些场景下,你不是需要一种生命周期比较长的变量吗?但是在C艹当中,生命周期比较长的,不随线程销毁的,不就是全局变量和静态变量static吗?事实上我们一般都使用static多一些,这是因为普通的全局变量被跨文件读写,有很大的安全性问题,而且很难排错。但是这种static不是可以被这个文件当中的所有线程/函数共享吗?这时候你是不是就需要加一些个同步机制来确保你属于你这个线程的变量不被别的线程修改,这样就需要一系列的PV操作,来加锁解锁,这就造成了很大的额外开销,因此程序猿们就想到了线程局部存储TLS这项技术,来去掉这些没必要的开销来实现线程的同步,当然如果你的程序设计本身就是大家一起去修改这个变量,就不要使用TLS了,就正常PV实现互斥同步就🆗了。

三、使用TLS变量的小案例

下面我们来简单写一段代码来测试一下TLS变量的用途:

这段代码里主要是设置一个银行账户初始化为500元,然后创建2个线程,使用事件Event控制他们先执行线程1,然后再执行线程2,线程1负责从银行账号里头取出来100元,然后执行打印操作,最后线程2再执行,打印原先的全局变量,结果发现还是500元没有发生变化。


#include <iostream>
#include<Windows.h>

HANDLE hEvent;

__declspec (thread) int g_account = 500;

void withdraw(int amount) {
    g_account -= amount;
    std::cout << "取出" << amount <<"元" << std::endl;
}

void print_account() {
    std::cout << "Money left is:" << g_account << std::endl;
}

DWORD WINAPI thread_func1(LPVOID lpParam) {
    std::cout << "***********************线程1执行了***********************" << std::endl;
    withdraw(100);
    print_account();
    std::cout << "***********************线程1执行结束了***********************" << std::endl;
    SetEvent(hEvent);
    return 0;
}

DWORD WINAPI thread_func2(LPVOID lpParam) {

    WaitForSingleObject(hEvent, INFINITE);
    std::cout << "***********************线程2执行了***********************" << std::endl;
    print_account();
    std::cout << "***********************线程2执行结束了***********************" << std::endl;
    return 0;
}

int main()
{
    HANDLE hThread[2];
    hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    hThread[0] = CreateThread(NULL, 0, thread_func1, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, thread_func2, NULL, 0, NULL);

    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);
    CloseHandle(hEvent);
    std::cout << "对象已经全部销毁" << std::endl;
    return 0;

}

执行结果:

四、TLS回调函数

下面我们进入本文的关键内容的学习:TLS回调函数,反调试,线程抢占执行

1、创建TLS回调函数步骤:

1)添加#pragma comment(linker,"/INCLUDE:__tls_used") 预编译指令,用于告诉链接器将TLS(线程本地存储)段包含在可执行文件或动态链接库(DLL)中。

#pragma comment(linker,"/INCLUDE:__tls_used")

 2)注册TLS回调函数

#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { tlsCallback,0 };
#pragma data_seg()

#pragma data_seg 是一个编译指令,用于指定数据段的名称。在 C++ 语言中,数据段是一块特殊的内存区域,用于存储程序中的全局变量和静态变量。

在 Windows 操作系统上,数据段还可以用于存储特殊类型的数据,例如 TLS(线程本地存储)回调函数。TLS回调函数是一种函数指针,当线程被创建或销毁时会被自动调用。TLS回调函数可以用于执行一些线程特定的初始化或清理工作。

3)实现TLS回调函数

void NTAPI tlsCallback(PVOID DllHandle, DWORD Reason, PVOID Reserved) {
    switch (Reason) {
    case DLL_PROCESS_ATTACH:
        std::cout << "进程创建了" << std::endl;
        break;
    case DLL_THREAD_ATTACH:
        std::cout << "线程创建了" << std::endl;
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
}

以下是创建TLS回调函数的完整代码:


#include <iostream>
#include<Windows.h>
//1、添加预处理指令
#pragma comment(linker,"/INCLUDE:__tls_used")
HANDLE hEvent;

__declspec (thread) int g_account = 500;

//3、实现TLS回调函数
void NTAPI tlsCallback(PVOID DllHandle, DWORD Reason, PVOID Reserved) {
    switch (Reason) {
    case DLL_PROCESS_ATTACH:
        std::cout << "进程创建了" << std::endl;
        break;
    case DLL_THREAD_ATTACH:
        std::cout << "线程创建了" << std::endl;
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
}

//2、注册TLS函数
#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { tlsCallback,0 };
#pragma data_seg()

void withdraw(int amount) {
    g_account -= amount;
    std::cout << "取出" << amount <<"元" << std::endl;
}

void print_account() {
    std::cout << "Money left is:" << g_account << std::endl;
}

DWORD WINAPI thread_func1(LPVOID lpParam) {
    std::cout << "***********************线程1执行了***********************" << std::endl;
    withdraw(100);
    print_account();
    std::cout << "***********************线程1执行结束了***********************" << std::endl;
    SetEvent(hEvent);
    return 0;
}

DWORD WINAPI thread_func2(LPVOID lpParam) {

    WaitForSingleObject(hEvent, INFINITE);
    std::cout << "***********************线程2执行了***********************" << std::endl;
    print_account();
    std::cout << "***********************线程2执行结束了***********************" << std::endl;
    return 0;
}

int main()
{
    HANDLE hThread[2];
    hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    hThread[0] = CreateThread(NULL, 0, thread_func1, NULL, 0, NULL);
    hThread[1] = CreateThread(NULL, 0, thread_func2, NULL, 0, NULL);

    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);
    CloseHandle(hEvent);
    std::cout << "对象已经全部销毁" << std::endl;
    return 0;

}

可以看到TLS运行在程序的最前面,于是我们可以利用这个特性来实现一个小功能

 五、利用TLS回调函数实现反调试功能

1、原理部分

基于前面提到的特性,我们知道TLS回调的执行在OEP之前完成,它是由操作系统装载的时候检查TLS表时候进行调用的,而main函数创建主线程执行又是在OEP之后,所有我们可以利用这个特点来进行程序的反调试,达到抢占执行的目的。

我们先来分析一下TLS抢占执行的原理:

TLS(线程本地存储)回调函数是在程序启动时由操作系统调用的,这意味着它们可以在 main 函数执行之前执行。TLS 回调函数的执行顺序由操作系统决定,通常与 TLS 回调函数在程序中的定义顺序无关。
在 Windows 操作系统上,TLS 回调函数的执行顺序如下:

1)操作系统在加载可执行文件时,会查找文件中所有的 TLS 回调函数,并将它们按照优先级排序。优先级值越小的回调函数,优先级越高。注意:这里加载的时候就已经检查并执行了,不用从OEP进去后从主线程那边进去执行!

2)操作系统会在程序启动时(即调用 main 函数之前)调用这些 TLS 回调函数。调用顺序为优先级高的回调函数先调用,优先级低的回调函数后调用。

3)当线程被创建或销毁时,系统也会调用与 TLS 相关的回调函数。这些回调函数的调用顺序与程序启动时的回调函数调用顺序相同。

因此,TLS 回调函数可以在程序启动时执行一些初始化工作,例如初始化全局变量或资源。此外,它们也可以用于执行一些线程特定的初始化或清理工作,例如初始化线程局部变量或释放线程局部资源。

2、案例的具体实现

下面我们来具体实现一下这个小栗子,作用就是使用调试软件附加的时候,自动退出程序的目的,注意我们这里的测试软件使用的是win32dbg因为od里面实现了反反调试的手段,如果大家感兴趣后面可以安排一期来解决这个问题。

#include <iostream>
#include<Windows.h>

#pragma comment(linker,"/INCLUDE:__tls_used")


void NTAPI tlsCallback(PVOID DllHandle, DWORD Reason, PVOID Reserved) {
    
    if (Reason == DLL_PROCESS_ATTACH) {
        BOOL result = false;
        HANDLE hNewHandle = 0;
        DuplicateHandle(
            GetCurrentProcess(),
            GetCurrentProcess(),
            GetCurrentProcess(),
            &hNewHandle,
            NULL, NULL,
            DUPLICATE_SAME_ACCESS
        );
        CheckRemoteDebuggerPresent(hNewHandle, &result);
        if (result) {
            MessageBoxA(0, "程序被调试了!", "警告", MB_OK);
            ExitProcess(0);
        }
    }
}

#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { tlsCallback,0 };
#pragma data_seg()

int main() {

    printf("main函数执行了\n");
    
}

这段代码的核心就是通过TLS回调函数的优先执行来执行我们的反调试代码,在反调试代码当中通过CheckRemoteDebuggerPresent(hNewHandle, &result)函数来检测hNewHandle对应的进程有没有被附加。这里的hNewHandle是通过DuplicateHandle()函数将原始句柄所对应的内核对象复制一份到当前进程的内核对象表中,并返回一个新的句柄,这个新的句柄与原始句柄所对应的内核对象是一模一样的,包括对象的属性、访问权限等信息。

那么为什么要费劲复制一个新的句柄呢?

》DuplicateHandle 函数复制的新的句柄与原始句柄是两个不同的对象,它们具有不同的内存地址和句柄值。这意味着,对新的句柄的操作不会影响原始句柄,而对原始句柄的操作也不会影响新的句柄。在这种情况下,即使恶意软件在外部修改了原始句柄的属性或者访问权限,也不会影响到新的句柄,从而保证了检测调试器的准确性。

补充:DuplicateHandle 函数用于复制一个句柄,它的参数含义如下:

hSourceProcessHandle:需要复制句柄所在的进程句柄。可以是本地进程句柄,也可以是其他进程的进程句柄。
hSourceHandle:需要复制的句柄。可以是任何类型的内核对象句柄,如文件句柄、事件句柄等。
hTargetProcessHandle:复制后的句柄所属的进程句柄。通常为本地进程句柄。
lpTargetHandle:指向新句柄的指针。该参数接收函数创建的新句柄。
dwDesiredAccess:指定新句柄的访问权限。可以与原句柄的访问权限不同。
bInheritHandle:指定新句柄是否可以被子进程继承。如果为 TRUE,则子进程可以继承新句柄;如果为 FALSE,则子进程不能继承新句柄。
dwOptions:指定复制句柄的选项。常用的选项包括:
DUPLICATE_CLOSE_SOURCE:复制完成后关闭源句柄。
DUPLICATE_SAME_ACCESS:新句柄与原句柄具有相同的访问权限。
lpTargetHandle:接收函数返回值,如果函数执行成功,则它会返回一个指向新句柄的指针。 

执行结果:

点击确定之后:

注意这里必须使用x32dbg,因为其他的调试软件一般都有反反调试的功能!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/524796.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【 Python 全栈开发 ⑭ 】数据操作方法

文章目录 一、运算符&#xff08;1&#xff09;&#xff1a;加号二、运算符&#xff08;2&#xff09;&#xff1a;乘号三、in 与 not in四、len()五、del六、max() 与 min()七、数据类型转换 一、运算符&#xff08;1&#xff09;&#xff1a;加号 运算符 “” 的作用是 “合…

〖Web全栈开发②〗—网络编程基础(下)

〖Web全栈开发②〗—网络编程基础&#xff08;下&#xff09; &#xff08;一&#xff09;TCP 网络应用程序开发流程1. TCP 网络应用程序开发流程的介绍2. TCP 客户端程序开发流程的介绍3. TCP 服务端程序开发流程的介绍4. 小结 &#xff08;二&#xff09;socket之send和recv原…

4.LiCTF NSSCTF WEB方向部分 WriteUp

文章目录 0x01、我FLAG呢&#xff1f;【源码信息泄露js信息泄露】0x02、导弹迷踪【js信息泄露】0x03、Follow me and hack me【hackbarburp备份文件】0x04、PHP是世界上最好的语言&#xff01;&#xff01;【代码执行 getshell】0x05、Vim yyds【命令执行 getshell】0x06、作业…

孙鑫VC++第一章 Windows内部运行原理

1. Windows应用程序&#xff0c;操作系统&#xff0c;计算机硬件之间的相互关系 1箭头表示操作系统控制输出设备2箭头表示操作系统可以得到输入设备信息3箭头表示应用程序通知操作系统执行具体操作 操作系统提供给应用程序的接口 API4箭头表示输入设备变化告诉应用程序 Window…

线性表之双向链表(详解)

&#x1f355;博客主页&#xff1a;️自信不孤单 &#x1f36c;文章专栏&#xff1a;数据结构与算法 &#x1f35a;代码仓库&#xff1a;破浪晓梦 &#x1f36d;欢迎关注&#xff1a;欢迎大家点赞收藏关注 文章目录 &#x1f365;前言&#x1f352;双向链表1. 带头双向循环链表…

【C++】通序录管理系统

1、缘起 最近&#xff08;2023-04-24&#xff09;学习完了 C 编程语言的 基础语法&#xff0c;然后将这些基础语法的知识点整合到一起&#xff0c;实现一个 通讯录管理系统。以此来巩固以前所学习过的知识点&#xff0c;以求在后续的学习中能够灵活应用。 2、系统需求 通讯录是…

ChatGPT结合本地数据_llamaindex

1 功能 大模型学习的主要是通用数据&#xff0c;而用户可能需要让ChatGPT在本地的知识库中寻找答案。 普通用户不太可能训练大模型&#xff1b;由于本地数据格式丰富&#xff0c;内容烦多&#xff0c;且考虑到使用成本和token大小限制&#xff0c;也不可能在每次提问时都将所有…

balenaEtcher v1.18.1 开源跨平台镜像文件快速刻录工具

balenaEtcher 是一款开源免费的跨平台镜像文件快速刻录工具&#xff0c;使用体验感觉比软碟通UltraISO好用多了&#xff0c;推荐使用。它可以帮助用户快速将 ISO 文件、IMG 文件或者其他格式的镜像文件刻录到 USB 驱动器、SD 卡或者其他可烧录介质上。它支持 Windows、macOS 和…

50 Projects 50 Days - Blurry Loading 学习记录

项目地址 Blurry Loading 展示效果 Blurry Loading 实现思路 元素组成只需要有一张图片和中间的文本即可。针对动态过程分析初始和终止状态即可&#xff0c;初始时图片全模糊&#xff0c;文本显示0%&#xff1b;终止时&#xff0c;图片完全不模糊&#xff0c;文本会显示100…

Junit 单元测试框架(简单使用)

目录 一、注解 1. Test 2. BeforeEach 和 BeforeAll 3. AfterEach 和 AfterAll 二、断言 1. Assertions类 1.1 assertEquals 和 assertNotEquals 1.2 assertTrue 和 assertFalse 1.3 assertNull 和 assertNotNull 三、用例执行顺序 1. 方法的排序 —— Order 四、…

人工智能轨道交通行业周刊-第44期(2023.5.8-5.14)

本期关键词&#xff1a;智能列控、苏州城轨智慧大脑、智慧乘务系统、深铁智慧运维、铁路遥感、3D视觉 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通R…

【C++入门攻略】和【编程常见问题】

常见问题 vsstudio快捷键 快速注释组合键 ctrlk ctrlc 取消注释快捷键 ctrlk ctrl u 支持垃圾回收机制 大多数面向对象编程语言具有垃圾回收机制。早期的C语言不具备垃圾回收机制&#xff0c;这意味着申请的内存资源在使用完成后&#xff0c;需要程序员自己释放。直到C11标…

1066 Root of AVL Tree(51行代码+超详细注释)

分数 25 全屏浏览题目 切换布局 作者 CHEN, Yue 单位 浙江大学 An AVL tree is a self-balancing binary search tree. In an AVL tree, the heights of the two child subtrees of any node differ by at most one; if at any time they differ by more than one, rebala…

孙鑫VC++第一章 Windows程序内部运行机制

目录 1.1 API和SDK 1.2 窗口和句柄 1.3 消息和队列 1.4 WinMain 1.4.1 WinMain函数的定义 1.4.2 窗口的创建 1.4.3 消息循环 1.4.4 窗口过程函数 1.1 API和SDK API:Windows操作系统提供给应用程序编程的接口。 SDK&#xff08;软件开发包&#xff09;:用于开发的所有资…

swing列表框_强制存储的DefaultListModel和DefaultComboBoxModel

package com.aynu.layout;import javax.swing.*; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener;public class DefaultListModelTest {JFrame jf new JFrame("测试DefaultListModel");JTextField bookNa…

JVM学习(二)

1. JVM 运行时内存 Java 堆从 GC 的角度还可以细分为: 新生代 ( Eden 区 、 From Survivor 区 和 To Survivor 区 )和 老年 代。 1.1. 新生代 是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象&#xff0c;所以新生代会频繁触发 MinorGC 进行垃圾回收。新…

《如何评价北化面向对象江某英之我是传奇》

点进来的都是家人了&#xff0c;来&#xff0c;今天带你们一起速通江某英的面向对象。 首先&#xff0c;我们先看一下江某英的教学安排&#xff0c;所谓知己知彼&#xff0c;百战不殆。 一共是九个章节&#xff0c;但是最后一个总复习没讲&#xff0c;这不是为难我们吗&#x…

【移动端网页布局】Flex 弹性布局案例 ② ( 顶部固定定位搜索栏 | 固定定位盒子居中对齐 | 二倍精灵图设置 | CSS3 中的垂直居中对齐 )

文章目录 一、顶部固定定位搜索栏1、固定定位盒子居中对齐2、设置最大宽度和最小宽度3、使用 Flex 弹性布局管理宽度4、二倍精灵图设置5、CSS3 中的垂直居中对齐 - 行高 内容高度 ( 总高度 - 边框高度 - 内边距高度 ) 二、代码示例1、HTML 标签结构2、CSS 样式3、展示效果 一、…

Pytroch nn.Unfold() 与 nn.Fold()图码详解

文章目录 Unfold()与Fold()的用途nn.Unfold()Unfold()与Fold() 变化模式图解 nn.Fold()单通道 滑动窗口无重叠模拟图片数据&#xff08;b,3,9,9&#xff09;&#xff0c;通道数 C 为3&#xff0c;滑动窗口无重叠。单通道 滑动窗口有重叠。 卷积等价于&#xff1a;Unfold Matri…

国民技术N32G430开发笔记(20)- FreeRTOS的移植

FreeRTOS的移植 1、官网下载FreeRTOSv202212.01&#xff0c;搜索官网下载即可。 2、新建一个FreeRTOSDemo的工程&#xff0c;可以把之前的工程中的Bootloader工程复制一份。 3、打开下载的freertos代码将相应代码移植到我们的工程中。 protable文件夹&#xff0c;因为是gcc环…