TLS反调试

1.概念

线程局部存储(Thread Local Storage,TLS)是一种线程级别的存储机制,它允许每个线程在运行时都拥有自己的私有变量

这些变量只能被该线程访问,而不会被其他线程所共享

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

补充:

1.竞争条件

当多个线程并发地访问共享资源时,可能会导致数据不一致或错误的结果

使用同步机制来确保数据的正确性

2.同步机制-锁

1.一线程对应一个锁

在使用锁进行同步时,只有一个线程能够持有锁,其他线程需要等待该线程释放锁才能继续执行

2.锁机制对应的开销

1.等待时间开销

如果一个线程请求获取锁,但此时锁已经被其他线程占用,则该线程需要等待。等待时间取决于锁的释放时间,如果等待时间较长,会导致线程阻塞,降低程序的执行效率

2.上下文切换开销

当一个线程持有锁并执行任务时,其他线程需要等待。这种等待会引发线程之间的频繁上下文切换,即从一个线程切换到另一个线程,这会增加额外的开销

3.锁竞争开销

如果多个线程竞争同一个锁,那么只有一个线程能够获得锁,其他线程需要等待。竞争锁的过程需要进行线程调度和原子操作,这会导致一定的开销

4.解决方案
1.减少锁的范围

只在必要的代码块中使用锁,将锁的粒度尽量缩小,以减少锁竞争和等待时间开销

2.使用更细粒度的锁

如果共享资源可以分解成独立的部分,可以考虑使用多个锁,以提高并发性能

3.使用无锁数据结构

无锁数据结构(如CAS原子操作)可以避免使用传统锁机制,减少锁竞争和上下文切换开销

4.合理调度线程数目

过多的线程可能会增加竞争和上下文切换开销,需要根据实际情况合理设置线程池大小

3.锁实例

import threading

# 创建一个互斥锁
lock = threading.Lock()

# 共享资源
shared_resource = 0

# 线程函数
def increment():
   global shared_resource
   
   # 获取锁
   lock.acquire()
   
   try:
       # 对共享资源进行操作
       shared_resource += 1
       print("当前共享资源值为:", shared_resource)
   finally:
       # 释放锁
       lock.release()

# 创建多个线程并启动
threads = []
for _ in range(5):
   t = threading.Thread(target=increment)
   threads.append(t)
   t.start()

# 等待所有线程执行完毕
for t in threads:
   t.join()

2.TLS局部存储的应用场景

1.线程池中的任务信息

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

2.线程特定的资源

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

3.线程相关的日志信息

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

4.线程局部的缓存

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

3.TLS变量实例

  1. 设置一个银行账户初始化为500元
  2. 创建2个线程
  3. 使用事件Event控制他们先执行线程1,2
  4. 线程1负责从银行账号里头取出来100元
  5. 执行打印操作
  6. 线程2再执行,打印原先的全局变量
  7. 结果发现还是500元没有发生变化
#include <iostream>
#include <Windows.h>

// 事件对象句柄,用于线程间同步
HANDLE hEvent;

// 线程局部存储变量,每个线程都有独立的 g_account 副本
__declspec(thread) int g_account = 500;

// 从 g_account 中取出指定金额
void withdraw(int amount) {
   g_account -= amount;
   std::cout << "取出" << amount << "元" << std::endl;
}

// 打印当前线程的 g_account 余额
void print_account() {
   std::cout << "Money left is:" << g_account << std::endl;
}

// 线程函数1
DWORD WINAPI thread_func1(LPVOID lpParam) {
   std::cout << "***********************线程1执行了***********************" << std::endl;
   withdraw(100);  // 取出100元
   print_account();  // 打印余额
   std::cout << "***********************线程1执行结束了***********************" << std::endl;
   SetEvent(hEvent);  // 设置事件为有信号状态,通知线程2
   return 0;
}

// 线程函数2
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);  // 创建线程1
   hThread[1] = CreateThread(NULL, 0, thread_func2, NULL, 0, NULL);  // 创建线程2

   WaitForMultipleObjects(2, hThread, TRUE, INFINITE);  // 等待两个线程执行完毕
   CloseHandle(hThread[0]);  // 关闭线程1句柄
   CloseHandle(hThread[1]);  // 关闭线程2句柄
   CloseHandle(hEvent);  // 关闭事件对象句柄
   std::cout << "对象已经全部销毁" << std::endl;
   return 0;
}

4.TLS回调函数

1.创建TLS回调函数的步骤:

1.添加#pragma comment (linker,”/INCLUDE:__tls_used”预编译指令

作用:告诉链接器将TLS断饱汉子啊可执行文件或者动态链接库(DLL)

2.注册TLS回调函数

#pragma data_seg(".CRT$XLX")//一个编译指令,用于指定数据段的名称
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { tlsCallback,0 };
#pragma data_seg()

注释

1.C++ 语言中

数据段是一块特殊的内存区域,用于存储程序中的全局变量和静态变量

2.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;
  }
}

4.完整版

 
#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;

}

5.TLS函数的反调试功能

1.原理

1.TLS回调的执行在OEP之前完成(它是由操作系统装载的时候检查TLS表时候进行调用)

2.main函数创建主线程执行又是在OEP之后

3.所以通过这样的方式,抢占执行目的

注释:

1.OEP

1.代表计算机程序中的一个标记点,表示程序的执行起点

2.在软件开发中,程序通常从一个特定的入口点开始执行,然后按照预定的顺序执行各个指令和功能。

3.原始入口点是程序中最基本的执行位置,它可以是主函数(例如C语言中的main函数)或其他被指定为入口点的函数。

4.在程序启动时,操作系统或运行时环境会定位到原始入口点,并从该点开始执行程序的逻辑。

2.抢占执行

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

3.执行顺序

1.查找文件中所有的TLS文件

2.把TLS文件按照优先级顺序摆列

1.按规则先加载所有DLL模块
2.按照DLL块中的TLS回调函数的链接顺序(后加载的TLS回调函数就先执行)
3.如果一个DLL模块中注册有多个TLS函数,则按照注册的顺序来执行
4.遵循后进先出原则
5.当程序线程被销毁或者被创建,系统也会调用TLS函数

(顺序和原则和上面相同)

4.具体实现

#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回调函数的优先执行来执行我们的反调试代码

检测函数:

1.CheckRemoteDebuggerPresent(hNewHandle, &result

检测hNewHandle对应的进程有没有被附加

2.hNewHandle

DuplicateHandle()函数

将原始句柄所对应的内核对象复制一份到当前进程的内核对象表中,并返回一个新的句柄

(这个新的句柄与原始句柄所对应的内核对象是一模一样的,包括对象的属性、访问权限等信息)

DuplicateHandle 函数复制的新的句柄与原始句柄是两个不同的对象

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

补充:

DuplicateHandle 函数用于复制一个句柄:

hSourceProcessHandle:需要复制句柄所在的进程句柄。可以是本地进程句柄,也可以是其他进程的进程句柄

hSourceHandle:需要复制的句柄。可以是任何类型的内核对象句柄,如文件句柄、事件句柄等

hTargetProcessHandle:复制后的句柄所属的进程句柄。通常为本地进程句柄

lpTargetHandle:指向新句柄的指针。该参数接收函数创建的新句柄

dwDesiredAccess:指定新句柄的访问权限。可以与原句柄的访问权限不同

bInheritHandle:指定新句柄是否可以被子进程继承。如果为 TRUE,则子进程可以继承新句柄;如果为 FALSE,则子进程不能继承新句柄

dwOptions:指定复制句柄的选项。常用的选项包括:

DUPLICATE_CLOSE_SOURCE:复制完成后关闭源句柄

DUPLICATE_SAME_ACCESS:新句柄与原句柄具有相同的访问权限

lpTargetHandle:接收函数返回值,如果函数执行成功,则它会返回一个指向新句柄的指针

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇