逆向工程核心原理——TLS回调函数

练习

我们先运行HelloTls.exe文件

然后我们使用OD调试这个程序

发现已经和之前打开弹出的内容不同了,这是因为程序运行EP代码之前先调用了TLS回调函数,而这个函数中存在反调试代码,使程序在被调试时弹出”Debugger Detected!”。

TLS原理分析

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

IMAGE_DATA_DIRECTORY[9]

如果在程序中使用了TLS功能,PE文件的数据目录表第十项就会设置TLS表,RVA为9310

IMAGE_TLS_DIRECTORY

typedef struct _IMAGE_TLS_DIRECTORY32 
{
    DWORD   StartAddressOfRawData;
    DWORD   EndAddressOfRawData;
    PDWORD  AddressOfIndex;
    PIMAGE_TLS_CALLBACK *AddressOfCallBacks;
    DWORD   SizeOfZeroFill;
    DWORD   Characteristics;
} IMAGE_TLS_DIRECTORY32

IMAGE_TLS_DIRECTORY有两种版本,分别为32位版本和64位版本

这里面比较重要的是AddressOfCallbacks,这个值指向含有TLS回调函数地址(VA)的数组,这就意味着可以向同一程序注册多个TLS回调函数

回调函数地址数组

该数组存储的就是TLS回调函数的地址。进程启动运行时,也就是EP代码执行前系统会逐一调用存储在该数组中的函数。

TLS回调函数

TLS回调函数是指,每当创建/终止进程的线程时会自动调用执行的函数,而创建进程的主线程也会自动调用回调函数,且会在EP代码之前执行,反调试技术利用的就是这个特征。

IMAGE_TLS_CALLBACK

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

仔细观察函数定义,我们发现这个函数与DllMain()函数的定义类似。

DllHandle为模块句柄,即加载地址

Reason为调用TLS回调函数的原因

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

练习-TlsTest.exe

#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#pragma comment(linker, "/INCLUDE:__tls_used")
void print_console(char* szMsg)
{
    HANDLE hStdout = GetStdHandle(STD_OUT1PUT_HANDLE);

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

在这份代码里,先注册了2个TLS回调函数(TLS_CALLBACK1、TLS_CALLBACK2)。它们的操作就是把DllHandle和Reason打印到控制台,然后退出。

DLL_PROCESS_ATTACH

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

DLL_THREAD_ATTACH

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

DLL_THREAD_DETACH

TLS回调函数全部执行完后,ThreadProc开始执行,在执行完后,Reason为3,此时TLS回调函数被调用执行

DLL_PROCESS_DETACH

main()主线程也会终止,此时Reason为0,TLS回调函数最后一次被调用执行。

调试TLS回调函数

如果直接使用调试器打开带有TLS回调函数的程序,则无法调试TLS回调函数,因为TLS回调函数会在EP代码之前执行,所以我们在调试代码之前需要对OD的设置进行一些修改

此时再调试带有TLS的程序时,会自动在ntdll.dll模块内部的“System Startup Breakpoint”处暂停,调试器暂停的位置是系统启动断点。然后根据前面的IMAGE_TLS_DIRECTORY获取TLS回调函数的地址,在回调函数的起始地址设置好断点,这样就可以进行TLS回调函数的调试了。

前面我们知道回调函数的首地址是00401000,所以我们在这个地方下个断点,然后F9运行到这。

代码逻辑比较清晰,通过IsDebuggerPresent的API来进行反调试,我们通过修改flags中的Z位置1,就可以绕过反调试了

手工添加TLS回调函数

设计规划

首先要确定IMAGE_TLS_DIRECTORY结构体与TLS回调函数放到文件的哪个位置。向PE文件添加代码或者数据时。有如下3种方法来查找合适位置:

  1. 添加到节区末尾的空白区域

  2. 增加最后一个节区的大小

  3. 在最后添加新节区

我们采用第二种方法,增加最后一个节区的大小。

Pointer to Raw Data=9000

Size of Raw Data=600

所以PE头中定义的文件整体大小为9600

我们用010editor打开hello.exe,然后在尾部插入200h个字节

编辑PE文件头

将.rcrs节区头中的Size of Raw Data=800,Characteristics=E0000060

在原有属性的基础上新增加了IMAGE_SCN_CNT_CODE|IMAGE_SCN_MEM_EXECUTE|IMAGE_SCN_MEM_WRITE属性

IMAGEE_DATA_DIRECTORY[9]

接下来要设置TLS表的值

我们的TLS表放在9600的位置,大小为24个字节(0x18),9600是文件偏移,转化成RVA就是1 E600

设置IMAGE_TLS_DIRECTORY结构体

我们在9600位置开始填入数据,大小为0x18个字节,把从9618开始后面0x18字节,0x18/4=6个地址,刚好赋给IMAGE_TLS_DIRECTORY,在9630位置设置函数地址,存放在Address of Callbacks指向的回调函数数组中,其中C20C是机器码,对应汇编就是 RETN 0C,也就是说这个函数只进行了平衡堆栈的操作,TLS有三个参数,所以retn 0xc

编写TLS回调函数

可以利用OD直接写汇编,最终将程序保存下来就可以了


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 767778848@qq.com