TLS回调函数(1)

简述

代码逆向分析领域中,TLS(Thread Local Storage,线程局部存储)回调函数(Callback Function)常用反调试。TLS回调函数的调用运行要先于EP代码的执行,该特征使它可以作为一种反调试技术的使用。

TLS

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

IMAGE_DATA_DIRECTORY[9]

若在编程中启用了TLS功能,PE头文件中就会设置TLS表(TLS Table)项目,如下(IMAGE_NT_HEADERS-IMAGE_OPTIONAL_HEADER-IMAGE_DATA_DIRECTORY[9]

在这里插入图片描述
在这里插入图片描述

typedef struct _IMAGE_TLS_DIRECTORY64 {
    ULONGLONG StartAddressOfRawData;
    ULONGLONG EndAddressOfRawData;
    ULONGLONG AddressOfIndex;         // PDWORD
    ULONGLONG AddressOfCallBacks;     // PIMAGE_TLS_CALLBACK *;
    DWORD SizeOfZeroFill;
    union {
        DWORD Characteristics;
        struct {
            DWORD Reserved0 : 20;
            DWORD Alignment : 4;
            DWORD Reserved1 : 8;
        } DUMMYSTRUCTNAME;
    } DUMMYUNIONNAME;

} IMAGE_TLS_DIRECTORY64;




typedef struct _IMAGE_TLS_DIRECTORY32 {
    DWORD   StartAddressOfRawData;
    DWORD   EndAddressOfRawData;
    DWORD   AddressOfIndex;             // PDWORD
    DWORD   AddressOfCallBacks;         // PIMAGE_TLS_CALLBACK *
    DWORD   SizeOfZeroFill;
    union {
        DWORD Characteristics;
        struct {
            DWORD Reserved0 : 20;
            DWORD Alignment : 4;
            DWORD Reserved1 : 8;
        } DUMMYSTRUCTNAME;
    } DUMMYUNIONNAME;

} IMAGE_TLS_DIRECTORY32;
typedef IMAGE_TLS_DIRECTORY32 * PIMAGE_TLS_DIRECTORY32;

_IMAGE_TLS_DIRECTORY32结构体有2种版本,分别为32位版本和64位版本。

在这里插入图片描述
在这里插入图片描述
代码逆向分析中涉及的比较重要的成员AddressOfCallbacks,该值指向含有TLS回调函数地方(VA)的数组。这意味着可以向同一程序注册多个TLS回调函数

回调函数地址数组

在这里插入图片描述
该数组中实际存储的就是TLS回调函数的地址。进程启动运行时,(执行EP代码前)系统会逐一调用存储在该数组中的函数。
可以看出此数组有两个回调函数(地址为4113B1和41129E),我们可以通过修改程序注册多个TLS函数

TLS回调函数

接下来就是从技术层面简单整理之前介绍的TLS回调函数相关内容。

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

请注意,创建或终止某线程时,TLS回调函数都会自动调用执行,前后共2次(原意即为此)。
执行进程的主线程(运行线程的EP代码)前,TLS回调函数会被先调用执行,许多逆向分析人员将该特征应用于程序的反调试技术。

IMAGE_TLS_CALLBACK

typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (
    PVOID DllHandle,
    DWORD Reason,
    PVOID Reserved
    );

仔细观察TLS回调函数的定义可以发现,它与DllMain的定义类似。

BOOL WINAPI DllMain(
			 			__in HINSTANCE hModule,
                      	__in DWORD fdwReason,
                       	__in LPVOID lpReserved
                     );

观察以上2个函数可以发现,它们的参数顺序与含义都是一样的。其实,参数DllMain为模块句柄(即加载地址),参数fdwReason表示调用TLS回调函数的原因,具体原因有四种,如下所示

#define DLL_PROCESS_ATTACH 1
#define DLL_THREAD_ATTACH 2
#define DLL_THREAD_DETACH 3
#define DLL_PROCESS_ATTACH 0

接下来用例子讲解这四种原因

#include<Windows.h>

#pragma comment(linker,"/INCLUDE:__tls_used")

void  print_console(const  char* szMsg)
{
	HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
	WriteConsole(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);
	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);
}

#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1,TLS_CALLBACK2,0 };

#pragma data_seg()

DWORD WINAPI ThreadProc(LPVOID lPram) {
	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");
	system("pause");
	return 0;
}

在这里插入图片描述

代码讲解:

#pragma comment(linker,"/INCLUDE:__tls_used")

告知链接器使用TLS

#pragma data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1,TLS_CALLBACK2,0 };
#pragma data_seg()

注册TLS函数,.CRT$XLX的作用:CRT表示使用C Runtime 机制,X表示表示名随机,L表示TLS Callback sectionX也可以换成B~Y任意一个字符

PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[] = { TLS_CALLBACK1,TLS_CALLBACK2,0 };存储回调函数地址

DLL_PROCESS_ATTACH

进程的主线程调用main函数前,已经注册的TLS回调函数(TLS_CALLBACK1,TLS_CALLBACK2)会先被调用执行,此时Reason的值为1(DLL_PROCESS_ATTACH

DLL_THREAD_ATTACH

所有的TLS回调函数执行完后,main()函数开始调用执行,创建用户线程(ThreadProc)前,TLS回调函数会被再次调用执行,此时Reason的值为2(DLL_THREAD_ATTACH

DLL_THREAD_DETACH

TLS回调函数全部 执行完毕后,ThreadProc()线程函数开始调用执行。其执行完毕后Reason=3(DLL_THREAD_DETACH),TLS回调函数被调用执行

DLL_PROCESS_DETACH

ThreadProc()线程函数执行完毕后,一直在等待线程终止的main函数(主线程)也会终止。此时Reason=0(DLL_PROCESS_ATTACH),TLS回调函数最后一次被调用执行。

补充:

源文件中并未使用printf()函数,因为开启特定编译选项(/MT)编译源程序,先于主程序调用执行的TLS回调函数中可能发生Run-time Error(运行时错误)。此时可以直接调用WriteConsole() API来以防万一。

调试TLS回调函数

若直接使用调试器打开带有TLS回调函数的程序,则无法调试TLS回调函数,因为TLS在EP代码之前就被调用执行了,如果TLS函数内部含有反调试代码,这使程序直接无法继续。需要如下操作选项----->调试设置---->事件------->点击 系统断点
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

调试器暂停的位置即是系统启动断点,在OD调试器的默认设置下,调试器会在EP处暂停,而WinDbg调试器默认在系统启动断点暂停

补充

DLL程序入口点函数:DllMain,注意:大小写是区别的(仅导出资源的DLL可以没有DllMain函数)

函数原型:

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    return TRUE;
}

hModule参数:指向DLL本身的实例句柄;

ul_reason_for_call参数:指明了DLL被调用的原因,可以有以下4个取值:
1.DLL_PROCESS_ATTACH:
当DLL被进程 <<第一次>> 调用时,导致DllMain函数被调用,

同时ul_reason_for_call的值为DLL_PROCESS_ATTACH,

如果同一个进程后来再次调用此DLL时,操作系统只会增加DLL的使用次数,

不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数。

2.DLL_PROCESS_DETACH:
当DLL被从进程的地址空间解除映射时,系统调用了它的DllMain,传递的ul_reason_for_call值是DLL_PROCESS_DETACH。
★如果进程的终结是因为调用了TerminateProcess,系统就不会用DLL_PROCESS_DETACH来调用DLL的DllMain函数。这就意味着DLL在进程结束前没有机会执行任何清理工作。

3.DLL_THREAD_ATTACH:
当进程创建一线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,

并用值DLL_THREAD_ATTACH调用DLL的DllMain函数。

新创建的线程负责执行这次的DLL的DllMain函数,

只有当所有的DLL都处理完这一通知后,系统才允许线程开始执行它的线程函数。

4.DLL_THREAD_DETACH:
如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),

系统查看当前映射到进程空间中的所有DLL文件映像,

并用DLL_THREAD_DETACH来调用DllMain函数,

通知所有的DLL去执行线程级的清理工作。
★注意:如果线程的结束是因为系统中的一个线程调用了TerminateThread,

系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数。

③lpReserved参数:保留,目前没什么意义。

TLS系列

TLS回调函数(1)

TLS回调函数(2)

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寻梦&之璐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值