记录黑客技术中优秀的内容, 传播黑客文化,分享黑客技术精华

TLS反调试的前世今生

2013-03-18 21:25

#冷夜

文章已发表至《黑客防线》2012.11期,转载请注明出处!

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

ps:原谅今天才想起来这篇文章分享出来…

0×00 TLS简述

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

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

1.png

图 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. int main()
  21. {
  22. MessageBox(NULL,”Main!”,”Test1″,MB_OK);
  23. return 0;
  24. }
  25. //anti-debug1 进程遍历
  26. void lookupprocess()
  27. {
  28. PROCESSENTRY32 pe32;
  29. pe32.dwSize = sizeof(pe32);
  30. HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
  31. BOOL bMore = ::Process32First(hProcessSnap,&pe32);
  32. while(bMore)
  33. {
  34. strlwr(pe32.szExeFile);
  35. if (!strcmp(pe32.szExeFile,”ollyice.exe”))
  36. {
  37. exit(0);
  38. }
  39. if (!strcmp(pe32.szExeFile,”ollydbg.exe”))
  40. {
  41. exit(0);
  42. }
  43. if (!strcmp(pe32.szExeFile,”peid.exe”))
  44. {
  45. exit(0);
  46. }
  47. if (!strcmp(pe32.szExeFile,”idaq.exe”))
  48. {
  49. exit(0);
  50. }
  51. bMore = ::Process32Next(hProcessSnap,&pe32);
  52. }
  53. ::CloseHandle(hProcessSnap);
  54. }
  55. //anti-debug2
  56. void Debugger(void)
  57. {
  58. int result=0;
  59. __asm{
  60. mov     eax, dword ptr fs:[30h]//TEB偏移30H处
  61. movzx   eax, byte ptr ds:[eax+2h]//取PEB中BeingDebug,若为1则被调试
  62. mov
  63. result,eax
  64. }
  65. if (result) exit(0);
  66. }

复制代码

 

 

2.png

图 2

 

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

.CRT表明是使用C RunTime机制$后面的XLBX表示随机的标识L表示是TLS callback sectionB可以被换成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意味着回调表的结束,因此我们加的函数都不会被调用。

3.png

图 3

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

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

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

4.png

图 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加载程序调试进程退出

5.png

图 5

 

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

 

0×04 汇编实现

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

 

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

复制代码

 

 

0×05 反反调试之策

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

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

6.png

图 6

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

函数段:.text

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

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

7.png

图 7

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

 

8.png

图 8

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

9.png

图 9

 

0×06 一点扩展

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

原创文章,转载请注明: 转载自Black-Hat Security Team

本文链接地址: TLS反调试的前世今生

知识来源: www.bhst.org/?p=146

阅读:232259 | 评论:0 | 标签:逆向破解 TLS 反调试

想收藏或者和大家分享这篇好文章→复制链接地址

“TLS反调试的前世今生”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

公告

关注公众号hackdig,学习最新黑客技术

推广

工具

标签云