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" ) 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}; #pragma data_seg()
注意的是,在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表,
1 2 3 4 5 6 7 8 9 typedef struct _IMAGE_TLS_DIRECTORY32 { DWORD StartAddressOfRawData; DWORD EndAddressOfRawData; PDWORD AddressOfIndex; PIMAGE_TLS_CALLBACK *AddressOfCallBacks; DWORD SizeOfZeroFill; DWORD Characteristics; } IMAGE_TLS_DIRECTORY32
与不加TLS的程序相比,PE文件中会多一个.tls段
TLS实现反调试 实现及原理 充分利用TLS回调函数在程序入口点之前就能获得程序控制权的特性,在TLS回调函数中进行反调试操作比传统的反调试技术有更好的效果。
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()
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); }; }
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 () { if (debugFlag != 0 ) { printf ("debuging" ); } 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,调试时数据亦或弹出乱码