TLS
什么是TLS?
TLS是 Thread Local Storage 的縮寫,線程局部存儲。主要是為了解決多線程中變量同步的問題
如果需要要一個線程內部的各個函數調用都能訪問,但其它線程不能訪問的變量(被稱為static memory local to a thread 線程局部靜態變量),就需要新的機制來實現,這就是TLS
用途1:
TLS變量
線程A去修改TLS變量時,線程B不會受影響,因為每個線程都擁有一個TLS變量的副本
創建TLS變量
__declspe(thread) int g_tls = 1000;
用途2:
在安全領域中,TLS常被用于處理如反調試,搶占執行等操作
TLS回調函數
#include<iostream>
#include<Windows.h>
// 首先加上編譯選項
_declspec(thread) int g_tlsNum = 100;
#ifdef _WIN64
#pragma comment(linker, "/INCLUDE:_tls_used")
#else
#pragma comment(linker, "/INCLUDE:__tls_used")
#endifDWORD WINAPI threadProc(LPVOID lparam) {g_tlsNum = 300;printf("g_tlsNum=%d\n",g_tlsNum);return 0;
}void NTAPI t_TlsCallBack_A(PVOID DllHandle, DWORD Reason, PVOID Reserved);/*注冊TLS函數,.CRT$XLX的作用CRT表示使用C Runtime庫X表示標識名隨機L表示 TLS Callback sectionX也可以換成B~Y任意一個字符*/
// 注冊 TLS 回調
#ifdef _WIN64
#pragma const_seg(".CRT$XLX") // x64 下用 const_seg(只讀段)
EXTERN_C const // 禁用 C++ 的名稱修飾
#else
#pragma data_seg(".CRT$XLX") // x86 下用 data_seg(可讀寫段)
#endif//存儲回調函數地址 PIMAGE_TLS_CALLBACK pTLS_CALLBACKs,寫了幾個回調函就要往里面添加幾個,最后必須要有一個0
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { t_TlsCallBack_A,0 };#ifdef _WIN64
#pragma const_seg()
#else
#pragma data_seg()
#endif// 編寫Tls回調函數 參數1:模塊加載基址 參數2:調用的原因 參數3:保留
void NTAPI t_TlsCallBack_A(PVOID DllHandle, DWORD Reason, PVOID Reserved) {switch (Reason) {case DLL_PROCESS_ATTACH:printf("Hello Tls\n");break;case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH:break;}
}int main() {// 創建線程CreateThread(NULL, NULL, threadProc,NULL,NULL,NULL);return 0;
}
何時被調用
- #define DLL_PROCESS_ATTACH 1 // 進程創建時
- #define DLL_THREAD_ATTACH 2 // 線程創建時
- #define DLL_THREAD_DETACH 3 // 線程銷毀時
- #define DLL_PROCESS_DETACH 0 // 進程銷毀時
查看執行結果,我們會發現TLS是最先執行的,這樣我們就可以用這個回調函數來反調試一些調試器的加載,一般來說調試器在加載一個程序的時候,程序最先執行的代碼是OEP(Original Entry Point),但TLS在OPE之前執行
我們來修改一下代碼來寫一個簡單的反調試程序
void NTAPI t_TlsCallBack_A(PVOID DllHandle, DWORD Reason, PVOID Reserved) {switch (Reason) {case DLL_PROCESS_ATTACH:{BOOL result = FALSE;HANDLE hRealProcess = NULL;DuplicateHandle(GetCurrentProcess(), // 當前進程GetCurrentProcess(), // 偽句柄 (HANDLE)-1GetCurrentProcess(), // 目標進程(仍為當前進程)&hRealProcess, // 存儲真實句柄NULL, FALSE, DUPLICATE_SAME_ACCESS);CheckRemoteDebuggerPresent(hRealProcess, &result); // 這種只是最簡單的,現代調試器都會有反反調試的手段if (result) {MessageBox(NULL, L"檢測到有調試器加載", L"Warning", MB_OK | MB_ICONWARNING);ExitProcess(0);}break;}case DLL_THREAD_ATTACH:break;case DLL_THREAD_DETACH:break;case DLL_PROCESS_DETACH:break;}
}
直接雙擊運行程序發現是沒有問題的
我們來測試下有調試器加載的情況
(由于目前市面上常用的調試器都有反反調試的功能,我們這個簡單的反調試肯定是不會被檢測出來的,所以我們用Visual Studio自帶的調試器來看一下)
可以看到我們main函數還沒執行之前就已經觸發了檢測
TLS表
在我們的pe文件當中,有這么一張表,就是用來告訴Tls函數和變量在哪里存放著
在我們16張表中第10張表就是我們的Tls表對應存放的虛擬地址
// IMAGE_TLS_DIRECTORY64結構體
typedef struct _IMAGE_TLS_DIRECTORY64 {ULONGLONG StartAddressOfRawData; // Tls初始化數據的起始地址ULONGLONG EndAddressOfRawData; // Tls初始化數據的結束地址 (這個范圍存放初始化的值)ULONGLONG AddressOfIndex; // Tls索引的位置ULONGLONG AddressOfCallBacks; // PIMAGE_TLS_CALLBACK * (Tls回調函數的數組指針)DWORD SizeOfZeroFill; // 填充0的個數union {DWORD Characteristics;struct {DWORD Reserved0 : 20;DWORD Alignment : 4;DWORD Reserved1 : 8;} DUMMYSTRUCTNAME;} DUMMYUNIONNAME;} IMAGE_TLS_DIRECTORY64;
// 獲取Tls表信息
void getTlsInfo(const char* peFileBuffer) {// 獲取Tls表地址TableAddress repositionAddress = g_tableAddress[IMAGE_DIRECTORY_ENTRY_TLS];// 通過Rva得到文件地址DWORD fileAddress = rvaToFoa(repositionAddress.myVirtualAddress);// 解析結構體PIMAGE_TLS_DIRECTORY64 tlsDirectory = (PIMAGE_TLS_DIRECTORY64)(peFileBuffer + fileAddress);printf("Tls初始化數據的起始地址:0x%llX\n", tlsDirectory->StartAddressOfRawData);printf("Tls初始化數據的結束地址:0x%llX\n", tlsDirectory->EndAddressOfRawData);printf("Tls索引的位置:0x%llX\n", tlsDirectory->AddressOfIndex);printf("Tls回調函數的數組指針:0x%llX\n", tlsDirectory->AddressOfCallBacks);printf("填充0的個數:%d\n", tlsDirectory->SizeOfZeroFill);}