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属性。
然后设置结构体值,添加代码就可以了。