0%

TLS回调函数

TLS:

TLS是各线程的独立的数据存储空间。使用TLS技术可在线程内部独立使用或修改进程的全局数据或静态数据,就像对待自身局部变量一样。

IMAGE_DATA_DIRECTORY[9]:

若编程中启用了TLS功能,PE头文件就会设置TLS表项目,就在IMAGE_DATA_DIRECTORY[9]里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;

AddressOfCallBacks:

该成员指向含有TLS回调函数地址(VA)的数组。

回调函数地址:

数组中存储的就是TLS回调函数地址。进程运行时,(执行EP前)系统会逐一调用存储在该数组的函数。

TLS回调函数:

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

IMAGE_TLS_CALLBACK

1
2
3
4
5
6
typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK)(
PVOID DllHandle,
DWORD Reason,
PVOID Reaserved
)

TLS回调函数的定义和DllMain很像,其中DllHandle为模块句柄(加载地址),Reason表示调用TLS回调函数的原因,具体有四种。

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

练习:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <windows.h>

#pragma comment(linker, "/INCLUDE:__tls_used")

void print_console(char* szMsg)
{
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);

WriteConsoleA(hStdout, szMsg, strlen(szMsg), NULL, NULL);
}

void NTAPI TLS_CALLBACK1(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
char szMsg[80] = {0,};
wsprintfA(szMsg, "TLS_CALLBACK1() : DllHandle = %X, Reason = %d\n", DllHandle, Reason);
print_console(szMsg);
}

void NTAPI TLS_CALLBACK2(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
char szMsg[80] = {0,};
wsprintfA(szMsg, "TLS_CALLBACK2() : DllHandle = %X, Reason = %d\n", DllHandle, Reason);
print_console(szMsg);
}

#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1, TLS_CALLBACK2, 0 };
#pragma data_seg()

DWORD WINAPI ThreadProc(LPVOID lParam)
{
print_console("ThreadProc() start\n");

print_console("ThreadProc() end\n");

return 0;
}

int main(void)
{
HANDLE hThread = NULL;

print_console("main() start\n");

hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
WaitForSingleObject(hThread, 60*1000);
CloseHandle(hThread);

print_console("main() end\n");

return 0;
}

以上代码像各位展示了注册TLS回调函数的方法。

DLL_PROCESS_ATTACH:

进程的主线程调用mian函数之前,此时Reason为1。

DLL_THREAD_ATTACH:

main函数开始执行,创建用户线程前,TLS再次被调用,此时Reason为2。

DLL_THREAD_DETACH :

线程函数执行完毕后,调用TLS回调函数,此时Reason为3。

DLL_PROCESS_ATTACH :

mian函数执行完后,TLS最后一次调用,此时Resaon为0。

注意:

源文件中并未使用printf()函数,因为开启特定编译选项(/MT)编译源程序,先于主程序调用执行的TLS回调函数中可能发生Run-time Error(运行时错误)。此时可以直接调用WriteConsole() API来以防万一。

调试TLS回调函数:

在OD默认设置下,调试器暂停在EP位置,WinDbg默认暂停在系统启动断点处。

最新OD2.0默认提供暂停在TLS回调函数选项。

手工添加TLS回调函数:

加入选择在一个节区后添加位置,由于要在扩展区创建IMAGE_TLS_DIRECTORY结构体与TLS回调函数,所以需要向节区添加IMAGE_SCN_CNT_CODE|IMAGE_SCN_MEM_EXECUTE属性,含必须向包含IMAGE_TLS_DIRECTORY结构体节区添加IMAGE_SCN_MEM_WRITE属性。

然后设置结构体值,添加代码就可以了。