第一次发帖,请大佬们轻喷。
在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回调函数是在程序运行时由操作系统自动调用的一组函数,用于在进程加载和卸载时执行一些初始化和清理操作。在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
;
}
|
程序执行的效果
实际上还有下面的信息没有输出,原因是因为main()函数结束了触发了TLS回调函数,但是控制台由于mian函数的结束已经不能收下面的信息
1
2
3
|
TLS_CALLBACK1() : DllHandle
=
490000
, Reason
=
0
TLS_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
+
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
|
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_TLS_DIRECTORY32结构体指针。
_IMAGE_TLS_DIRECTORY32的AddressOfCallBacks为红色箭头处
我们发现AddressOfCallBacks的内容不是空就是无效地址
另外通过上面置入汇编的方法我们取TEB的TlsLinks
在Windows操作系统中,TEB(Thread Environment Block)是一个数据结构,它包含了许多有关线程的信息。其中一个字段是TlsLinks,它是一个指向线程的TLS(Thread Local Storage)数组的指针,由一个单向链表组成。
1
|
+
0xf10
TlsLinks : _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