Featured image of post TLS

TLS

什么是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在逆向分析中的应用

  1. 反调试技术

TLS 回调在主函数之前执行,常用于:

  • 检测调试器
  • 代码完整性检查
  • 虚拟机检测
  1. 初始化代码
  • 全局/静态变量的初始化
  • 运行时环境的设置
  • 加密数据的解密
  1. 恶意软件分析

很多恶意软件使用 TLS 回调来:

  • 隐藏真实入口点
  • 提前执行恶意代码
  • 干扰调试分析
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
人生若只如初见
使用 Hugo 构建
主题 StackJimmy 设计