逆向工程核心原理——第四十五章

回调函数(TLS)

若在编程中启用了TLS功能,PE头文件中就会设置一个TLS表(TLS Table)。

(IMAGE_NT_HEADERS-IMAGE_OPTIONAL_HEADER-IMAGE_DATA_DIRECTORY[9]))

我们可以看到TLS回调函数的定义:

启动参数DllHandle为句柄,参数reason为调用函数的原因。

我们通过一个实验,来准确理解这四种状态。

TlsTest.exe

#include <windows.h>
#include<iostream>

//告知连接器使用TLS
#pragma comment(linker, "/INCLUDE:__tls_used")

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

运行结果为:

*注:编译时请选择Debug版本,否则程序只会运行main函数中的内容。

通过上面实验我们可以发现:

1.当进程的主程序调用main()前,已注册的TLS回调函数会被调用执行,此时Reason的值为1.

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

3.等Threadproc()线程函数执行结束后,TLS会被再次执行,Reason为3。

4.当main()函数结束时,TLS回再次调用,此时Reason为0.

posted @ 2020-11-09 16:02  Kylimi  阅读(101)  评论(0编辑  收藏  举报