s1eep123's blog.

TLS反调试

Word count: 1.1kReading time: 5 min
2022/10/20

TLS

what is TLS

Thread Local Storage(TLS),在Windows多线程编程中,访问全局变量时由于CPU时间片调度可能会导致出现意想不到的结果。最常见的方法就是对全局变量访问加锁。Windows为解决一个进程中多个线程同时访问全局变量。设计了TLS机制。TLS可以简单地由操作系统代为完成整个互斥过程,也可以由用户自己编写控制信号量的函数。当进程中的线程访问预先制定的内存空间时,操作系统会调用系统默认的或用户自定义的信号量函数,保证数据的完整性与正确性。

在执行含有TLS的程序时,首先依次加载用户编写的TLS回调函数数组(指针函数数组),接着转到OEP执行。因此基于TLS的反调试,原理是在实际的入口点(OEP)代码执行之前通过TLS回调函数执行检测调试器代码,达到TLS反调试实现的效果。下面来一段程序

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
#include <iostream>
#include<Windows.h>

#pragma comment (linker, "/INCLUDE:__tls_used") //通知链接器为TLS数据在PE文件头中添加数据

//TLS提供与DllMain类似,能够注册一系列的TLS回调函数Reason。
void NTAPI TlsCallBackFunction1(PVOID Handle, DWORD Reason, PVOID Reserve) {
::MessageBox(NULL, TEXT("Tls1"), TEXT("Tls reverse1"), MB_OK);
}

void NTAPI TlsCallBackFunction2(PVOID Handle, DWORD Reason, PVOID Reserve) {
::MessageBox(NULL, TEXT("Tls2"), TEXT("Tls reverse2"), MB_OK);
}

int main()
{
::MessageBox(NULL, TEXT("Main"), TEXT("Main fun"), MB_OK);
printf("%x,%x", TlsCallBackFunction1, &TlsCallBackFunction2);
}

#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_Callback[] = { TlsCallBackFunction1,TlsCallBackFunction2}; //TLS callback函数数组,依次执行
#pragma data_seg()

//程序先执行TlsCallBackFunction1,TlsCallBackFunction2,然后执行main函数

image-20221020212953470

注意的是,在TLS回调函数执行时,VC运行库msvcrt.dll,mfc.dll等并未载入,不能使用C库的函数(比如printf)。如果有需要使用,应该使用LoadLibrary()函数载入相应的库并使用GetProcAddress()获得函数地址。但此类操作可能会导致调试器的相关事件触发,不建议进行此类操作。

PE文件中,在可选头OptionHeader中的IMAGE_DATA_DIRECTORY数组中(IMAGE_NT_HEADERS.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS])保存了TLS表,

image-20221020212228324

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_TLS_DIRECTORY32 
{
DWORD StartAddressOfRawData; //  TLS初始化数据的起始地址
DWORD EndAddressOfRawData;// TLS初始化数据的结束地址 两个正好定位一个范围,范围放初始化的值
PDWORD AddressOfIndex;// TLS 索引的位置
PIMAGE_TLS_CALLBACK *AddressOfCallBacks;// Tls回调函数的数组指针
DWORD SizeOfZeroFill;// 填充0的个数
DWORD Characteristics;
} IMAGE_TLS_DIRECTORY32

image-20221020214352317

与不加TLS的程序相比,PE文件中会多一个.tls段

image-20221020212025236

TLS实现反调试

实现及原理

充分利用TLS回调函数在程序入口点之前就能获得程序控制权的特性,在TLS回调函数中进行反调试操作比传统的反调试技术有更好的效果。

NtQueryInformationProcess

demo如下

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
#include <iostream>
#include<Windows.h>
#include<winternl.h>

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

DWORD debugFlag = 0;

typedef NTSTATUS(NTAPI* pfnNtQueryInformationProcess)(
_In_ HANDLE ProcessHandle,
_In_ UINT ProcessInformationClass,
_Out_ PVOID ProcessInformation,
_In_ ULONG ProcessInformationLength,
_Out_opt_ PULONG ReturnLength
);

void NTAPI TlsCallBackFunction1(PVOID Handle, DWORD Reason, PVOID Reserve) {

pfnNtQueryInformationProcess NtQueryInformationProcess = NULL;
HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
if (hNtDll) {
NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess");
if (NtQueryInformationProcess) {
NtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort, &debugFlag, sizeof(DWORD), NULL);
}
}
if (debugFlag != 0) {
::MessageBox(NULL, TEXT("detect debugger"), TEXT("detect debugger"), MB_OK);
}
}

int main()
{
::MessageBox(NULL, arr, TEXT("test"), MB_OK);
system("pause");
}

#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_Callback[] = { TlsCallBackFunction1,NULL };
#pragma data_seg()

image-20221020220758521

IsDebuggerPresent

微软给我们提供了一个API函数用来检测当前程序是否正在被调试

函数原型

1
2
3
4
5
BOOL IsDebuggerPresent();

Return value
If the current process is running in the context of a debugger, the return value is nonzero.
If the current process is not running in the context of a debugger, the return value is zero.

demo

1
2
3
4
5
6
void NTAPI TlsCallBackFunction1(PVOID Handle, DWORD Reason, PVOID Reserve) {

if (IsDebuggerPresent()) {
::MessageBox(NULL, TEXT("detect debugger"), TEXT("detect debugger"), MB_OK);
};
}

image-20221020221725673

debugFlag的妙用

调试器附加进程时,NtQueryInformationProcess中为debugFlag赋值为0xffff ffff,未调试时debugFlag为0,借用debugFlag在不同状态下的值来对某些关键数据进行加密,来实现数据保护以及反调试的效果。例如一下demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
//printf("%x,%x", TlsCallBackFunction1, &TlsCallBackFunction2);
//printf("%x", debugFlag);
if (debugFlag != 0) {
printf("debuging");
}
//check successful
char arr[] = { 0x63, 0x68, 0x65, 0x63, 0x6B, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6C,0x21, 0x21, 0x00 };
for (int i = 0; i < 19; i++) {
arr[i] = arr[i] ^ debugFlag;
}
::MessageBox(NULL, arr, TEXT("test"), MB_OK);
system("pause");
}

未调试时弹出check successful,调试时数据亦或弹出乱码

image-20221020164047018

image-20221020164144222

CATALOG
  1. 1. TLS
    1. 1.0.1. what is TLS
  • 2. TLS实现反调试
    1. 2.0.1. 实现及原理
    2. 2.0.2. NtQueryInformationProcess
    3. 2.0.3. IsDebuggerPresent
    4. 2.0.4. debugFlag的妙用