TLS 回调函数
TLS 回调函数
简单介绍
TLS(Thread Local Storage, 线程局部存储)回调函数,TLS 回调函数的调用运行要先于 EP 代码的执行,并且每次创建或结束线程都会再次调用,故常用于反调试。
函数定义
1 | typedef VOID (NTAPI *PIMAGE_TLS_CALLBACK)( |
其与 DllMain() 函数的定义十分相似。
细节
TLS 和 TLS 的特征
TLS 是各线程的独立的数据存储空间,使用 TLS 技术可以在线程内部独立使用或修改进程的全局数据或静态数据。
若启用 TLS 功能,PE 头文件中会设置 TLS 表(TLS Table)项目:
未启用则是这样的:
并且如果使用 IDA 打开,会在 Exports 选项卡看到 TlsCallBack 字样:
TLS 调用原因(Reason)
1 | //winnt.h |
TLS 回调函数代码示例
1 | //本代码在vs2019x86平台上通过测试 |
对以上代码的解释:
#pragma comment(linker,"/INCLUDE:__tls_used")
作用是告知编译器使用 TLS 回调函数。其中,在 x86 环境下,使用__tls_used
,而在x64环境下使用_tls_used
TLS_CALLBACK
这个函数即是我们定义的 TLS 回调函数,遵照上面的函数定义即可。#pragma data_seg(".CRT$XLY")
和#pragma data_seg()
作用是注册回调函数,如果回调函数只有 1 个,则可以以上述形式编写代码即可,如果回调函数有多个,则可以声明一个数组,如PIMAGE_TLS_CALLBACK tls_callback_func[] = {TLS_CALLBACK1, TLS_CALLBACK2, 0};
注意数组最后一个元素必须为 0 。WaitForSingleObject
这个函数作用是等待线程运行,否则 main 进程会先于 ThreadProc 线程结束,有可能 ThreadProc 线程还未运行,进程就结束了,运行结果就不太清晰如果编译选项中开启了 /MT ,则 TLS 中的 printf 函数可能会出现错误,此时可以使用如下代码来防止出现问题:
1
2
3
4char string[80]={0};
wsprintfA(string,"TLStest,reason=%d\n",Reason);
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsoleA(hStdout, string, strlen(string), NULL, NULL);运行结果如下,可以看出其 Reason 对应的各种情况:
如何调试 TLS 回调函数
直接使用调试器打开带 TLS 回调函数的程序,则无法调试 TLS 回调函数,因为 TLS 早在 EP 代码开始前就被执行了,因此需要在调试器中打开
System breakpoint
选项,从而使程序暂停在系统启动断点System Startup Breakpoint
处。有的调试器也提供”暂停在 TLS 回调函数处”的选项。