每當創建 / 終止進程的線程時會自動調用執行的函數。創建的主線程也會自動調用回調函數,且其調用執行先於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;
}