什么是TLS
TLS设计的本意,是为了解决多线程程序中变量同步的问题,是Thread Local Storage的缩写,意为线程局部存储。线程本身有独立于其他线程的栈空间,因此线程中的局部变量不用考虑同步问题,即如果需要在一个线程内部的各个函数调用都能访问,但其他线程不能访问的变量(线程局部静态变量),就需要TLS机制来实现
TLS变量
每个线程都拥有一个TLS变量的副本,创建代码如下:
1
|
_declspec(thread) int g_a = 100;
|
TLS回调函数
编译选项:
1
|
#pragma comment(linker,"/INCLUDE:__tls_used")
|
注册TLS函数
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 添加函数声明
void NTAPI TLS_CALLBACK1(PVOID DLLHandle, DWORD Reason, PVOID Reserved);
/*.CRT$XLX 的作用
CRT 表示使用 C Runtime 机制 X 表示表示名随机
L 表示 TLS Callback section
X 也可以换成 B~Y 任意一个字符 */
#pragma data_seg(".CRT$XLX")
//存储回调函数地址
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1, TLS_CALLBACK2, 0 };
#pragma data_seg()
void NTAPI TLS_CALLBACK1(PVOID DLLHandle, DWORD Reason, PVOID Reserved) {
printf("TLS函数执行了\n");
}
|
TLS函数何时被调用:
1
2
3
4
|
#define DLL_PROCESS_ATTACH 1 //进程创建时
#define DLL_THREAD_ATTACH 2 //线程创建时
#define DLL_THREAD_DETACH 3 //线程销毁时
#define DLL_PROCESS_DETACH 0 //进程销毁时
|
如:
1
2
3
4
|
void NTAPI TLS_CALLBACK1(PVOID DLLHandle, DWORD Reason, PVOID Reserved) {
if (Reason == DLL_PROCESS_ATTACH)
printf("TLS函数执行了\n");
}
|
已知TLS回调函数调用时机是在线程运行前得到调用(在OEP之前执行),那么TLS反调试的方法也呼之欲出了:只要在TLS回调函数中添加反调试代码即可
TLS反调试
使用VS生成Release版程序时,需要注意禁用"全程序优化":
项目设置 -> C/C++ -> 优化 -> 全程序优化,设置为"否"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
void NTAPI TLS_CALLBACK1(PVOID DLLHandle, DWORD Reason, PVOID Reserved) {
if (Reason == DLL_PROCESS_ATTACH){
BOOL result = FALSE;
HANDLE hNewHandle = 0;
DuplicateHandle(
GetCurrentProcess(), // 源进程句柄(当前进程)
GetCurrentProcess(), // 源句柄(这里应该是要复制的具体句柄)
GetCurrentProcess(), // 目标进程句柄(当前进程)
&hNewHandle, // 接收新句柄的指针
NULL, // 请求的访问权限(NULL 表示相同权限)
NULL, // 是否可继承(NULL 表示相同)
DUPLICATE_SAME_ACCESS // 复制选项
);
CheckRemoteDebuggerPresent(hNewHandle, &result);
if(result){
MessageBoxA(0, "程序被调试了!", "警告", MB_OK);
ExitProcess(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
|
BOOL isDebugged = FALSE;
// 方法1:修复 CheckRemoteDebuggerPresent
CheckRemoteDebuggerPresent(GetCurrentProcess(), &isDebugged);
// 方法2:使用 IsDebuggerPresent
if (IsDebuggerPresent()) {
isDebugged = TRUE;
}
// 方法3:检查 PEB 中的 BeingDebugged 标志
__asm {
mov eax, fs:[0x30] // PEB 地址
mov al, [eax + 0x02] // BeingDebugged 偏移
mov isDebugged, al
}
if (isDebugged) {
// 多种退出方式增加难度
MessageBoxA(NULL, "检测到调试器!", "警告", MB_ICONERROR);
// 方式1:正常退出
ExitProcess(0);
// 方式2:触发异常
// __asm { int 3 }
// 方式3:无限循环
// while(1) { Sleep(1000); }
}
|
绕过手段:
- IDA 可以修改 API 返回值
- 可以 NOP 掉检查代码
- 可以在 TLS 回调执行前设置断点
PE文件中的TLS表
在 PE 文件中,TLS 相关信息通过数据目录表的第10项(IMAGE_DIRECTORY_ENTRY_TLS)指向
1
2
3
4
5
6
7
8
|
typedef struct _IMAGE_TLS_DIRECTORY {
DWORD StartAddressOfRawData; // TLS 模板的起始地址
DWORD EndAddressOfRawData; // TLS 模板的结束地址
DWORD AddressOfIndex; // TLS 索引的地址
DWORD AddressOfCallBacks; // TLS 回调函数数组的地址
DWORD SizeOfZeroFill; // 零填充的大小
DWORD Characteristics; // 特性标志
} IMAGE_TLS_DIRECTORY, *PIMAGE_TLS_DIRECTORY;
|
在IDA中查看TLS表
1.通过数据目录
View → Open subviews → Segments
找到 .tls 段,或者
Ctrl+S 打开段窗口,查找 .tls 段
2.通过名称
Ctrl+L 搜索 "tls" 或 ".tls"
或者在 Names 窗口中搜索 TLS 相关符号
3.使用 IDA 脚本
1
2
3
4
5
|
# 获取 TLS 目录
tls_dir = idaapi.get_tls_directory()
# 获取 TLS 回调函数
for callback in idaapi.get_tls_callbacks():
print("TLS Callback: 0x%X" % callback)
|
TLS回调函数数组
TLS 回调函数是一个以 NULL 结尾的函数指针数组:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// TLS 回调函数原型
typedef VOID (NTAPI *PIMAGE_TLS_CALLBACK)(
PVOID DllHandle,
DWORD Reason,
PVOID Reserved
);
// TLS 回调函数数组
PIMAGE_TLS_CALLBACK CallBackArray[] = {
TLS_Callback1,
TLS_Callback2,
// ...
NULL // 以 NULL 结束
};
|
TLS在逆向分析中的应用
- 反调试技术
TLS 回调在主函数之前执行,常用于:
- 初始化代码
- 全局/静态变量的初始化
- 运行时环境的设置
- 加密数据的解密
- 恶意软件分析
很多恶意软件使用 TLS 回调来: