Windows逆向工程提升之IMAGE_TLS_DIRECTORY
- 公开视频 -> 链接点击跳转公开课程
- 博客首页 -> 链接点击跳转博客主页
目录
TLS的作用
TLS的实现
静态 TLS
动态 TLS
内部实现
回调机制
TLS Directory 的结构
TLS的作用
-
TLS (Thread Local Storage) 是一种用于为多线程应用程序提供线程独立存储空间的机制。在多线程程序中,每个线程可以有自己独特的一组数据,互不干扰。
-
保存线程状态数据(如线程上下文)。
-
避免线程之间共享全局变量导致的竞争和冲突。
-
为每个线程提供独立的缓存、统计等,不需要使用锁机制。
TLS的实现
静态 TLS
在编译时分配空间(通过 __declspec(thread) 或 thread_local 关键字)。
-
通过使用
__declspec(thread)
声明 TLS 数据。 -
静态 TLS 在编译时分配,系统会自动初始化和清理。
-
适用于预定义的 TLS 数据场景。
-
#include <windows.h> #include <stdio.h> // 声明线程局部存储变量 __declspec(thread) int tlsData = 0; // 线程函数 DWORD WINAPI ThreadProc(LPVOID lpParameter) { // 为当前线程初始化 TLS 数据 tlsData = (int)(size_t)lpParameter; // 使用 TLS 数据 printf("Thread %d: TLS Data = %d\n", GetCurrentThreadId(), tlsData); // 模拟工作 Sleep(1000); return 0; } int main() { // 创建线程 const int threadCount = 3; HANDLE threads[threadCount]; for (int i = 0; i < threadCount; ++i) { threads[i] = CreateThread(NULL, 0, ThreadProc, (LPVOID)(size_t)(i + 1), 0, NULL); } // 等待线程完成 WaitForMultipleObjects(threadCount, threads, TRUE, INFINITE); // 清理句柄 for (int i = 0; i < threadCount; ++i) { CloseHandle(threads[i]); } return 0; }
动态 TLS
运行时通过 API(如 TlsAlloc, TlsFree)动态管理。
-
使用
TlsAlloc()
分配一个 TLS 索引,动态管理每个线程的 TLS 数据。 -
每个线程负责分配、获取和释放自己的数据。
#include <windows.h>
#include <stdio.h> // 全局 TLS 索引
DWORD g_TlsIndex; // 线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter) { // 为当前线程分配 TLS 数据 int* tlsData = (int*)malloc(sizeof(int)); *tlsData = (int)(size_t)lpParameter; // 将线程传递的参数放入 TLS 数据 TlsSetValue(g_TlsIndex, tlsData); // 使用 TLS 数据 printf("Thread %d: TLS Data = %d\n", GetCurrentThreadId(), *tlsData); // 模拟工作 Sleep(1000); // 释放 TLS 数据 free(tlsData); return 0;
} int main() { // 1. 分配 TLS 索引 g_TlsIndex = TlsAlloc(); if (g_TlsIndex == TLS_OUT_OF_INDEXES) { printf("Failed to allocate TLS Index\n"); return 1; } // 2. 创建线程 const int threadCount = 3; HANDLE threads[threadCount]; for (int i = 0; i < threadCount; ++i) { threads[i] = CreateThread(NULL, 0, ThreadProc, (LPVOID)(size_t)(i + 1), 0, NULL); } // 等待线程完成 WaitForMultipleObjects(threadCount, threads, TRUE, INFINITE); // 3. 清理 for (int i = 0; i < threadCount; ++i) { CloseHandle(threads[i]); } // 释放 TLS 索引 TlsFree(g_TlsIndex); return 0;
}
内部实现
-
每个线程都会分配一个线程环境块(Thread Environment Block,TEB),TEB 结构中包含一个用于存储 TLS 数据的区域:
-
动态 TLS 的数据存储在 TEB 的
TlsSlots
数组中,每个槽对应一个TlsAlloc()
返回的索引。 -
静态 TLS 的数据在程序加载时由系统内存分配,并初始化到对应的线程。
特性 动态分配 TLS (Win32 API) 静态分配 TLS (__declspec(thread)) 易用性 手动管理,需自己分配和释放 TLS 数据 自动完成线程初始化和销毁,使用方便 性能 每个访问可能需要一层索引查找,性能稍低 编译时分配,直接访问内存,性能高 生命周期 动态分配,程序员负责管理 生命周期由操作系统控制 灵活性 可以动态创建任意数量的 TLS 只能事先声明固定的 TLS 变量
回调机制
-
TLS 提供线程生命周期管理的机制:TLS 回调函数。这些回调函数会在以下情况下被调用:
-
线程附加(Thread Attach):当线程启动时,初始化 TLS 数据。
-
线程分离(Thread Detach):当线程结束时,清理 TLS 数据。
#include <windows.h> #ifdef _WIN64
#pragma comment (linker, "/INCLUDE:_tls_used")
#else
#pragma comment (linker, "/INCLUDE:__tls_used")
#endif //#pragma comment (linker, "/INCLUDE:pTLS_CALLBACKs") _declspec(thread) DWORD dw = 0x12345678;
_declspec(thread) DWORD dw1 = 0xCCCCCCCC;void NTAPI TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved) //TLS callback function
{}void NTAPI TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved) //TLS callback function
{}#ifdef _WIN64
#pragma const_seg(".CRT$XLB")
EXTERN_C const
#else
#pragma data_seg(".CRT$XLX")
#endif PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK, TLS_CALLBACK1,0 };#ifdef _WIN64
#pragma const_seg()
#else
#pragma data_seg()
#endif int main(void)
{return 0;
}
TLS Directory 的结构
TLS 数据由 IMAGE_TLS_DIRECTORY 结构描述,其定义如下
typedef struct _IMAGE_TLS_DIRECTORY { ULONGLONG StartAddressOfRawData; // TLS 数据起始地址(RVA) ULONGLONG EndAddressOfRawData; // TLS 数据结束地址(RVA) ULONGLONG AddressOfIndex; // TLS 索引表地址 ULONGLONG AddressOfCallBacks; // TLS 回调数组地址 DWORD SizeOfZeroFill; // 初始化为零的大小 DWORD Characteristics; // 保留字段(通常为 0)
} IMAGE_TLS_DIRECTORY64, *PIMAGE_TLS_DIRECTORY64;
-
StartAddressOfRawData:
-
指的是 TLS 初始化数据段的起始地址。
-
这部分数据会复制到每个线程的 TLS 段中,作为初始化状态。
-
-
EndAddressOfRawData:
-
TLS 初始化数据段的结束地址。
-
-
AddressOfIndex:
-
指向一个 TLS 索引(通常位于线程环境块 TEB 中),用于标识当前线程的 TLS 段。
-
-
AddressOfCallBacks:
-
指向一个回调函数指针数组。在线程创建或退出时,这些回调函数会依次被调用。
-
-
SizeOfZeroFill:
-
表示未初始化数据的大小,如果存在,则这部分数据会在 TLS 数据段初始化时清零。
-
-
Characteristics:
-
通常保留字段,值通常为 0。
-