您现在的位置是:首页 >技术交流 >【逆向工程核心原理:TLS回调函数】网站首页技术交流
【逆向工程核心原理:TLS回调函数】
TLS
代码逆向分析领域中,TLS(Thread Local Storage,线程局部存储)回调函数(Callback Function)常用反调试。TLS回调函数的调用运行要先于EP代码的执行,该特征使它可以作为一种反调试技术的使用。
TLS是各线程的独立的数据存储空间,使用TLS技术可在线程内部独立使用或修改进程的全局数据或静态数据,就像对待自身的局部变量一样。
- PE中的TLS
若在编程中启用了TLS功能,PE头文件中就会设置TLS表(TLS Table)项目
IMAGE_NT_HEADERS
IMAGE_OPTIONAL_HEADER
IMAGE_DATA_DIRECTORY[9]
上图多了TLS结构的索引,通过这个地址可以找到TLS结构。
看一下TLS结构体定义。
TLS结构有32位和64位两个版本,其属性个数相同,只是属性的长度发生了变化
typedef struct _IMAGE_TLS_DIRECTORY64 {
ULONGLONG StartAddressOfRawData;
ULONGLONG EndAddressOfRawData;
ULONGLONG AddressOfIndex; // PDWORD
ULONGLONG AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *;
DWORD SizeOfZeroFill;
union {
DWORD Characteristics;
struct {
DWORD Reserved0 : 20;
DWORD Alignment : 4;
DWORD Reserved1 : 8;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
} IMAGE_TLS_DIRECTORY64;
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;
通过这个目录表,可以找到TLS结构体,关注这个变量,就可以找到一个数组,数组里是回调函数地址的数组,以全零结尾
这就是找到了回调函数的地址数组,这里只有一个地址
TLS回调函数运行时机
所谓TLS回调函数是指,每当创建/终止进程的线程时会自动调用执行的函数。有意思的是,创建进程的主线程时,也会自动调用回调函数,且其调用执行先于EP代码。反调试技术利用的就是TLS回调函数的这一特征。
注:
请注意,创建或终止某线程时,TLS回调函数都会自动调用执行,前后共2次。
执行进程的主线程(运行线程的EP代码)前,TLS回调函数会被先调用执行,许多逆向分析人员将该特征应用于程序的反调试技术。
// IMAGE_TLS_CALLBACK结构
typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (
PVOID DllHandle,
DWORD Reason,
PVOID Reserved
);
DllHandle为模块句柄,Reason是调用的原因:
// 进程主线程main执行前调用
#define DLL_PROCESS_ATTACH 1
// main开始执行,main开始创建线程前调用
#define DLL_THREAD_ATTACH 2
// 子线程结束后调用
#define DLL_THREAD_DETACH 3
// 子线程执行完毕,main也结束后调用
#define DLL_PROCESS_ATTACH 0
example:
#include<Windows.h>
// 通知链接器使用TLS
#pragma comment(linker,"/INCLUDE:__tls_used")
void print_console(const char* szMsg)
{
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsole(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
", 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
", DllHandle, Reason);
print_console(szMsg);
}
// 注册TLS函数,.CRT$XLX的作用:CRT表示使用C Runtime 机制,X表示表示名随机,L表示TLS Callback section,X也可以换成B~Y任意一个字符
// 在这里用数组存放TLS回调函数地址
#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1,TLS_CALLBACK2,0 };
#pragma data_seg()
DWORD WINAPI ThreadProc(LPVOID lPram) {
print_console("ThreadProc() start
");
print_console("ThreadProc() end
");
return 0;
}
int main(void)
{
HANDLE hThread = NULL;
print_console("main()start
");
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
WaitForSingleObject(hThread, 60 * 1000);
CloseHandle(hThread);
print_console("main() end
");
system("pause");
return 0;
}
调试TLS
开始调试前,要设置System breakpoint
2.x也可以直接设置TLS ballback
断下后,去AddressofCallback
去找存放回调函数地址的数组,这里写了一个数据0x401000
在这里就是TLS回调函数内容
手工添加TLS
调试可以,也可以通过修改PE文件,给没有TLS
的程序添加TLS回调函数
- 扩大最后一个节
- 修改PE头,保证扩展正确
- 增加TLS的目录表
- 增加TLS的结构体(放在扩大的节里)
- 根据结构体所指的数组中的地址,添加TLS回调函数代码
1、2:扩大节、修改PE节表
3、添加TLS目录表
4、去RVA=C200处,添加TLS结构体内容
5、添加TLS函数代码
- 结果
载入od调试,就是触发这个弹窗