在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回調函數好像沒什么用……只是設置處理異常的函數鏈?
安全牛
合天網安實驗室
安全圈
E安全
看雪學苑
安全牛
中國信息安全
系統安全運維
安全內參
信息安全與通信保密雜志社
一顆小胡椒
一顆小胡椒