TLS回调函数


简介

代码逆向分析领域中,TLS(Thread Local Storage,线程局部存储)回调函数(Callback Function)常用于反调试。

TLS 回调函数的调用运行要先于 EP 代码的执行,该特征使它可以作为一种反调试技术使用。

TLS

讲解 TLS 回调函数前,先简单了解一下有关 TLS 的知识。TLS 是各线程的独立的数据存储空间。使用 TLS 技术可在线程内部独立使用或修改进程的全局数据或静态数据,就像对待自身的局部变量一样。

IMAGE_DATA_DIRECTORY[9]

若在编程中启用了 TLS 功能,PE 头文件中就会设置 TLS 表(TLS Table)项目,如下图所示(IMAGE_NT_HEADERS-IMAGE_OPTIONAL_HEADER-IMAGE_DATA_DIRECTORY[9])。

IMAGE_TLS_DIRECTORY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef struct _IMAGE_TLS_DIRECTORY64 {
ULONGLONG StartAddressOfRawData;
ULONGLONG EndAddressOfRawData;
ULONGLONG AddressOfIndex; //PDWORD
ULONGLONG AddressOfCallBacks; //PIMAGE_TLS_CALLBACK *;
DWORD SizeOfZeroFill;
DWORD Characteristics;
} IMAGE_TLS_DIRECTORY64;

typedef _IMAGE_TLS_DIRECTORY64 * PIMAGE_TLS_DIRECTORY64;

typedef struct _IMAGE_TLS_DIRECTORY32 {
DWORD StartAddressOfRawData;
DWORD EndAddressOfRawData;
DWORD AddressOfIndex; //PDWORD
DWORD AddressOfCallBacks; //PIMAGE_TLS_CALLBACK *;
DWORD SizeOfZeroFill;
DWORD Characteristics;
} IMAGE_TLS_DIRECTORY32;

#ifdef _WIN64
typedef IMAGE_TLS_DIRECTORY64 IMAGE_TLS_DIRECTORY;
typedef PIMAGE_TLS_DIRECTORY64 PIMAGE_TLS_DIRECTORY;
#else
typedef IMAGE_TLS_DIRECTORY32 IMAGE_TLS_DIRECTORY;
typedef PIMAGE_TLS_DIRECTORY32 PIMAGE_TLS_DIRECTORY;
#endif

IMAGE_TLS_DIRECTORY结构体有 2 种版本,分别为 32 位版本与 64 位版本。

DIE 会直接为我们解析IMAGE_TLS_DIRECTORY结构体。

代码逆向分析中涉及的主要成员为AddressOfCallback6s,该值指向含有 TLS 回调函数地址(VA)的数组。这意味着可以向同一程序注册多个 TLS 回调函数(数组以 NULL 值结束)。

回调函数地址数组

该数组中实际存储的就是 TLS 回调函数的地址。进程启动运行时,(执行 EP 代码前)系统会逐一调用存储在该数组中的函数。请注意,虽然以上练习示例中仅注册了 1 个 TLS 函数(地址为 0x401000),但其实我们可以通过修改程序注册多个 TLS 函数。

TLS 回调函数

接下来从技术层面简单整理之前介绍的 TLS 回调函数相关内容。

所谓 TLS 回调函数是指,每当创建/终止进程的线程时会自动调用执行的函数。有意思的是,创建进程的主线程时也会自动调用回调函数,且其调用执行先于 EP 代码。反调试技术利用的就是 TLS 回调函数的这一特性。

请注意,创建或终止某线程时,TLS 回调函数都会自动调用执行,前后共 2 次(原意即为此)。执行进程的主线程(运行进程的 EP 代码)前,TLS 回调函数会先被调用执行,许多逆向分析人员将该进程应用于程序的反调试技术。

IMAGE_TLS_CALLBACK

TLS 回调函数的定义如下:

1
2
3
4
5
6
typedef VOID
(NTAPI * PIMAGE_TLS_CALLBACK){
PVOID DllHandle,
DWORD Reason,
PVOID Reserved
};

仔细观察 TLS 回调函数的定义可以发现,它与DllMaiin()函数的定义类似。

1
2
3
4
5
BOOL WINAPI DllMain(
__in HINSTANCE hinstDLL,
__in DWORD fdwReason,
__in LPVOID lpvReserved
);

观察以上 2 个函数可以发现,它们的参数顺序和函数都是一样的。其中,参数DllHandle为模块句柄(即加载地址),参数Reason表示调用 TLS 回调函数的原因,具体原因有 4 种,如以下代码所示:

1
2
3
4
#define DLL_PROCESS_ATTACH 1
#define DLL_THREAD_ATTACH 2
#define DLL_THREAD_DETACH 3
#define DLL_PROCESS_DETACH 0

要想准确理解 TLS 回调函数的工作原理(在哪个时间点调用哪个回调函数),最好的方法就是亲自创建。

调试 TLS 回调函数

手工添加 TLS 回调函数