码迷,mamicode.com
首页 > 其他好文 > 详细

TLS (Thread Local Storage)反调试原理

时间:2015-05-10 09:51:33      阅读:249      评论:0      收藏:0      [点我收藏+]

标签:tls

TLS的特别之处在于使得程序的入口点EP不是第一条执行的指令,所以常常用于反调试检测之中。
用一个已经开启的TLS的程序来做说明。

数据结构

TLS存在于PE文件格式之中。IMAGE_DATA_DIRECTORY DataDirectory[9] 存放了TLS目录的地址。

winNT.h [F12 可得到定义位置]
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory

同其他目录表数组一样,也是8字节结构 (VA+Size)

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

技术分享

从TLS的VA处,可以找到该目录的详细信息。

32位下的TLS目录详情
typedef struct _IMAGE_TLS_DIRECTORY32 {   //SIZE:0x18h
    DWORD   StartAddressOfRawData;
    DWORD   EndAddressOfRawData;
    DWORD   AddressOfIndex;             // PDWORD
    DWORD   AddressOfCallBacks;         // PIMAGE_TLS_CALLBACK *
    DWORD   SizeOfZeroFill;
    DWORD   Characteristics;
} IMAGE_TLS_DIRECTORY32;
typedef IMAGE_TLS_DIRECTORY32 * PIMAGE_TLS_DIRECTORY32;

技术分享

AddressOfcallBacks是一个指向指针数组的指针,指向的指针数组是TLS注册的回调函数地址。回调函数以数组形式连续分布,并以一个全为0的DWORD值来表示结束。
一个TLS可以有多个回调函数,这些回调函数都会被调用。

技术分享

因此00401000 处就是TLS注册的回调函数,也可以看到,本程序只注册了一个回调函数。

触发机制

当创建/终止线程时会自动调用TLS的回调函数。
EP是在系统创建了该程序的主线程之后进入的,所以TLS回调函数先于EP运行,这也正是很多反调试技术用TLS的原因。

TLS callback函数的定义

typedef VOID
(NTAPI *PIMAGE_TLS_CALLBACK) (
    PVOID DllHandle,     //模块句柄,即加载地址
    DWORD Reason,        
    PVOID Reserved
    );

其中reason有以下几种:(winNT.h)
#define DLL_PROCESS_ATTACH   1    进程启动
#define DLL_THREAD_ATTACH    2    线程启动
#define DLL_THREAD_DETACH    3    线程退出
#define DLL_PROCESS_DETACH   0    进程退出

编程实例

// tls_test.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>


#pragma comment(linker,"/INCLUDE:__tls_used")

void NTAPI tls_callback1(LPVOID dllhhanle,DWORD reason,PVOID Reserved)
{
    printf("Tls_callback1 :dllhandle=%x,reason=%d\n",dllhhanle,reason);
}


void NTAPI tls_callback2(LPVOID dllhhanle,DWORD reason,PVOID Reserved)
{
    printf("Tls_callback2 :dllhandle=%x,reason=%d\n",dllhhanle,reason);
}


#pragma  data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[]={tls_callback1,tls_callback2,0}; //end with 0
#pragma  data_seg()


DWORD WINAPI ThreadProc(LPVOID lpParam)
{

    printf("ThreadProc() Start\n");
    printf("ThreadProc() end\n");
    return 0;
}


int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hThread = NULL;
    printf("Main Start\n");
    hThread = CreateThread(NULL,0,ThreadProc,NULL,0,NULL);
    WaitForSingleObject(hThread,60*1000);
    CloseHandle(hThread);
    printf("Main end\n");
    return 0;

}

关于代码的说明,在《黑客免杀攻防》上说的非常清楚。

#pragma comment(linker,"/INCLUDE:__tls_used")

表明要使用TLS表

#pragma  data_seg(".CRT$XLX")
PIMAGE_TLS_CALLBACK pTLS_CALLBACKs[]={tls_callback1,tls_callback2,0};
#pragma  data_seg()

注册回调函数,.CRT$XLX
CRT表示C RunTime` 机制
x 表示标识名随机,但不代表这里可随机,只能用X
L 表示tls callback section
X (B-Y)任意一个字母都可以

    WaitForSingleObject(hThread,60*1000);

保证主线程执行时,创建的线程已经退出。(使主线程阻塞60s)

有意思的是,上述代码是在VS2008下编译的。用VC6.0编译,TLS无效。当时就不知道怎么办了,在网上搜到了解决方案。先说点题外话,这就是和大牛的差距,他们可能是这么想的,如果TLS调用除了问题,那么一定是TLS在PE中的结构有问题。然后他们去查看TLS结构,果然发现问题在了这里。所以,想法很重要,没出来结果就要好好琢磨,想出来就去试试。

我来对这个问题进行一下总结,并给出新的解决方法。
TLS回调问题,网上已有人给出了解决方案,但总是有一些问题,基本如下:
  1、VC6不支持。
  2、VS2005的Debug版正常,Release版不正常。
  3、VS2005的Release版正常,Debug版不正常。
VC6不支持的原因是VC6带的TLSSUP.OBJ有问题,它已定义了回调表的第一项,并且为00意味着回调表的结束,因此我们加的函数都不会被调用。[INDENT]对于第2个问题,我没遇到,倒是遇到了第3个问题。对这个问题进行了一下研究,发现问题所在:在Link过程中节.CRT$XLA和.CRT$XLB合并时,应该是按字母顺序无间隙合并,但在DEBUG版的输出中实事并非如此,顺序没错,但却产生了很大的间隙,间隙填0,相当于在我们的回调表前加0若干个0,又是回调表提前结束,这也许是BUG。针对第二种情况,我没有遇到,不知道是否是这个原因,如果是,则我想应是LINK的BUG。 
    针对上述问题,本来我想可以使用VS2008的tlssup.obj,但是它与VC6的不兼容,改起来比较麻烦,后来我突然想到,也许我们可以自己创建一个tlssup.obj,基于这个思路,写了自己的tlssup,目前测试结果显示,它可以兼容VC6,VS2005,VS2008,代码如下:

       /*文件名:tlssup.c, 要求以C方式编译, 如果你的工程是CPP工程,请针对此源文件取消预编译头*/
  #include <windows.h>
  #include <winnt.h>

  int _tls_index=0;

  #pragma data_seg(".tls")
  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;

  #pragma data_seg(".rdata$T")

  extern PIMAGE_TLS_CALLBACK my_tls_callbacktbl[];

  IMAGE_TLS_DIRECTORY32 _tls_used={(DWORD)&_tls_start,(DWORD)&_tls_end,(DWORD)&_tls_index,(DWORD)my_tls_callbacktbl,0,0};

  /*tlssup.c结束*/


    然后,我们在其它CPP文件中定义my_tls_callbacktbl如下即可:
  extern "C" PIMAGE_TLS_CALLBACK my_tls_callbacktbl[] = {my_tls_callback1,0};  //可以有多个回调,但一定要在最后加一个空项,否则很可能出错。
    当然下面一行也不能少:
#pragma comment(linker, "/INCLUDE:__tls_used") 

自己一看,VC6.0下的那个程序回调函数地址数组确实第一个为0了,修改了之后,发现创建线程的tls回调没问题,但是main函数前并没有tls_callback执行,想不出来原因。反调试最好的方式就是在EP前检测,所以VC6.0按照这个方法的做不了。后来干脆用vs2008了。

vs2008 正确版本的执行结果:
C:\VC6\MyProjects\tls_test\Release>tls_test.exe
Tls_callback1 :dllhandle=400000,reason=1 //程序启动(但是还未进入EP)
Tls_callback2 :dllhandle=400000,reason=1
Main Start
Tls_callback1 :dllhandle=400000,reason=2  //创建的线程启动
Tls_callback2 :dllhandle=400000,reason=2
ThreadProc() Start
ThreadProc() end
Tls_callback1 :dllhandle=400000,reason=3  //创建的线程执行完毕
Tls_callback2 :dllhandle=400000,reason=3
Main end
Tls_callback1 :dllhandle=400000,reason=0  //程序退出
Tls_callback2 :dllhandle=400000,reason=0

有两个疑问:
1. TLS回调函数和DLLMain函数的参数一致,那么他们的ReasonForCall 代表的意思一样吗?
比如dll_process_attach 表示dll创建,还是process创建。
还有,一个程序先创建进程,tls一次,再创建主线程,按理说应该tls一次呢,怎么没有呢。main函数是运行在主线程上的吧。
2. <逆向工程核心原理> p457,讲到如果创建了一个线程,那么TLS就在创建线程之前执行。可《黑客免杀技术》又说是执行前,也就是创建了之后,执行前。郁闷,觉得不对啊,应该是创建了线程之后,此时线程未启动,而先启动TLS,也就是说线程已经创建了。从我的理解来说或,可能是作者认为:线程的创建成功是以线程启动了作为标识。
我感觉这些其实就是线程创建后的回调函数(当然不是执行函数),所以应该是创建成功了。不过还没有执行而已。回调函数嘛,不就是完事之后给你一个通知,然后我去自定义回调函数嘛。。
3. 事实上,这个PE文件有.tls段。tls directory存放在.rdata区,回调函数地址在.rdata区,回调函数内容在.text区,和tls区有毛线关系?所以,不知道这个.tls段有毛用啊。

哎,说多了都是泪,对线程还是一窍不通啊。

TLS (Thread Local Storage)反调试原理

标签:tls

原文地址:http://blog.csdn.net/bugmeout/article/details/45605497

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!