在说TLS反调试技术之前,我们先看一下TLS技术是什么。
TLS是各线程的独立的数据存储空间,使用TLS技术可在线程内部独立使用或者修改进程的全局数据或是静态数据,就像对待自身的局部变量一样。TLS回调函数常用于反调试,因为TLS回调函数运行会先于EP代码执行。
若在编程中使用了TLS功能,PE头文件中就会设置TLS表项目
IMAGE_TLS_DIRECTORY结构体定义如下:
ypedef struct _IMAGE_TLS_DIRECTORY64 { ULONGLONG StartAddressOfRawData; ULONGLONG EndAddressOfRawData; ULONGLONG AddressOfIndex; // PDWORD ULONGLONG AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *; DWORD SizeOfZeroFill; union { DWORD Characteristics; struct { DWORD Reserved0 : 20; DWORD Alignment : 4; DWORD Reserved1 : 8; } DUMMYSTRUCTNAME; } DUMMYUNIONNAME; } IMAGE_TLS_DIRECTORY64; typedef struct _IMAGE_TLS_DIRECTORY32 { DWORD StartAddressOfRawData; DWORD EndAddressOfRawData; DWORD AddressOfIndex; // PDWORD DWORD AddressOfCallBacks; // PIMAGE_TLS_CALLBACK * DWORD SizeOfZeroFill; union { DWORD Characteristics; struct { DWORD Reserved0 : 20; DWORD Alignment : 4; DWORD Reserved1 : 8; } DUMMYSTRUCTNAME; } DUMMYUNIONNAME; } IMAGE_TLS_DIRECTORY32;
需要注意的是,AddressOfCallBack成员指向的是一个含有TLS回调函数的数组,这表明可以注册多个TLS回调函数。
TLS回调函数定义如下:
typedef VOID (NTAPI *PIMAGE_TLS_CALLBACK) ( PVOID DllHandle, //模块句柄 DWORD Reason, //调用TLS回调函数时机 PVOID Reserved // );
第二参数有四个状态:
#define DLL_PROCESS_ATTACH 1 #define DLL_THREAD_ATTACH 2 #define DLL_THREAD_DETACH 3 #define DLL_PROCESS_DETACH 0
我们可以看到TLS回调函数很像DllMain()函数。下面我们来一个例子加深对TLS的印象。
#include "stdafx.h" #include <Windows.h> #pragma comment(linker,"/INCLUDE:__tls_used") void PrintAtShell(WCHAR* wzMessage); DWORD WINAPI ThreadProc(LPVOID lParam); void NTAPI TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved); void NTAPI TLS_CALLBACK2(PVOID DllHandle, DWORD Reason, PVOID Reserved); int main() { HANDLE ThreadHandle = NULL; PrintAtShell(L"main() start! "); ThreadHandle = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); WaitForSingleObject(ThreadHandle, 60 * 1000); CloseHandle(ThreadHandle); PrintAtShell(L"main() end! "); return 0; } void PrintAtShell(WCHAR* wzMessage) { //检索指定设备句柄,STD_OUTPUT_HANDLE指示标准输出设备 HANDLE StdHandle = GetStdHandle(STD_OUTPUT_HANDLE); //将输入内容显示到控制台屏幕上,类似于printf,但是因为TLS回调函数先于main()函数执行,所以有可能printf()函数无法正常使用 WriteConsole(StdHandle, wzMessage,lstrlen(wzMessage), NULL, NULL); } DWORD WINAPI ThreadProc(LPVOID lParam) { PrintAtShell(L"ThreadProc() start! "); PrintAtShell(L"ThreadProc() end! "); return 0; } void NTAPI TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved) { WCHAR wzMessage[80] = { 0 }; wsprintf(wzMessage, L"TLS_CALLBACK1():DllHandle = %X,Reason = %d ", DllHandle, Reason); PrintAtShell(wzMessage); } void NTAPI TLS_CALLBACK2(PVOID DllHandle, DWORD Reason, PVOID Reserved) { WCHAR wzMessage[80] = { 0 }; wsprintf(wzMessage, L"TLS_CALLBACK2():DllHandle = %X,Reason = %d ", DllHandle, Reason); PrintAtShell(wzMessage); } #pragma data_seg(".CRT$XLX") PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1,TLS_CALLBACK2,0 }; #pragma data_seg()
输出结果:
TLS_CALLBACK1():DllHandle = 930000,Reason = 1
TLS_CALLBACK2():DllHandle = 930000,Reason = 1
main() start!
TLS_CALLBACK1():DllHandle = 930000,Reason = 2
TLS_CALLBACK2():DllHandle = 930000,Reason = 2
ThreadProc() start!
ThreadProc() end!
TLS_CALLBACK1():DllHandle = 930000,Reason = 3
TLS_CALLBACK2():DllHandle = 930000,Reason = 3
main() end!
TLS_CALLBACK1():DllHandle = 930000,Reason = 0
TLS_CALLBACK2():DllHandle = 930000,Reason = 0
因为我们定义了两个TLS回调函数,当进程刚刚启动时调用TLS回调函数,打印出1,然后main函数启动,之后又启动一个线程,触发TLS回调函数2,然后线程启动,关闭线程之后,调用TLS3,最后进程结束,触发0事件。
相信这时候对TLS回调函数有了一定理解了吧,现在我们开始正题,如何利用TLS回调函数进行反调试。
我们知道了TLS回调函数在main()函数执行之前执行,所以,我们可以定义一个TLS回调函数去检测当前进程有没有被调试。我们知道调试器一般会下int 3断点进行调试,我们只需在函数入口处检查是否下了int 3断点即可判断是否被调试。所以我们直接看代码吧:
#include "stdafx.h" #include <Windows.h> #pragma comment(linker,"/INCLUDE:__tls_used") void NTAPI MY_TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved); int main() { MessageBox(NULL, L"运行", L"警告", 0); return 0; } void NTAPI MY_TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved) { if (Reason == DLL_PROCESS_ATTACH) { //获得当前Exe模块基地址 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)GetModuleHandle(NULL); PIMAGE_NT_HEADERS32 pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + (DWORD)pDosHeader->e_lfanew); //执行函数入口 BYTE* OEP = (BYTE*)(pNtHeader->OptionalHeader.AddressOfEntryPoint + (DWORD)pDosHeader); //判断函数入口处有没有被调试器下int3断点 for (int i = 0; i < 200; i++) { if (OEP[i] == 0xCC) { MessageBox(NULL, L"调试", L"警告", 0); ExitProcess(0); } } } } #pragma data_seg(".CRT$XLX") PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { MY_TLS_CALLBACK,0 }; #pragma data_seg()
运行结果:
未调试状态:
用x32dbg或者OD调试状态: