【逆向学习】TLS_Callback TLS回调函数学习以及出题思路

· 2023-03-08 · 951 人浏览

TLS_Callback

TLS_Callback(Thread-local storage,线程本地存储)是WindowsAPI下的一个回调函数,它会在程序进程开始,结束以及线程开始,结束的时候调用。

MSDN地址

TLS在逆向中

TLS_Callback在逆向中有一个特点: 先于main函数执行/在main结束后执行。因此也经常被用作反调试,或者是藏匿变量修改,以防止动态和静态分析。

代码实现

参考此处

#include <stdio.h>
#include <Windows.h>

#ifdef _WIN64
#pragma comment (linker, "/INCLUDE:_tls_used")
#pragma comment (linker, "/INCLUDE:thread_callback_base")
#else
#pragma comment (linker, "/INCLUDE:__tls_used")
#pragma comment (linker, "/INCLUDE:_thread_callback_base")
#endif

void generate_str(char* ptr, DWORD reason)
{
    switch (reason) {
    case DLL_PROCESS_ATTACH:
        strcat_s(ptr ,200, "DLL_PROCESS_ATTACH\n");
        break;
    case DLL_PROCESS_DETACH:
        strcat_s(ptr, 200, "DLL_PROCESS_DETACH\n");
        break;
    case DLL_THREAD_ATTACH:
        strcat_s(ptr, 200, "DLL_THREAD_ATTACH\n");
        break;
    case DLL_THREAD_DETACH:
        strcat_s(ptr, 200, "DLL_THREAD_DETACH\n");
        break;
    }
}

// TLS回调
void NTAPI tls_callback_1(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    char ptr[256]{ 0 };
    strcpy_s(ptr, "    TLS Callback1: ");
    generate_str(ptr, Reason);
    WriteConsoleA(hStdout, ptr, strlen(ptr), NULL, NULL);
}

// TLS回调
void NTAPI tls_callback_2(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    char ptr[256]{ 0 };
    strcpy_s(ptr, "    TLS Callback2: ");
    generate_str(ptr, Reason);
    WriteConsoleA(hStdout, ptr, strlen(ptr), NULL, NULL);
}

#ifdef _WIN64
#pragma const_seg(".CRT$XLF")
EXTERN_C const
#else
#pragma data_seg(".CRT$XLF")
EXTERN_C
#endif
PIMAGE_TLS_CALLBACK thread_callback_base[] = { tls_callback_1, tls_callback_2, 0 };
#ifdef _WIN64
#pragma const_seg()
#else
#pragma data_seg()
#endif //_WIN64

#define OK_PRINT_FLAG(s) ("\033[1;40;32m[+]\033[0m "##s)
#define ERR_PRINT_FLAG(s) ("\033[1;40;31m[-]\033[0m "##s)

// 线程回调
DWORD WINAPI thread_func(
    _In_ LPVOID lpParameter
)
{
    printf(OK_PRINT_FLAG("Enter thread func...\n"));
    Sleep(5000);
    printf(OK_PRINT_FLAG("Leave thread func...\n"));
    return 0;
}

int main(void)
{
    printf(OK_PRINT_FLAG("Enter main...\n"));

    HANDLE hThread = CreateThread(NULL, 0, thread_func, NULL, 0, NULL);
    WaitForSingleObject(hThread, INFINITE);

    printf(OK_PRINT_FLAG("Leave main...\n"));
    return 0;
}

执行效果:

image-20230308150152221

TLS回调函数调用顺序分析

其中,tls_callback的第二个参数Reason可以有四个值分别是DLL_PROCESS_ATTACH、DLL_THREAD_ATTACH、DLL_THREAD_DETACH、DLL_PROCESS_DETACH

分别对应1,2,3,0

因此几次调用TLS回调函数的顺序也是1-2-3-0即DLL_PROCESS_ATTACH->DLL_THREAD_ATTACH->DLL_THREAD_DETACH->DLL_PROCESS_DETACH

在ida中反编译可以看到:

image-20230308145211171

一些逆向出题思路

因此可以在进程启动时,对程序进行反调试,例如

image-20230308145625408

在CTF逆向中,也可以利用DLL_PROCESS_DETACH后于main函数结束执行,在main函数结束后继续调用某些函数。

通过配合CreateThread,对程序中某些变量重复修改也可以打出一套组合拳。

Theme Jasmine by Kent Liao