首页
社区
课程
招聘
[原创]x32TLS回调函数实验
2023-5-6 16:26 16876

[原创]x32TLS回调函数实验

2023-5-6 16:26
16876

x32TLS回调函数实验

第一次发帖,请大佬们轻喷。

 

在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
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回调可以用来反调试,原理实为在实际的入口点代码执行之前执行检测调试器代码。

测试程序源代码

下面程序并没有加入检测调试的代码,但提供了反反调试的思路。

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

程序执行的效果

 

image-20230506151923419

 

实际上还有下面的信息没有输出,原因是因为main()函数结束了触发了TLS回调函数,但是控制台由于mian函数的结束已经不能收下面的信息

1
2
3
TLS_CALLBACK1() : DllHandle = 490000, Reason = 0
 
TLS_CALLBACK2() : DllHandle = 490000, Reason = 0

逆向此程序

编译后用x32dbg载入我们程序

 

发现x32dbg断在了TLS回调函数这个地方

 

image-20230505205131717

 

怎么不让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

能否在程序运行过程中找到TLS回调函数数组?

先说结论:不能

 

先来了解两个数据结构

TEB

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
   +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

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] //+0x02c ThreadLocalStoragePointer : Ptr32 Void

ThreadLocalStoragePointer是TEB(Thread Environment Block)结构体中的一个字段,它指向当前线程的TLS(Thread Local Storage)数组的起始地址。

 

也就是说,ThreadLocalStoragePointer的内容都是 _IMAGE_TLS_DIRECTORY32的结构体指针

 

image-20230506112052724

 

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

 

image-20230506153023325

 

_IMAGE_TLS_DIRECTORY32的AddressOfCallBacks为红色箭头处

 

image-20230506153407173

 

image-20230506153113613

 

image-20230506153133807

 

我们发现AddressOfCallBacks的内容不是空就是无效地址

 

另外通过上面置入汇编的方法我们取TEB的TlsLinks

 

在Windows操作系统中,TEB(Thread Environment Block)是一个数据结构,它包含了许多有关线程的信息。其中一个字段是TlsLinks,它是一个指向线程的TLS(Thread Local Storage)数组的指针,由一个单向链表组成。

1
+0xf10 TlsLinks : _LIST_ENTRY

发现TlsLinks的值为NULL

 

image-20230506151219830

 

结论:

 

因此不能在程序运行中找到TLS回调函数表。

 

原因:在 PE 文件的导入表中,AddressOfCallBacks 字段的值是指向 IAT 表中函数的地址的指针数组,通常用于进行动态链接的过程中。但是,在 PE 加载到内存中的时候,AddressOfCallBacks 可能会被动态地填充,因此可能会出现其值为 NULL 的情况。

 

TlsLinks在线程和TLS回调函数的值都为空的原因还没调查出来……如果有知道的大神希望能告知。

分析steam的steamservice.dll的TLS回调函数的作用

image-20230506105233273

 

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

 

image-20230506105328706

 

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

 

image-20230506105550573

 

image-20230506111125284

 

因此我们可以推断,unk_10245850为一个数组,里面存的是函数的调用地址

 

以我现在的水平,这个TLS回调函数好像没什么用……只是设置处理异常的函数链?


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞4
打赏
分享
最新回复 (2)
雪    币: 914
活跃值: (2188)
能力值: ( LV5,RANK:68 )
在线值:
发帖
回帖
粉丝
万剑归宗 1 2023-5-8 13:36
2
0
Mark
雪    币: 19299
活跃值: (28933)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-5-9 09:06
3
1
感谢分享
游客
登录 | 注册 方可回帖
返回