第45章:TLS回调函数
TLS(Thread Local Storage,线程局部存储)回调函数(Callback Function)常用于反调试。
TLS 回调函数的调用运行要先于 EP 代码的执行。它是各线程独立的数据存储空间,可修改进程的全局/静态数据。
若在编程中启用了 TLS,PE 头文件中会设置 TLS 项目,即:IMAGE_TLS_Directory
其中比较重要的成员是:AddressOfCallBacks 它指向回调函数数组地址。
自己找一下试试:
同时获取到相应的节区信息,计算: 9310 - 8000 + 6600 = 7910 .
第四个元素即为 AddressOfCallBacks .
408114 明显超出了文件的大小,因此判断是加上了 ImageBase (即 VA ),在节区头查看 ImageBase 大小为 400000 .
因此 RVA 为 8114 ,8114 - 8000 + 6600 = 6714 .
参数 Reason 表示调用 TLS 回调函数的原因:
第二个程序的代码( TlsTest.cpp ):
#include <windows.h> #pragma comment(linker, "/INCLUDE:__tls_used")// 通过#pragma comment(Linker,"INCLUDE:_tls_used")显示的申明crt库中定义的_tls_used变量 // 以便向_tls_used!AddressOfCallBacks域添加回调函数。 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") //CRT 表明使用 C RunTime 机制,X 表示标识名随机,L 表示 TLS callback section, X 为 B-Y 之间的任意一个字母 PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1, TLS_CALLBACK2, 0 }; // 注意,此处将自定义函数放入了 TLS 表中。 #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; }
程序运行截图:
① DLL_Process_ATTACH 在主线程调用 Main()函数前,已经注册的两个函数会被调用执行。
② DLL_Thread_ATTACH 在 TLS 函数完成后,main()函数开始执行。在创建用户线程前(若有),TLS 回调函数会再次被执行。
③ DLL_Thread_Detach TLS 函数执行完后,线程函数开始调用执行,线程函数结束后(若有)再次调用 TLS 函数。
④DLL_Process_Detach main()函数结束后,再次调用 TLS 函数。
若不在主函数中调用 CreateThread 函数,则只会执行两次 TLS 。
使用 win xp sp3 进行实际调试:
使用 OD ,将断点设置为 TLS 断点,程序会断在此处:
对比代码,并观察程序名可知,程序被断在了 TLS_CallBack1 处。继续运行程序至返回:
可以看到,通过间接调用,实现了 TLS CallBack1 的调用。继续执行程序:
可以看到,函数执行到了第一次执行的位置,表明会循环执行,直至函数执行完:
手工添加 TLS 回调函数
①将 TLS 结构体及 TLS 回调函数放在最后一个节区 or 其它节区的空白地区 or 添加新节区。
属性增加了 Write (方便在调试时编写代码),Execute(可执行),CNT_CODE(区段包含代码)。
②设置 TLS 表
③设置 IMAGE_TLS_DIRECTORY 结构体
StartAddressOfRawData:tls模板在内存中的起始VA,模板是用于创建线程时初始化TLS数据的;
EndAddressOfRawDataL:tls模板在内存中的结束VA;
AddressOfIndex:存储TLS索引的位置;
这三个值都可以指向 NULL 区域。
第四个值 CallBack 指向一个数组,以 四个字节的 NULL 结尾。数组每四个字节指向一个地址,存储着函数。
有意思的是,在使用 StudyPe+ x86 修改后,在 win xp sp3 上无法运行,但是在 Win 10 上可以。经查证:
使用软件进行修改时,增加最后一个节区 200 字节时,会自动将 VirtualSize 增大 1000,就算自己修改后回去后,可选头中的 ImageSize 还是自动增大 1000。
因此如果自己改变了 VirtualSize ,那么会造成 ImageSize 比实际大 1000,在 xp 上无法运行。