首页
论坛
课程
招聘
[软件保护] [其他内容] [原创]TLS回调函数(Note)
2021-4-23 15:39 5697

[软件保护] [其他内容] [原创]TLS回调函数(Note)

2021-4-23 15:39
5697

TLS回调函数学习

一:TLS

Thread Local Storage,线程局部存储:各线程独立的数据存储空间。使用TLS技术可以在线程内部独立使用或修改进程的全局数据或静态数据, 就像对待自身的局部变量一样

 

TLS回调函数常用于反调试,主要是利用了TLS回调函数的调用要先于EP代码的执行

 

如果开启了TLS功能,PE文件头就会设置TLS表,IMAGE_NT_HEADERS-IMAGE_OPTIONAL_HEADER-IMAGE_DATA_DIRECTORY[9]描述了IMAGE_TLS_DIRECTORY结构体的位置。

 

IMAGE_TLS_DIRECTORY:

 

图片描述
对其中各参数的描述:

 

StartAddressOfRawData:tls模板在内存中的起始VA,模板是用于创建线程时初始化TLS数据的,可以看到模板中的内容其实就是TLS中创建的变量

 

EndAddressOfRawDataL:tls模板在内存中的结束VA

 

AddressOfIndex:存储TLS索引的位置

 

AddressOfCallBacks: 指向TLS注册的回调函数的函数指针(地址)数组

 

SizeOfZeroFill:用于指定非零初始化数据后面的空白空间的大小

 

Characteristics:属性

 

逆向过程中比较重要的成员就是AddressOfCallBacks,指向含有TLS回调函数地址的数组,进程在启动运行时,系统会逐一调用储存在该数组中的函数。

二:TLS回调函数

每当创建或终止进程的线程时会自动调用执行的函数。当然,创建进程的主线程的时候也会自动调用回调函数,且其执行先于EP代码。反调试技术就是利用的TLS回调函数的这一特征。

 

图片描述
参数顺序和定义都是一样的。第一个参数表示模块句柄,第二个参数表示调用TLS回调函数的原因。

 

对于第二个参数Reaseon:

 

图片描述
分别对应了这四种情况

三:实例(体会TLS回调函数)

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
54
55
56
57
58
59
60
61
#include <windows.h>
 
#pragma comment(linker, "/INCLUDE:__tls_used")//告知链接器使用TLS
 
void print_console(char* szMsg) //定义一个函数来向控制台打印字符串
{
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    //先于主线程调用执行的TLS回调函数中使用printf可能会发生Runtime Error,可直接调用WriteConsole API
    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);//wsprintfA宽字节的sprintf
    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);
}
/*
    注册TLS函数
    .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()
 
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;
}

执行效果:

 

图片描述

 

分析:在创建主线程的时候,就调用了回调函数,此时的调用Reason就是1

 

然后创建子线程的时候,此时又调用了回调函数,此时的调用Reason就是2

 

之后子线程执行完毕,调用回调函数,调用Reason就是3

 

最后主线程执行完毕,调用回调函数,调用原因是0

四:调试TLS回调函数

如果使用的调试器是WinDbg,那么默认情况下它就会在系统启动断点(System Startup Breakpoint)处暂停。而像OD这种默认就会在程序的EP处暂停。

 

下面是设置OD在系统启动断点处暂停。
图片描述

 

然后将程序拖入

 

就会暂停在此处

 

图片描述
然后我们通过查看IMAGE_TLS_DIRECTORY结构体的AddressOfCallbacks查看TLS回调函数的地址

 

从而在对应的函数地址处下断点

 

图片描述

 

查看发现在rdata节区

 

图片描述
图片描述

 

文件偏移为791C

 

图片描述

 

这个值是以IMAGE_OPTIONAL_HAEDER中的IMAGE_BASE的值为基地址的VA,现在我们查看默认的image_base:

 

图片描述
其实一般都是0x400000

 

然后我们相减得到RVA为0x8114,转换为文件偏移为0x6714:

 

如下:
图片描述

 

然后我们在OD中ctrl+G跳转过去下断点:

 

图片描述

 

然后运行即可调试我们的TLS回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
00401000    55              push ebp
00401001    8BEC            mov ebp,esp
00401003    FF15 04804000   call dword ptr ds:[<&KERNEL32.IsDebugger>; KERNEL32.IsDebuggerPresent
00401009    85C0            test eax,eax
0040100B    74 1C           je XHelloTls.00401029
0040100D    6A 00           push 0x0
0040100F    68 8C924000     push HelloTls.0040928C                   ; ASCII "TLS Callback"
00401014    68 9C924000     push HelloTls.0040929C                   ; ASCII "Debugger Detected!"
00401019    6A 00           push 0x0
0040101B    FF15 E8804000   call dword ptr ds:[<&USER32.MessageBoxA>>; USER32.MessageBoxA
00401021    6A 01           push 0x1
00401023    FF15 00804000   call dword ptr ds:[<&KERNEL32.ExitProces>; KERNEL32.ExitProcess
00401029    5D              pop ebp
0040102A    C2 0C00         retn 0xC

分析汇编可以得到程序的大致过程:

 

调用函数IsDebuggerPresent检测PEB.BeingDebugged成员得到是否处于调试状态。

 

然后测试返回值,如果没有在调试状态,那么直接跳转到retn 0xC(加个0xC是为了平衡栈,回调函数有三个参数)进行返回,如果在调试状态,则调用MessageBoxA弹出提示窗口。

 

其实我们可以给程序手动添加TLS回调函数,具体步骤如下:

 

1.

 

图片描述

 

下面使用第二种进行讲解步骤

  1. 编辑PE文件头,修改对应节区头的Size of Raw Data和 Characteristics的值

Size of Raw Data你扩展了多少就增加多少

 

Characteristics如下图:

 

图片描述

  1. 设置TLS表,让其指向我们增加的IMAGE_TLS_DIRECTORY结构体

  2. 设置我们的IMAGE_TLS_DIRECTORY,对照参数进行填写

例: 图片描述

 

然后我们先向那个TLS回调函数的地址指向的文件处写上C2 0C 00,对应汇编RETN 0C,为之后我们将程序拖入OD中添加TLS回调函数代码做准备,让其直接先返回

 

然后我们将这个程序载入OD中调试到TLS回调函数,添加我们的代码(对照上方我们调试程序得到的汇编),最后保存出去即可。


第五届安全开发者峰会(SDC 2021)议题征集正式开启!

最后于 2021-4-23 15:41 被SYJ-Re编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (4)
雪    币: 741
活跃值: 活跃值 (152)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
至尊小仙侠 活跃值 2021-4-24 10:40
2
1
牛还是你牛嘻嘻嘻
雪    币: 3400
活跃值: 活跃值 (525)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
option 活跃值 2021-4-25 09:02
3
1
TLS比较高档,学习了
雪    币: 1884
活跃值: 活跃值 (540)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
htpidk 活跃值 2021-4-26 08:42
4
1
总结的666
雪    币: 3275
活跃值: 活跃值 (3345)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
下咯 活跃值 2021-4-27 19:07
5
0
mark
游客
登录 | 注册 方可回帖
返回