在说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
#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!\r\n");
ThreadHandle = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
WaitForSingleObject(ThreadHandle, 60 * 1000);
CloseHandle(ThreadHandle);
PrintAtShell(L"main() end!\r\n");
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!\r\n");
PrintAtShell(L"ThreadProc() end!\r\n");
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\r\n", 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\r\n", 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
#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()
运行结果:
未调试状态: