PE结构:TLS与TLS回调函数精讲解析

前言

这是我们VIP2会员内部视频:PE文件结构的内部课件的部分内容,鉴于网络上没有比较详尽、通俗易懂的科普文章,本篇内部课件,在此分享给大家,如果您喜欢本篇文章,想深入学习内部视频,想学好软件安全逆向技术,想学好逆向破解技术:欢迎联系小迪老师微信ixiaodi668,我们在这里等你。

本篇文章为内部课件的一部分:如下所示

图片[1]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

内部视频教学试看

TLS 简介

TLS (Thread Local Storage) = 线程本地存储

就是为每个线程单独分配的一块数据区,使得不同线程有自己的独立变量副本。

参考文档:

https://learn.microsoft.com/en-us/cpp/c-language/thread-local-storage?view=msvc-170&redirectedfrom=MSDN

图片[2]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

TLS 执行时机

PE 支持设置“TLS 回调函数”(TLS Callback)。当进程或线程启动时,这些函数会被系统自动调用

执行顺序大概如下:


EXE加载

当 EXE 被加载到内存时:

操作系统 loader →
  执行 TLS Callback →
  执行 C 运行库初始化 →
  执行 main/WinMain

DLL 加载

当 DLL 被 LoadLibrary 或被系统加载时:

操作系统 loader →
  TLS Callback(DLL_PROCESS_ATTACH) →
  DllMain(DLL_PROCESS_ATTACH) →
  返回

当创建新线程时

线程创建 →
  TLS Callback(DLL_THREAD_ATTACH) →
  DllMain(DLL_THREAD_ATTACH)

在这里大家只需要认识到一点:就是

TLS 回调函数的执行时机,比 exe 或者 dll 两者的入口位置,还要早

因为它的加载时机最靠前,所以可以作很多用途

TLS Callback 的用途

1. 反调试隐藏点

恶意软件常把反调试代码写在 TLS Callback 中,因为:

  • 你来不及下 main() 入口断点
  • 你来不及下 DllMain() 入口断点
  • TLS 回调在调试器入口断点反应前就执行了

大部分人的xdbg配置都会只勾选一个“入口断点”

大部分人的xdbg配置都会只勾选一个“入口断点”

反调试常常阻止 OllyDbg / x64dbg 运行, 在 TLS 里崩溃调试器或者结束程序进程。

反调试代码示例:

mov eax, fs:[30h]  ; PEB
cmp byte ptr [eax+2], 1 ; BeingDebugged
je EXIT_PROCESS

2. 壳(packer)/恶意软件(Malware)用于解密重要代码

某些软件保护壳VMProtect 会在 TLS Callback 中:

  • 解密代码段
  • 修改 IAT
  • 修改代码段权限

某些恶意软件也会在 TLS Callback 中:

  • 解密 .text 或 .data 段:让执行的代码在 TLS 回调里动态恢复。
  • 解密 IAT / Import table:绕过杀软静态分析。
  • 解密资源(如字符串、配置数据):延迟到运行时才解密,静态分析不到。
  • 解密自身 DLL 代码段:用于保护 DLL 注入后的第一段代码。
  • 解密配置信息(远控服务器C2地址、密钥):恶意软件常用。
  • 自动自毁/修补:反分析时自动破坏自身以防逆向。

3. 软件保护:初始化 HOOK、补丁

一些程序会在 TLS 回调里进行:

  • Inline Hook 自身函数
  • 禁用调试器
  • 注入代码
  • 检测硬件断点、软件断点

4.恶意软件行为

  • 在 main() 之前执行恶意逻辑,防止分析工具进入程序入口。
  • 隐藏恶意行为,分析者下断到 main 还没看到恶意行为。
  • 创建新的线程: 恶意线程在 TLS 阶段启动,不容易被发现。
  • 重定向执行流(自定义入口): 把真正的 main 隐藏起来。

总结:

在逆向和恶意软件Malware分析(病毒木马分析)中

TLS Callback 是常见的隐藏点。

什么时候 强烈推荐 把某些代码放进 TLS 执行 ?

  • 做软件保护(反破解)
  • 做壳
  • 做反调试
  • 做加密逻辑
  • 做木马(恶意软件)
  • 做防注入
  • 做关键代码的完整性检查
  • 需要在 OEP 之前执行

如果你正在研究逆向、HOOK、注入、防调试,那么 TLS 是神器。

PE 文件结构之解析TLS

TLS 结构位置

扩展PE 头Optional HeaderData Directory 项中有一个:

IMAGE_DIRECTORY_ENTRY_TLS (索引(下标):9)

图片[4]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

使用 010Editor 工具可以看到它的 RVA 和 Size

图片[5]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

使用 studyPE 也可以查看,如果存在 TLS,那么 RVA、FOA、Size 肯定不会为 0

图片[6]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

TLS 结构

typedef struct _IMAGE_TLS_DIRECTORY32 {
    DWORD StartAddressOfRawData;
    DWORD EndAddressOfRawData;
    DWORD AddressOfIndex;       
    DWORD AddressOfCallBacks;   // 指向 TLS Callback 数组
    DWORD SizeOfZeroFill;
    DWORD Characteristics;
} IMAGE_TLS_DIRECTORY32;

成员解析:一共 24 个字节

字段

说明

StartAddressOfRawData

TLS 数据区起始地址(VA)

EndAddressOfRawData

TLS 数据区结束地址(VA)

AddressOfIndex

TLS 索引变量(编译器用)

AddressOfCallBacks

指向 TLS 回调函数数组,数组以 0 结尾

SizeOfZeroFill

要清零的字节数

Characteristics

保留

如何解析TLS 回调函数

  1. DataDirectory[9].VirtualAddress 找到 TLS DirectoryRVA
  2. 转 VA / 文件偏移得到真正的 IMAGE_TLS_DIRECTORY
  3. 读取 AddressOfCallBacks
    (这是一个
    指针数组 VA每个元素都是回调函数地址

格式:

AddressOfCallBacks → [Callback1][Callback2][Callback3]...[0]

例如:

401020 → 00401234

401024 → 00404567

401028 → 00000000 // 数组结束

示例解析

图片[7]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

RVA 转换为 FOA,Size 是 18h == 24 个字节

图片[8]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

图片[9]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

最重要的是第 4 个成员:AddressOfCallBacks == 4FA834

typedef struct _IMAGE_TLS_DIRECTORY32 {
    DWORD StartAddressOfRawData;
    DWORD EndAddressOfRawData;
    DWORD AddressOfIndex;       
    DWORD AddressOfCallBacks;   // 指向 TLS Callback 数组
    DWORD SizeOfZeroFill;
    DWORD Characteristics;
} IMAGE_TLS_DIRECTORY32;

我们讲一个极端情况,这个掌握了,其他的就都会分析了:

1. 跳转到 dbg 发现内存地址4FA834无效

图片[10]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

2.这个时候我们要思考程序是否存在重定位的情况

存在重定位表

图片[11]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

存在 reloc 段

图片[12]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

是动态基址

图片[13]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

3.计算重定位后的 TLS 回调函数数组地址

4FA834ImageBase = 0xFA834

其中ImageBase = 00400000NewBase = 1A0000

TLS 回调数组地址 = 1A0000 + 0xFA834 = 29A834

图片[14]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

4.验证计算结果

数组的结构是这样的:

AddressOfCallBacks → [29A834]=1F33DB、[29A838]=1F2549、[29A83C]=0

图片[15]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

图片[16]-PE结构:TLS与TLS回调函数精讲解析-软件安全逆向社区论坛-技术社区-学技术网

5.解析大致代码

IMAGE_TLS_DIRECTORY32* tlsDir = 
    (IMAGE_TLS_DIRECTORY32*)((BYTE*)base + RvaToOffset(dataDir.VirtualAddress));

DWORD* callbacks = (DWORD*)tlsDir->AddressOfCallBacks;

while (*callbacks)
{
    printf("TLS Callback: %p\n", (void*)*callbacks);
    callbacks++;
}

总结

TLS线程局部存储机制,但对逆向工程更重要的是 TLS Callback

  • 在 main/DllMain 之前执行
  • 可用于反调试、解密、初始化 HOOK
  • 常见于壳与恶意软件
  • 在 PE 文件中:
    DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS] → TLS Directory → Callback Pointer Array(回调函数数组)
  • TLS 执行时机:
操作系统 loader →
    执行 TLS Callback →(全部) →
    C/C++ 静态初始化(全局对象构造等) →
    mainCRTStartup →
    main() 或 WinMain()
DLL 被加载 →
  加载器初始化(LdrInitialize) →
    TLS Callback 执行(全部 TLS 回调) →
    C/C++ CRT 静态初始化(全局对象构造、全局变量初始化) →
    DLL入口点 DllMain(DLL_PROCESS_ATTACH)
请登录后发表评论

    没有回复内容