欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

TLS反调试的前世今生

程序员文章站 2022-03-11 15:28:49
文章已发表至《黑客防线》2012.11期,转载请注明出处! From http://www.bhst.org & http://nightx.info...
文章已发表至《黑客防线》2012.11期,转载请注明出处!

From http://www.bhst.org & http://nightx.info

0×00 TLS简述

Thread Local Storage(TLS),是Windows为解决一个进程中多个线程同时访问全局变量而提供的机制。TLS可以简单地由操作系统代为完成整个互斥过程,也可以由用户自己编写控制信号量的函数。当进程中的线程访问预先制定的内存空间时,操作系统会调用系统默认的或用户自定义的信号量函数,保证数据的完整性与正确性。

而当Coder选择使用自己编写的信号量函数时,在应用程序初始化阶段,系统将要调用一个由用户编写的初始化函数以完成信号量的初始化以及其他的一些初始化工作。此调用必须在程序真正开始执行到入口点之前就完成,以保证程序执行的正确性。

TLS反调试的前世今生

图 1

基于TLS的反调试,原理实为在实际的入口点代码执行之前执行检测调试器代码,实现方式便是使用TLS回调函数实现。通过TLS反调试实现的效果,形如图1,在OD动态调试器加载程序到入口点之前便已经执行反调试代码并退出程序。此外,利用TLS启动时,某些病毒也得以能够在调试器启动之前就开始运行,因为一些调试器是在程序的主入口点处切入的。

 

0×01 函数原型

TLS回调函数原型如下:

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

实现TLS反调试,便是充分利用TLS回调函数在程序入口点之前就能获得程序控制权的特性,使得普通的反调试技术有更好的实际效果。

PE格式中,为TLS数据开辟了一段空间,位置为IMAGE_NT_HEADERS.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]

其中DataDirectory的元素具有如下数据结构:

typedef struct _IMAGE_DATA_DIRECTORY {

DWORD VirtualAddress;

DWORD Size;

} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

TLSDataDirectory元素,VirtualAddress成员指向一个结构体,结构体中定义了访问需要互斥的内存地址、TLS回调函数地址以及其他一些信息。

 

0×02 C++下一个TLS-Anti-DebugDemo

 

微软提供的VC编译器默认都支持直接在程序中使用TLS,要在程序中使用TLS,首先为TLS数据单独建一个数据段,并用相关数据填充此段,通知链接器为TLS数据在PE文件头中添加数据。

由此,给出第一个利用TLS实现加载前反调试的DEMOTest1

代码如下:

 

  1. #include “windows.h”
  2. #include “iostream”
  3. #include”tlhelp32.h”
  4. //通知链接器PE文件要创建TLS目录
  5. #pragma comment(linker, “/INCLUDE:__tls_used”)
  6. void lookupprocess(void);
  7. void Debugger(void);
  8. void NTAPI tls_callback(PVOID h, DWORD reason, PVOID pv)
  9. {
  10. lookupprocess();
  11. Debugger();
  12. MessageBox(NULL,”Not Main!”,”Test1″,MB_OK);
  13. return;
  14. }
  15. //创建TLS段
  16. #pragma data_seg(“.CRT$XLB”)
  17. //定义回调函数
  18. PIMAGE_TLS_CALLBACK p_thread_callback = tls_callback;
  19. #pragma data_seg()
  20.  
  21. int main()
  22. {
  23. MessageBox(NULL,”Main!”,”Test1″,MB_OK);
  24. return 0;
  25. }
  26. //anti-debug1 进程遍历
  27. void lookupprocess()
  28. {
  29. PROCESSENTRY32 pe32;
  30. pe32.dwSize = sizeof(pe32);
  31. HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
  32. BOOL bMore = ::Process32First(hProcessSnap,&pe32);
  33. while(bMore)
  34. {
  35. strlwr(pe32.szExeFile);
  36. if (!strcmp(pe32.szExeFile,”ollyice.exe”))
  37. {
  38. exit(0);
  39. }
  40. if (!strcmp(pe32.szExeFile,”ollydbg.exe”))
  41. {
  42. exit(0);
  43. }
  44. if (!strcmp(pe32.szExeFile,”peid.exe”))
  45. {
  46. exit(0);
  47. }
  48. if (!strcmp(pe32.szExeFile,”idaq.exe”))
  49. {
  50. exit(0);
  51. }
  52. bMore = ::Process32Next(hProcessSnap,&pe32);
  53. }
  54. ::CloseHandle(hProcessSnap);
  55. }
  56.  
  57. //anti-debug2
  58. void Debugger(void)
  59. {
  60. int result=0;
  61. __asm{
  62. mov     eax, dword ptr fs:[30h]//TEB偏移30H处
  63. movzx   eax, byte ptr ds:[eax+2h]//取PEB中BeingDebug,若为1则被调试
  64. mov
  65. result,eax
  66. }
  67. if (result) exit(0);
  68. }

复制代码

 

 

TLS反调试的前世今生

图 2

 

如图2,通过弹窗可以清晰的判断程序执行前首先执行了TLS回调函数。上述代码中,创建TLS段的部分“.CRT$XLB”的含义如下:

.CRT表明是使用C RunTime机制,$后面的XLB:X表示随机的标识,L表示是TLS callback section,B可以被换成BY的任意一个字母,但是不能使用“.CRT$XLA”“.CRT$XLZ”,因为“.CRT$XLA”“.CRT$XLZ”是用于tlssup.obj

如果想要定义多个TLS回调函数,可以将

PIMAGE_TLS_CALLBACK p_thread_callback = tls_callback;

更改为:

PIMAGE_TLS_CALLBACK p_thread_callback [] = {tls_callback_1, tls_callback_2, tls_callback_3,0};

 

注意:

VC6.0下以这种方式会发现TLS实现失效,如图3,原因是VC6带的TLSSUP.OBJ有问题,它已定义了回调表的第一项,并且为00意味着回调表的结束,因此我们加的函数都不会被调用。

TLS反调试的前世今生

图 3

如不得不在VC6.0下实现TLS,可以尝试自己编写TLSSUP.OBJ,方法如下:

建立一个控制台工程,创建tlssup.c文件并将该文件加入工程。

右键该tlssup.c文件,选择Setting[设置]->C/C++->Gategory->TLS反调试的前世今生recomliled Headers[预编译的头文件]->Not using precompiled headers[不适用预补偿页眉]。如图4

TLS反调试的前世今生

图 4

// tlssup.c代码:

 

  1. #include <windows.h>#include <winnt.h>int _tls_index=0;int _tls_start=0;#pragma data_seg(“.tls$ZZZ”)int _tls_end=0;#pragma data_seg(“.CRT$XLA”)int __xl_a=0;#pragma data_seg(“.CRT$XLZ”)int __xl_z=0;extern PIMAGE_TLS_CALLBACK my_tls_callback[];IMAGE_TLS_DIRECTORY32 _tls_used={(DWORD)&_tls_start,(DWORD)&_tls_end,(DWORD)&_tls_index,(DWORD)my_tls_callback,0,0};

 

然后,在其它CPP文件中定义my_tls_callbackt即可:

extern “C” PIMAGE_TLS_CALLBACK my_tls_callback[] = {my_tls_callback1,0};

可以有多个回调,但一定要在最后加一个空项,否则很可能出错。

 

0×03 实现效果

 

针对上面的DEMO1,编译运行后查看不同加载情况下效果:

使用VS2010编译后直接执行,程序正常运行,如上图2

使用VS2010自带的调试器加载调试,进程直接退出;

5使用OllyDbg加载程序调试,进程退出;

TLS反调试的前世今生

图 5

 

当然,最终反调试效果依赖于TLS回调函数中的反调试代码。

 

0×04 汇编实现

高级语言可以做的事情,汇编自然也可以做到,给出一种参考的汇编实现Demo

 

  1. .386
  2. .model flat,stdcall
  3. option casemap:none
  4.  
  5. include \masm32\include\windows.inc
  6. include \masm32\include\user32.inc
  7. include \masm32\include\kernel32.inc
  8. include \masm32\include\ntdll.inc
  9. includelib \masm32\lib\user32.lib
  10. includelib \masm32\lib\kernel32.lib
  11. includelib \masm32\lib\ntdll.lib
  12.  
  13. .const
  14. TLS_CallBackStart dd TlsCallBack0
  15. szInNormal db ‘正常运行中’,0
  16. szTitle db ‘Anti-Debug’,0
  17. szInTls db ‘断点设置出错’,0
  18. szResult db ‘无法写回到已修改的寄存器’,0
  19.  
  20. PUBLIC _tls_used
  21. _tls_used IMAGE_TLS_DIRECTORY <TLS_Start, TLS_End, dwTLS_Index, TLS_CallBackStart, 0, ?>
  22.  
  23. .data?
  24. dwTLS_Index dd ?
  25. dwResult dd  ?
  26.  
  27. OPTION DOTNAME
  28. ;; 定义一个TLS节
  29. .tls SEGMENT
  30. TLS_Start LABEL DWORD
  31. dd 0100h dup(“slt.”)
  32. TLS_End LABEL DWORD
  33. .tls ENDS
  34. OPTION NODOTNAME
  35.  
  36. .code
  37. ; ;TLS回调函数
  38. TlsCallBack0 proc Dllhandle:LPVOID,dwReason:DWORD,lpvReserved:LPVOID
  39. mov     eax,dwReason
  40. cmp     eax,DLL_PROCESS_ATTACH  ; 在进行加载时被调用
  41.     jnz     ExitTlsCallBack0
  42.     invoke  GetCurrentProcessId
  43.     invoke  OpenProcess,PROCESS_ALL_ACCESS,NULL,eax
  44.     invoke  CheckRemoteDebuggerPresent,eax,addr dwResult
  45.     cmp     dword ptr dwResult,0
  46.     jne     _found
  47.     jmp ExitTlsCallBack0
  48. _found:
  49.     ;invoke  MessageBox,NULL,addr szInTls,addr szTitle,MB_ICONWARNING
  50.     invoke  GetCurrentThread
  51.     invoke  NtSetInformationThread,eax,11H,NULL,NULL
  52.     ;invoke  MessageBox,NULL,addr szResult,addr szTitle,MB_ICONWARNING
  53. mov     eax,ebx
  54.  
  55. ExitTlsCallBack0:
  56.     mov     dword ptr[TLS_Start],0
  57.     xor     eax,eax
  58.     inc     eax
  59.     ret
  60.  
  61. TlsCallBack0 endp
  62.  
  63. Start:
  64.     invoke   MessageBox,NULL,addr szInNormal,addr szTitle,MB_OK
  65.     invoke   ExitProcess, 1
  66. end  Start

  

0×05 反反调试之策

针对TLS反调试,给出几种突破方法[注:针对无壳无其他PE加密环境]

1.使用修改版Ollydbg,此处以ShoutBoyJiack版本OD为例,载入程序,直接停在TLS回调函数入口点。而后可以修改代码,跳过反调试部分,保存程序文件,再使用普通OD加载即可。如图6

TLS反调试的前世今生

图 6

2.手工抹掉TLS反调试,使用IDA加载程序,在Function WIndow中发现TlsCallback_0函数。如图7

函数段:.text

位置:00401190[转换得文件偏移为590]

长度:44[对应10进制68字节]

TLS反调试的前世今生

图 7

通过转换得到文件偏移为590,长度为68字节。用C32asm等十六进制编辑器使用00填充此部分十六进制代码。如图8

 

TLS反调试的前世今生

图 8

这时执行程序,发现只弹出第二个窗口。载入OD,已经跳过TLS回调函数的反调试部分直接到达EP。如图9

TLS反调试的前世今生

图 9

 

0×06 一点扩展

利用TLS回调函数在调试器加载前执行Anti-Debug函数保护软件不被恶意修改,关键部分依旧是具体实现反调试部分的代码编写。比如传统的检测断点,检测进程,检测调试器等等。而后,为更好的实现反调试效果,可通过类似加密壳的方式对输入表与输出表等进行加密,更大程度的保护TLS代码不被轻易删除