在steam客戶端注入dll的時候發現steam客戶端直接消失,一番調試操作,我懷疑是不是steam有什么檢測?用x32dbg附加后發現斷在了TLS回調函數處!

這是什么玩意?bing一下,發現與反調試相關,更加驗證了我的猜想。

又一番操作發現我解決不了這個檢測,于是就想著先寫一個含有TLS回調函數的程序,逆自己的程序,熟悉一下。

后來,我知道為什么我注入dll到steam客戶端,會導致steam客戶端直接消失了,原來是我dll代碼的問題。

BOOL WINAPI 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回調函數介紹

TLS回調函數是在程序運行時由操作系統自動調用的一組函數,用于在進程加載和卸載時執行一些初始化和清理操作。在Windows操作系統中,TLS回調函數是通過TLS回調表來管理的。

TLS回調函數可以用于在進程加載時初始化線程本地存儲(TLS)數據、打開文件、創建共享內存對象等操作。類似地,在進程卸載時,TLS回調函數可以用于釋放先前分配的內存、關閉文件和清理其他資源。這些回調函數通常在DLL文件中實現,并通過動態鏈接庫(DLL)的入口點DllMain函數進行注冊。

在TLS回調函數中,可以訪問當前線程的TLS數據,并對其進行修改或檢查。可以在TLS回調函數中使用操作系統提供的函數來完成各種任務,例如GetModuleHandle、LoadLibrary、GetProcAddress等。

總的來說,TLS回調函數是一組非常有用的函數,可以在程序加載和卸載時執行一些重要的初始化和清理操作,有助于提高程序的穩定性和安全性。

值得一提的是TLS回調可以用來反調試,原理實為在實際的入口點代碼執行之前執行檢測調試器代碼。

測試程序源代碼

下面程序并沒有加入檢測調試的代碼,但提供了反反調試的思路。

#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");
    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");
    system("pause");
    return 0;
}

程序執行的效果

實際上還有下面的信息沒有輸出,原因是因為main()函數結束了觸發了TLS回調函數,但是控制臺由于mian函數的結束已經不能收下面的信息。

TLS_CALLBACK1() : DllHandle = 490000, Reason = 0
TLS_CALLBACK2() : DllHandle = 490000, Reason = 0

逆向此程序

編譯后用x32dbg載入我們程序。

發現x32dbg斷在了TLS回調函數這個地方。

怎么不讓TLS回調函數執行?

先說結論:應該在這個函數首行如圖中1處,寫入ret 0xC 。

為什么是ret 0xC呢?

下面為TLS的回調函數原型,有三個參數

void NTAPI TlsCallBackFunction(PVOID Handle, DWORD Reason, PVOID Reserve);

我們看圖中2處,棧頂為這個回調函數執行完后返回的地址,棧頂+4、+8、+c為調用回調函數傳入的參數,+10處為調用這個回調函數的ebp的值。為了棧平衡,我們要把傳進這個回調函數的參數所占用的棧空間干掉,三個參數的大小為0xC。

因此ret 0xC。

能否在程序運行過程中找到TLS回調函數數組?

先說結論:不能

先來了解兩個數據結構。

TEB

nt!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   +0x034 LastErrorValue   : Uint4B
   +0x038 CountOfOwnedCriticalSections : Uint4B
   +0x03c CsrClientThread  : Ptr32 Void
   +0x040 Win32ThreadInfo  : Ptr32 Void
   +0x044 User32Reserved   : [26] Uint4B
   +0x0ac UserReserved     : [5] Uint4B
   +0x0c0 WOW32Reserved    : Ptr32 Void
   +0x0c4 CurrentLocale    : Uint4B
   +0x0c8 FpSoftwareStatusRegister : Uint4B
   +0x0cc SystemReserved1  : [54] Ptr32 Void
   +0x1a4 ExceptionCode    : Int4B
   +0x1a8 ActivationContextStack : _ACTIVATION_CONTEXT_STACK
   +0x1bc SpareBytes1      : [24] UChar
   +0x1d4 GdiTebBatch      : _GDI_TEB_BATCH
   +0x6b4 RealClientId     : _CLIENT_ID
   +0x6bc GdiCachedProcessHandle : Ptr32 Void
   +0x6c0 GdiClientPID     : Uint4B
   +0x6c4 GdiClientTID     : Uint4B
   +0x6c8 GdiThreadLocalInfo : Ptr32 Void
   +0x6cc Win32ClientInfo  : [62] Uint4B
   +0x7c4 glDispatchTable  : [233] Ptr32 Void
   +0xb68 glReserved1      : [29] Uint4B
   +0xbdc glReserved2      : Ptr32 Void
   +0xbe0 glSectionInfo    : Ptr32 Void
   +0xbe4 glSection        : Ptr32 Void
   +0xbe8 glTable          : Ptr32 Void
   +0xbec glCurrentRC      : Ptr32 Void
   +0xbf0 glContext        : Ptr32 Void
   +0xbf4 LastStatusValue  : Uint4B
   +0xbf8 StaticUnicodeString : _UNICODE_STRING
   +0xc00 StaticUnicodeBuffer : [261] Uint2B
   +0xe0c DeallocationStack : Ptr32 Void
   +0xe10 TlsSlots         : [64] Ptr32 Void
   +0xf10 TlsLinks         : _LIST_ENTRY
   +0xf18 Vdm              : Ptr32 Void
   +0xf1c ReservedForNtRpc : Ptr32 Void
   +0xf20 DbgSsReserved    : [2] Ptr32 Void
   +0xf28 HardErrorsAreDisabled : Uint4B
   +0xf2c Instrumentation  : [16] Ptr32 Void
   +0xf6c WinSockData      : Ptr32 Void
   +0xf70 GdiBatchCount    : Uint4B
   +0xf74 InDbgPrint       : UChar
   +0xf75 FreeStackOnTermination : UChar
   +0xf76 HasFiberData     : UChar
   +0xf77 IdealProcessor   : UChar
   +0xf78 Spare3           : Uint4B
   +0xf7c ReservedForPerf  : Ptr32 Void
   +0xf80 ReservedForOle   : Ptr32 Void
   +0xf84 WaitingOnLoaderLock : Uint4B
   +0xf88 Wx86Thread       : _Wx86ThreadState
   +0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void
   +0xf98 ImpersonationLocale : Uint4B
   +0xf9c IsImpersonating  : Uint4B
   +0xfa0 NlsCache         : Ptr32 Void
   +0xfa4 pShimData        : Ptr32 Void
   +0xfa8 HeapVirtualAffinity : Uint4B
   +0xfac CurrentTransactionHandle : Ptr32 Void
   +0xfb0 ActiveFrame      : Ptr32 _TEB_ACTIVE_FRAME
   +0xfb4 SafeThunkCall    : UChar
   +0xfb5 BooleanSpare     : [3] UChar

_IMAGE_TLS_DIRECTORY32

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的首地址。

mov eax,fs:[0x2c] //+0x02c ThreadLocalStoragePointer : 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)數組的指針,由一個單向鏈表組成。

+0xf10 TlsLinks : _LIST_ENTRY

發現TlsLinks的值為NULL。

結論:

因此不能在程序運行中找到TLS回調函數表。

原因:在 PE 文件的導入表中,AddressOfCallBacks 字段的值是指向 IAT 表中函數的地址的指針數組,通常用于進行動態鏈接的過程中。但是,在 PE 加載到內存中的時候,AddressOfCallBacks 可能會被動態地填充,因此可能會出現其值為 NULL 的情況。

TlsLinks在線程和TLS回調函數的值都為空的原因還沒調查出來……如果有知道的大神希望能告知。

分析steam的steamservice.dll的TLS回調函數的作用

在IDA中也找到該位置,F5!可以看到當dll注入到steam的主程序中會觸發此TLS回調函數。

這段看不太懂,咱們直接看IDA中的反匯編代碼。

 

因此我們可以推斷,unk_10245850為一個數組,里面存的是函數的調用地址。

以我現在的水平,這個TLS回調函數好像沒什么用……只是設置處理異常的函數鏈?