July 25, 2022

关于逆向工程核心原理-TLS

又是没整理的一个知识点,做PPT真是强制帮我整理知识点了。

TLS

Introduction Of TLS

TLS ( Thread Local Storage,线程局部存储 ) 回调函数(Callback Function),常用于反调试。

TLS 是各线程的独立数据存储空间,使用 TLS 技术可在线程内部独立使用或修改进程的全局数据或静态数据,就像对待自己的局部变量一样(编程中这种功能非常有用)。

IMAGE_DATA_DIRECTORY[9]

这 IMAGE_DATA_DIRECTORY 保存着 TLS TABLE

IMAGE_TLS_DIRECTORY

分别有 32位 版本和 64位 版本

image-20220725144740323

而该结构体比较重要的是成员是 AddressOfCallbacks ,该值指向含有 TLS 回调函数的地址的数组,这意味着同一个程序可以注册多个 TLS 回调函数(数组以 NULL 结束)。

image-20220725145238921

可以发现注册了两个 TLS 函数,进程启动运行时,系统会逐一调用存储该数组中的函数

image-20220725145552652

IMAGE_TLS_CALLBACK

从技术层面来讲,所谓 TLS 回调函数是指,每当创建/终止进程的线程时会自动调用执行的函数,有意思的是创建进程的主线程时也会自动调用回调函数,且其调用先于 EP 代码。

TLS 回调函数定义如下

image-20220725151128175

可以发现与 DllMain() 函数定义相似

image-20220725151443657

可以发现这两个定义的顺序与含义都是一样的

其中原因有四种

image-20220725151657394

TLSTest.exe

Resource code:

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
#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 回调函数

TLS_CALLBACK1 与 TLS_CALLBACK2

main 函数功能为创建用户线程 ThreadProc 后中止

效果如下

image-20220725152409051

DLL_PROCESS_ATTACH

进程的主线程调用 main() 函数前,已经注册的 TLS 回调函数(TLS_CALLBACK1、TLS_CALLBACK2)会被先执行,此时 Reason 的值为 1 (DLL_PROCESS_ATTACH)

DLL_THREAD_ATTACH

所有 TLS 回调函数完成调用后,main 函数开始调用执行,创建用户线程(ThreadProc)前,TLS 回调函数会被再次调用,此时 Reason = 2 (DLL_THREAD_ATTACH)

DLL_THREAD_DETACH

TLS 回调函数全部执行完,ThreadProc() 线程函数开始执行,该用户起的线程函数结束后再度调用 TLS 函数,其 Reason = 3(DLL_THREAD_DETACH)

DLL_PROCESS_DETACH

ThreadProc 函数执行完毕后,一直等待线程终止的 main 函数也就是主线程终止,这时候再度调用 TLS 回调函数(DLL_PROCESS_DETACH)

两个 TLS 函数分别调用执行了 4 次,总共为 8 次

源代码中并未使用 printf 函数,因为开启特定编译选项(/MT)编译源程序时,先于主线程调用执行的 TLS 回调函数可能会引用 Run-Time Error(运行时错误),所以用了 WriteConsole API 以防万一

DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

This post is written by P.Z, licensed under CC BY-NC 4.0.