每当创建 / 终止进程的线程时会自动调用执行的函数。创建的主线程也会自动调用回调函数,且其调用执行先于EP代码。
1 TLS技术简介
TLS全称为Thread Local Storage,是Windows为解决一个进程中多个线程同时访问全局变量而提供的机制。TLS可以简单地由操作系统代为完成整个互斥过程,也可以由用户自己编写控制信号量的函数。当进程中的线程访问预先制定的内存空间时,操作系统会调用系统默认的或用户自定义的信号量函数,保证数据的完整性与正确性。
1.1 TLS回调函数
当用户选择使用自己编写的信号量函数时,在应用程序初始化阶段,系统将要调用一个由用户编写的初始化函数以完成信号量的初始化以及其他的一些初始化工作。此调用必须在程序真正开始执行到入口点之前就完成,以保证程序执行的正确性。
TLS回调函数具有如下的函数原型:
void NTAPI TlsCallBackFunction(PVOID Handle, DWORD Reason, PVOID Reserve);
1.2 TLS的数据结构
Windows的可执行文件为PE格式,在PE格式中,专门为TLS数据开辟了一段空间,具体位置为IMAGE_NT_HEADERS.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]。其中
DataDirectory的元素具有如下结构:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
对于TLS的DataDirectory元素,VirtualAddress成员指向一个结构体,结构体中定义了访问需要互斥的内存地址、TLS回调函数地址以及其他一些信息。
2 反调试实现及原理
充分利用TLS回调函数在程序入口点之前就能获得程序控制权的特性,在TLS回调函数中进行反调试操作比传统的反调试技术有更好的效果。
2.1 在程序中使用TLS
Microsoft提供的VC编译器都支持直接在程序中使用TLS,下文都将使用VC进行操作。要在程序中使用TLS,必须为TLS数据单独建一个数据段,用相关数据填充此段,并通知链接器为TLS数据在PE文件头中添加数据。为此,需要在程序源文件中添加如下代码:
#pragma comment(linker, "/INCLUDE:__tls_used")
#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK TlsCallBackArray[] = {
TlsCallBackFunction1,
TlsCallBackFunction2,
......
NULL
};
#pragma data_seg()
其中TlsCallBackArray数组中保存了所有的TLS回调函数指针。值得指出的是,数组必须以NULL指针结束,且数组中的每一个回调函数在程序初始化时都会被调用,程序员可按需要添加。但程序员不应当假设操作系统已何种顺序调用回调函数。如此则要求在TLS回调函数中进行反调试操作需要一定的独立性。
需要指出的是,在TLS回调函数执行时,VC运行库msvcrt.dll,mfc.dll等并未载入,不能使用C库的函数。如果有需要使用,应该使用LoadLibrary()函数载入相应的库并使用GetProcAddress()获得函数地址。但此类操作可能会导致调试器的相关事件触发,不建议进行此类操作。
OD反调试实现代码:
// TLS.cpp : 定义控制台应用程序的入口点。
//
//TLS回调函数在Main函数之前执行,也会在OD下断之前执行
// 运行结果:
//t_TlsCallBack_A->ThreadATTACH!
//t_TlsCallBack_B->ThreadATTACH!
//t_Thread->First Printf : TLS gt_szStr = 0x11111111 (第一个线程将gt_szStr修改为0x22222222)
//t_Thread->Second Printf : TLS gt_szStr = 0x22222222
//t_TlsCallBack_A->ThreadDetach!
//t_TlsCallBack_B->ThreadDetach!
//
//t_TlsCallBack_A->ThreadATTACH!
//t_TlsCallBack_B->ThreadATTACH!
//t_Thread->First Printf : TLS gt_szStr = 0x11111111 (但并未影响到第二个线程中gt_szStr的值)
//t_Thread->Second Printf : TLS gt_szStr = 0x22222222
//t_TlsCallBack_A->ThreadDetach!
//t_TlsCallBack_B->ThreadDetach!
#include "stdafx.h"
#include <Windows.h>
首先需要准备TLS变量与回调函数
//TLS变量
__declspec (thread) int gt_nNum = 0x11111111;
TCHAR gt_szStr[] = _T("TLS gt_szStr = 0x%p \r\n");
//TLS回调函数A (TLS回调函数在Main函数之前执行,也会在OD下断之前执行)
void NTAPI t_TlsCallBack_A(PVOID DLLHandle, DWORD Reason, PVOID Red)
{
//OD反调试
HWND hWnd = FindWindow(L"OllyDbg", NULL);
PostMessage(hWnd, WM_DESTROY, 0, 0);
if (Reason==DLL_THREAD_DETACH)//如果线程退出则打印信息
{
_tprintf(_T("t_TlsCallBack_A -> ThreadDetach!\r\n"));
}
if (Reason == DLL_THREAD_ATTACH)//如果线程创建则打印信息
{
_tprintf(_T("t_TlsCallBack_A -> ThreadATTACH!\r\n"));
}
return;
}
//TLS回调函数B
void NTAPI t_TlsCallBack_B(PVOID DLLHandle, DWORD Reason, PVOID Red)
{
if (Reason == DLL_THREAD_DETACH)//如果线程退出则打印信息
{
_tprintf(_T("t_TlsCallBack_B -> ThreadDetach!\r\n"));
}
if (Reason == DLL_THREAD_ATTACH)//如果线程创建则打印信息
{
_tprintf(_T("t_TlsCallBack_B -> ThreadATTACH!\r\n"));
}
return;
}
//声明一个区段,注册TLS回调函数信息
#pragma data_seg(".CRT$XLB")
// 保存TLS回调函数的数组,NULL表示结束
PIMAGE_TLS_CALLBACK p_thread_callbackp[] = { t_TlsCallBack_A, t_TlsCallBack_B,NULL };
#pragma data_seg()
//编写一个多线程程序,并在内部使用TLS
DWORD WINAPI t_ThreadFun(PVOID pParam)
{
_tprintf(_T("t_Thread -> First Printf:"));
_tprintf(gt_szStr, gt_nNum);
gt_nNum = 0x22222222;//其中一个线程修改了gt_nNum
_tprintf(_T("t_Thread -> Second Printf:"));
_tprintf(gt_szStr, gt_nNum);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
CreateThread(NULL, 0, t_ThreadFun, NULL, 0, 0);
Sleep(100); //暂停100毫秒,让第一个线程运行完
_tprintf(_T("\r\n"));
CreateThread(NULL, 0, t_ThreadFun, NULL, 0, 0);
Sleep(100);
_tprintf(_T("\r\n"));
system("pause");
return 0;
}