概述:
本篇文章主要学习有关TLS(Thread Local Storage,线程局部存储)回调函数(CallBack Function),这种回调函数由于其特殊的特性通常会被运用于反调试技术中。本篇文章将通过分析代码,直接调试和手动创建TLS回调函数三个部分来学习有关TLS回调函数的知识。
TLS:
首先简单了解一下有关于TLS的基础知识,根据TLS的英文全称的翻译其实可以粗略的看出来一些特性。TLS实际上是一块存储空间,这块存储空间是各个线程的独立的数据存储空间。也可以说TLS是一种变量的存储方式,这个变量所在的线程内是全局可访问的(可以修改进程的全局数据和静态变量),但是这个变量不能被其它的线程所访问(保证数据的线程独立性)。
IMAGE_DATA_DIRECTORY TLSDirectory:
在启用了TLS功能的PE文件中,会设置有关于TLS的TLS Table(TLS表),这个表的位置信息可以在IMAGE_OPTION_HEADER中找到:
这里的VirtualAddress指向的RVA是0x9310(对应RAW为0x7910)这个指针实际上是指向一个结构体:IMAGE_TLS_DIRECTORY
这个结构体中存储了有关于TLS模板的各个数据,根据程序编译时的位数不同分为32位和64位两种结构,具体如下:
typedef struct _IMAGE_TLS_DIRECTORY32 {
DWORD StartAddressOfRawData;
DWORD EndAddressOfRawData;
DWORD AddressOfIndex; // PDWORD
DWORD AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *
DWORD SizeOfZeroFill;
union {
DWORD Characteristics;
struct {
DWORD Reserved0 : 20;
DWORD Alignment : 4;
DWORD Reserved1 : 8;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
} IMAGE_TLS_DIRECTORY32;
typedef IMAGE_TLS_DIRECTORY32 * PIMAGE_TLS_DIRECTORY32;
typedef struct _IMAGE_TLS_DIRECTORY64 {
ULONGLONG StartAddressOfRawData;
ULONGLONG EndAddressOfRawData;
ULONGLONG AddressOfIndex; // PDWORD
ULONGLONG AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *;
DWORD SizeOfZeroFill;
union {
DWORD Characteristics;
struct {
DWORD Reserved0 : 20;
DWORD Alignment : 4;
DWORD Reserved1 : 8;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
} IMAGE_TLS_DIRECTORY64;
结构体中各个参数的意义大致如下:
StartAddressOfRawData:TLS模板的起始位置的VA,这个所谓的模板其实就是用于初始化TLS函数的数据
EndAddressOfRawData:TLS模板终止位置的VA
AddressOfIndex:存储TLS索引的位置
AddressOfCallBacks:指向TLS注册的回调函数的函数指针(地址)数组
SizeOfZeroFill:用于指定非零初始化数据后面的空白空间的大小
Characteristics:属性
在这些成员中最为重要的是AddressOfCallBacks,这个成员是一个指向函数地址数组的指针,这里的函数的地址就是TLS需要调用的回调函数的实际地址
TLS回调函数:
首先简单介绍一下什么是TLS回调函数。前面有说到,在IMAGE_TLS_DIRECTORY中有一个成员AddressOfCallBacks中存储这指向TLS回调函数具体地址数组的指针,这里的的回调函数具体地址指的就是TLS回调函数。
TLS回调函数是指,每当创建/终止线程时会自动调用执行的函数(创建进程的主线程时也会自动调用回调函数,且回调函数的执行顺序是先于EP代码的执行,所以TLS回调函数的这个特性通常被用于反调试技术)由于是创建和终止线程时都会调用,所以在程序从打开到结束这个TLS回调函数会被执行两次。
TLStest1.cpp:
下面先来看一段代码,初步了解一下TLS回调函数在编程中具