首页
社区
课程
招聘
[原创]TLS回调函数(Note)
发表于: 2021-4-23 15:39 14566

[原创]TLS回调函数(Note)

2021-4-23 15:39
14566

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回调函数地址的数组,进程在启动运行时,系统会逐一调用储存在该数组中的函数。

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

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

对于第二个参数Reaseon:

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

执行效果:

图片描述

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

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

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

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

如果使用的调试器是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回调函数

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

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

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

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

1.

图片描述

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

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

Characteristics如下图:

图片描述

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

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

例: 图片描述

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

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

 
 
 
 
 
 
 
 
 
 
 
 
 
 
#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;
}
#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);

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-4-23 15:41 被SYJ-Re编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (4)
雪    币: 2998
活跃值: (8003)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
牛还是你牛嘻嘻嘻
2021-4-24 10:40
1
雪    币: 8227
活跃值: (2731)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
TLS比较高档,学习了
2021-4-25 09:02
1
雪    币: 7465
活跃值: (4196)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
总结的666
2021-4-26 08:42
1
雪    币: 377
活跃值: (5996)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
5
mark
2021-4-27 19:07
0
游客
登录 | 注册 方可回帖
返回
//