TLS (Thread Local Storage)反调试原理

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有问题,它已定义了回调表的第一项,并且为0,0意味着回调表的结束,因此我们加的函数都不会被调用。[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段有毛用啊。

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

时间: 2024-08-25 07:11:40

TLS (Thread Local Storage)反调试原理的相关文章

[并发并行]_[C/C++]_[使用线程本地存储Thread Local Storage(TLS)调用复制文件接口的案例]

使用场景: 1. 在复制文件时,一般都是一个线程调用一个接口复制文件,这时候需要缓存数据,如果每个文件都需要创建独立的缓存,那么内存碎片是很大的. 如果创建一个static的内存区,当多线程调用同一个接口时,多个线程同时使用同一个static缓存会造成数据污染.最好的办法是这个缓存只对这个线程可见, 当线程创建时创建缓存区,当线程结束时销毁缓存区. 2. 代码里有注释: test.cpp #include <iostream> #include "pthread.h" #i

[并发并行]_[C/C++]_[使用线程本地存储Thread Local Storage(TLS)-win32和pthread比较]

场景: 1.  需要统计某个线程的对象上创建的个数. 2. 当创建的堆空间需要根据线程需要创建和结束时销毁时. 3. 因为范围是线程只能看到自己的存储数据,所以不需要临界区或互斥量来维护自己的堆内存. 加入如果用全局std::map实现,那么必须在put和get时加锁,这是很损耗资源的. 4. 可以用在维护一个连接,比如socket,database连接. 说明: 1. Java也有自己的线程本地存储ThreadLocal 2. pthread的win32版本: http://sourcewar

TLS 与 python thread local

TLS 先说TLS( Thread Local Storage),wiki上是这么解释的: Thread-local storage (TLS) is a computer programming method that uses static or global memory local to a thread. 线程本地存储(TLS)是一种电脑编程技术, 它用静态或者全局的存储器来保存线程本地的变量(意译). 其目的是为了实现变量隔离,即“同一个”全局变量,对于不同的线程,其值可以不同(类似

【转】反调试技巧总结-原理和实现

总结: 1.  FindWindow.比如 FindWindowA("OLLYDBG", NULL); 2.  EnumWindow函数调用后,系统枚举所有顶级窗口,为每个窗口调用一次回调函数.在回调函数中用 GetWindowText得到窗口标题,进行检测. 3.  GetForeGroundWindow返回前台窗口(用户当前工作的窗口).当程序被调试时,调用这个函数将获得Ollydbg的窗口句柄,再用GetWindowTextA检测. 4.枚举进程列表,看是否有调试器进程(OLLY

WIN10 X64下通过TLS实现反调试

目录(?)[-] TLS技术简介 1 TLS回调函数 2 TLS的数据结构 具体实现及原理 1 VS2015 X64 release下的demo 2 回调函数的具体实现 21 使用IsDebuggerPresent检测调试器 22 使调DebugPort检测调试器 实际测试 1 测试直接执行 2 测试用调试器加载 总 结 1 TLS技术简介 Thread Local Storage(TLS),是Windows为解决一个进程中多个线程同时访问全局变量而提供的机制.TLS可以简单地由操作系统代为完成

一种基于TLS的高级反调试技术

盗版行为日益猖獗,严重影响到软件开发者和开发商的知识产权及利益,反盗版技术的重要性也越来越引起人们的重视.在反盗版技术中,起最大作用的当属反调试技术.然而传统的反调试技术都存在一个弱点:他们都在程序真正开始执行之后才采取反调试手段.实际上在反调试代码被执行前,调试器有大量的时间来影响程序的执行,甚至可以在程序入口处插入断点命令来调试程序.对于使用C/C++语言编译的程序来说,问题通常会更严重,在执行到main()函数之前,会执行C/C++编译器插入的很大一段代码,这也给调试器带来影响程序执行的机

浅谈Android反调试 之 PTRACE_TRACEME

反调试原理: 关于Ptrace:  http://www.cnblogs.com/tangr206/articles/3094358.html ptrace函数 原型为: #include <sys/ptrace.h>long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);ptrace有四个参数:  1). enum __ptrace_request request:指示了ptrace要执行的命

浅谈android反调试之 签名校验

反调试原理 很多时候,我们都需要进行修改修改应用程序的指令,然后重打包运行,重新打包就需要充签名. 利用签名的变化我们用于反调试.反调试实现代码如下: 为了更加隐藏,比较函数可能在SO层进行实现,如下, 还可以实现的更隐藏 解决方案: 通过全局搜索getPackageInfo 等关键字,找到相关逻辑. 然后修改逻辑!! null

反调试技术- IsDebuggerPresent,原理 与 反反调试

IsDebuggerPresent 这个函数可以用在程序中,检测当前程序是否正在被调试,从而执行退出等行为,达到反调试的作用. 1.IsDebuggerPresent 这个函数从汇编的角度看,就是一下三句代码.下面依次来分析这三句代码的原理. 75593789 K> 64:A1 18000000 mov eax, dword ptr fs:[18] 7559378F 8B40 30 mov eax, dword ptr [eax+30] 75593792 0FB640 02 movzx eax,