第一次发帖,请大佬们轻喷。
在steam客户端注入dll的时候发现steam客户端直接消失,一番调试操作,我怀疑是不是steam有什么检测?用x32dbg附加后发现断在了TLS回调函数处!
这是什么玩意?bing一下,发现与反调试相关,更加验证了我的猜想。
又一番操作发现我解决不了这个检测,淦,好难啊!
于是就想着先写一个含有TLS回调函数的程序,逆自己的程序,熟悉一下。
后来,我知道为什么我注入dll到steam客户端,会导致steam客户端直接消失了,原来是我dll代码的问题!
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      | BOOLWINAPI DllMain(HMODULE hModule, DWORD dwReason, LPARAM lParam) {    switch (dwReason)    {    case DLL_PROCESS_ATTACH:    {        MessageBoxA(0, "DLL_PROCESS_ATTACH", "报告", 0);        DisableThreadLibraryCalls(hModule);        HANDLE handle =CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)MainThread, hModule, 0, nullptr);        CloseHandle(handle);        break;    }    case DLL_THREAD_ATTACH:    {        MessageBoxA(0, "DLL_THREAD_ATTACH", "报告", 0);        break;    }    }} | 
能看的出上面代码的毛病吗?
没错,没有返回值。但是vs 2019 竟然能编译成功……
如我我稍微仔细一下,加上return true; 也不会有下面的内容了。
 TLS回调函数是在程序运行时由操作系统自动调用的一组函数,用于在进程加载和卸载时执行一些初始化和清理操作。在Windows操作系统中,TLS回调函数是通过TLS回调表来管理的。
 TLS回调函数可以用于在进程加载时初始化线程本地存储(TLS)数据、打开文件、创建共享内存对象等操作。类似地,在进程卸载时,TLS回调函数可以用于释放先前分配的内存、关闭文件和清理其他资源。这些回调函数通常在DLL文件中实现,并通过动态链接库(DLL)的入口点DllMain函数进行注册。
 在TLS回调函数中,可以访问当前线程的TLS数据,并对其进行修改或检查。可以在TLS回调函数中使用操作系统提供的函数来完成各种任务,例如GetModuleHandle、LoadLibrary、GetProcAddress等。
 总的来说,TLS回调函数是一组非常有用的函数,可以在程序加载和卸载时执行一些重要的初始化和清理操作,有助于提高程序的稳定性和安全性。
 值得一提的是TLS回调可以用来反调试,原理实为在实际的入口点代码执行之前执行检测调试器代码。
下面程序并没有加入检测调试的代码,但提供了反反调试的思路。
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      
      32
      
      33
      
      34
      
      35
      
      36
      
      37
      
      38
      
      39
      
      40
      
      41
      
      42
      
      43
      
      44
      
      45
      
      46
      
      47
      | #include <windows.h>#pragma comment(linker, "/INCLUDE:__tls_used")void print_console(char*szMsg){    HANDLE hStdout =GetStdHandle(STD_OUTPUT_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);    return;}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");    return0;}intmain(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");    return0;} | 
程序执行的效果

实际上还有下面的信息没有输出,原因是因为main()函数结束了触发了TLS回调函数,但是控制台由于mian函数的结束已经不能收下面的信息
| 
      1
      
      2
      
      3
      | TLS_CALLBACK1() : DllHandle =490000, Reason =0TLS_CALLBACK2() : DllHandle =490000, Reason =0 | 
编译后用x32dbg载入我们程序
发现x32dbg断在了TLS回调函数这个地方

怎么不让TLS回调函数执行?
先说结论:应该在这个函数首行如图中1处,写入ret 0xC 。
为什么是ret 0xC呢?
下面为TLS的回调函数原型,有三个参数
| 
      1
      | void NTAPI TlsCallBackFunction(PVOID Handle, DWORD Reason, PVOID Reserve); | 
我们看图中2处,栈顶为这个回调函数执行完后返回的地址,栈顶+4、+8、+c为调用回调函数传入的参数,+10处为调用这个回调函数的ebp的值。为了栈平衡,我们要把传进这个回调函数的参数所占用的栈空间干掉,三个参数的大小为0xC
因此ret 0xC
先说结论:不能
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      
      32
      
      33
      
      34
      
      35
      
      36
      
      37
      
      38
      
      39
      
      40
      
      41
      
      42
      
      43
      
      44
      
      45
      
      46
      
      47
      
      48
      
      49
      
      50
      
      51
      
      52
      
      53
      
      54
      
      55
      
      56
      
      57
      
      58
      
      59
      
      60
      
      61
      
      62
      
      63
      
      64
      
      65
      
      66
      
      67
      | nt!_TEB   +0x000NtTib            : _NT_TIB   +0x01cEnvironmentPointer : Ptr32 Void   +0x020ClientId         : _CLIENT_ID   +0x028ActiveRpcHandle  : Ptr32 Void   +0x02cThreadLocalStoragePointer : Ptr32 Void   +0x030ProcessEnvironmentBlock : Ptr32 _PEB   +0x034LastErrorValue   : Uint4B   +0x038CountOfOwnedCriticalSections : Uint4B   +0x03cCsrClientThread  : Ptr32 Void   +0x040Win32ThreadInfo  : Ptr32 Void   +0x044User32Reserved   : [26] Uint4B   +0x0acUserReserved     : [5] Uint4B   +0x0c0WOW32Reserved    : Ptr32 Void   +0x0c4CurrentLocale    : Uint4B   +0x0c8FpSoftwareStatusRegister : Uint4B   +0x0ccSystemReserved1  : [54] Ptr32 Void   +0x1a4ExceptionCode    : Int4B   +0x1a8ActivationContextStack : _ACTIVATION_CONTEXT_STACK   +0x1bcSpareBytes1      : [24] UChar   +0x1d4GdiTebBatch      : _GDI_TEB_BATCH   +0x6b4RealClientId     : _CLIENT_ID   +0x6bcGdiCachedProcessHandle : Ptr32 Void   +0x6c0GdiClientPID     : Uint4B   +0x6c4GdiClientTID     : Uint4B   +0x6c8GdiThreadLocalInfo : Ptr32 Void   +0x6ccWin32ClientInfo  : [62] Uint4B   +0x7c4glDispatchTable  : [233] Ptr32 Void   +0xb68glReserved1      : [29] Uint4B   +0xbdcglReserved2      : Ptr32 Void   +0xbe0glSectionInfo    : Ptr32 Void   +0xbe4glSection        : Ptr32 Void   +0xbe8glTable          : Ptr32 Void   +0xbecglCurrentRC      : Ptr32 Void   +0xbf0glContext        : Ptr32 Void   +0xbf4LastStatusValue  : Uint4B   +0xbf8StaticUnicodeString : _UNICODE_STRING   +0xc00StaticUnicodeBuffer : [261] Uint2B   +0xe0cDeallocationStack : Ptr32 Void   +0xe10TlsSlots         : [64] Ptr32 Void   +0xf10TlsLinks         : _LIST_ENTRY   +0xf18Vdm              : Ptr32 Void   +0xf1cReservedForNtRpc : Ptr32 Void   +0xf20DbgSsReserved    : [2] Ptr32 Void   +0xf28HardErrorsAreDisabled : Uint4B   +0xf2cInstrumentation  : [16] Ptr32 Void   +0xf6cWinSockData      : Ptr32 Void   +0xf70GdiBatchCount    : Uint4B   +0xf74InDbgPrint       : UChar   +0xf75FreeStackOnTermination : UChar   +0xf76HasFiberData     : UChar   +0xf77IdealProcessor   : UChar   +0xf78Spare3           : Uint4B   +0xf7cReservedForPerf  : Ptr32 Void   +0xf80ReservedForOle   : Ptr32 Void   +0xf84WaitingOnLoaderLock : Uint4B   +0xf88Wx86Thread       : _Wx86ThreadState   +0xf94TlsExpansionSlots : Ptr32 Ptr32 Void   +0xf98ImpersonationLocale : Uint4B   +0xf9cIsImpersonating  : Uint4B   +0xfa0NlsCache         : Ptr32 Void   +0xfa4pShimData        : Ptr32 Void   +0xfa8HeapVirtualAffinity : Uint4B   +0xfacCurrentTransactionHandle : Ptr32 Void   +0xfb0ActiveFrame      : Ptr32 _TEB_ACTIVE_FRAME   +0xfb4SafeThunkCall    : UChar   +0xfb5BooleanSpare     : [3] UChar | 
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      | 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; | 
已知在系统32位体系下fs:[0]就是TEB结构体的首地址
在TLS回调函数内通过x32dbg置入如下汇编代码,取到ThreadLocalStoragePointer的首地址
| 
      1
      | mov eax,fs:[0x2c] //+0x02cThreadLocalStoragePointer : Ptr32 Void | 
ThreadLocalStoragePointer是TEB(Thread Environment Block)结构体中的一个字段,它指向当前线程的TLS(Thread Local Storage)数组的起始地址。
也就是说,ThreadLocalStoragePointer的内容都是 _IMAGE_TLS_DIRECTORY32的结构体指针

这里我们看到有三个_IMAGE_TLS_DIRECTORY32结构体指针。

_IMAGE_TLS_DIRECTORY32的AddressOfCallBacks为红色箭头处



我们发现AddressOfCallBacks的内容不是空就是无效地址
另外通过上面置入汇编的方法我们取TEB的TlsLinks
在Windows操作系统中,TEB(Thread Environment Block)是一个数据结构,它包含了许多有关线程的信息。其中一个字段是TlsLinks,它是一个指向线程的TLS(Thread Local Storage)数组的指针,由一个单向链表组成。
| 
      1
      | +0xf10TlsLinks : _LIST_ENTRY | 
发现TlsLinks的值为NULL

结论:
因此不能在程序运行中找到TLS回调函数表。
原因:在 PE 文件的导入表中,AddressOfCallBacks 字段的值是指向 IAT 表中函数的地址的指针数组,通常用于进行动态链接的过程中。但是,在 PE 加载到内存中的时候,AddressOfCallBacks 可能会被动态地填充,因此可能会出现其值为 NULL 的情况。
TlsLinks在线程和TLS回调函数的值都为空的原因还没调查出来……如果有知道的大神希望能告知。

在IDA中也找到该位置,F5! 可以看到当dll注入到steam的主程序中会触发此TLS回调函数

这段看不太懂,咱们直接看IDA中的反汇编代码


因此我们可以推断,unk_10245850为一个数组,里面存的是函数的调用地址
以我现在的水平,这个TLS回调函数好像没什么用……只是设置处理异常的函数链?
更多【x32TLS回调函数实验】相关视频教程:www.yxfzedu.com