zoukankan      html  css  js  c++  java
  • TLS回调函数

    @author: dlive

    TLS (Thread Local Storage 线程局部存储 )回调函数常用于反调试。

    TLS回调函数的调用运行要先于EP代码执行,该特性使它可以作为一种反调试技术使用。

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

    0x01 PE TLS Table

    若在编程中启用了TLS功能,PE头文件中就会设置TLS表(IMAGE_NT_HEARDERS->IMAGE_OPTIONAL_HEADER->IMAGE_DATA_DIRECTORY[9])

    可以看到TLS Table的RVA是00009310,找到对应位置如下

    TLS Table中比较重要的成员为AddressOfCallbacks,该值指向含有TLS回调函数地址(VA)的数据(一个程序中可以注册多个TLS回调函数)

    0x02 TLS回调函数

    TLS回调函数是指,每当创建/终止进程的线程时会自动调用执行的函数(前后共调用两次)。创建进程的主线程时也会自动调用回调函数,且其调用执行先于EP代码。

    TLS回调函数的声明:

    void NTAPI TLS_CALLBACK(PVOID DllHandle, DWORD Reason, PVOID Reserved)
    

    DllMain的声明:

    BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
    

    可以看到两者声明非常相似,第一个参数为模块句柄,即加载地址,第二个参数为调用原因

    调用原因有四种

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

    TlsTest.cpp

    #include <windows.h>
    
    //告知连接器使用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
    ", 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
    ", 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
    ");
    
        print_console("ThreadProc() end
    ");
    
        return 0;
    }
    
    int main(void)
    {
        HANDLE hThread = NULL;
    
        print_console("main() start
    ");
    	//创建子线程
        hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
      	//等待子线程结束
        WaitForSingleObject(hThread, 60*1000);
        CloseHandle(hThread);
    
        print_console("main() end
    ");
    
        return 0;
    }
    

    主线程调用main前调用TLS回调函数,调用原因为DLL_PROCESS_ATTACH

    子线程启动前调用TLS,原因为DLL_THREAD_ATTACH

    子线程结束后调用TLS,原因为DLL_THREAD_DETACH

    主线程结束后调用TLS的原因为DLL_PROCESS_DETACH

    0x03 调试TLS回调函数

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

    调试TLS回调函数时,因为回调函数代码在EP之前就已经执行了,所以调试选项需要设置暂停于系统断点(system breakpoint), 设置后调试器会在ntdll.dll模块内部的“system startup breakpoint‘处暂停

    然后在PE中找到回调函数的地址,下断点调试即可

    OD2.0中直接提供暂停在TLS函数的选项

  • 相关阅读:
    Linux学习篇(四):学习 gdb
    Linux学习篇(三):学习 gcc
    c#序列化感悟(重点讲讲二进制序列化)
    参数保存随笔
    写程序时try,catch查看报错的行号
    stram流char[]保存,支持中文,Filestram需要先转byte[]才能使用,但是性能更好《转载》
    c#序列化和反序列化《转载》
    如果两个测量设备出现相关性数据问题,且过度像素没区别?(打光效果一致),怎么办
    当Hobject类型出现内存泄漏爆炸增长的问题,怎么处理
    使用gige2500万相机时遇见的问题(条纹以及取图过久)
  • 原文地址:https://www.cnblogs.com/dliv3/p/6489629.html
Copyright © 2011-2022 走看看