标签: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的原因。
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段有毛用啊。
哎,说多了都是泪,对线程还是一窍不通啊。
TLS (Thread Local Storage)反调试原理
标签:tls
原文地址:http://blog.csdn.net/bugmeout/article/details/45605497