textbox

IT博客 联系 聚合 管理
  103 Posts :: 7 Stories :: 22 Comments :: 0 Trackbacks

漫谈TLS_CallBack:原理、编程、手工感染及检测(这篇不错的文章结果在搜索引擎上找不到了,特贴出来共享一下)

原作者: :Hannibal509@gmail.com

一、TLS_CallBack原理
TLS是Thread Local Storage,即线程局部存储的意思。实际上线程局部存储有2种,动态的和静态的。动态线程局部存储使用TlsAlloc, TlsFree, TlsSetValue和TlsGetValue这些API,有很多编程的书都讲过这个问题,我就不多废话了。而静态线程局部存储,则是今天的主角。在 Windows的PE/COFF可执行文件格式中支持静态线程局部存储。TLS回调函数要执行经历下面3个步骤。
1、在链接(link)时,链接器要在PE文件中创建TLS目录(详见PE格式)。
2、 在创建线程时,加载器(loader)会从TEB(thread environment block,线程环境块,通过FS寄存器可以获取TEB的位置)中获取一个指向TLS回调函数数组的指针。
3、如果在TLS回调函数数组不是一个空的数组,加载器就会顺序执行这个数组中的各个回调函数。
我们设一个单线程的进程,TLS回调函数是在创建主线程时执行的(设是使用DLL_PROCESS_ATTACH,详见代码中的注释),而程序的 start函数(Entry Point)不过是在主线程起始地址,它只是相当于我们创建其他进程时传给CreateThead()的第三个参数。所以只有当主线程创建之后才能轮到它 执行的,而所以它当然就在start函数执行之前就已经被执行过了。调试器一般把默认断点设置在start函数上,结果当然就是断不到TLS回调函数了。
那么是不是就真的断不到TLS回调函数了呢?也不是。根据调试器的原理,调试器是根据操作系统抛出的各种调试事件对被调试的进程进行操作的。只有遇有调试 事件或者异常时,操作系统才会挂起被调试进程中的各个线程,并唤醒调试器进行操作。(详见《Advanced Windows Debugging》第三章)能不能断到TLS回调函数,实际上就是问这个函数的调用时机是不是在系统抛出所有的调试事件之前。
我借用《Windows Internals》第四版中的一张图来说明问题。
这是创建一个进程的过程。我们看到显然操作系统抛出CreateProcessEvent事件早于TLS_CallBack函数的执行。但是我们注意到还 有一个ExceptionEvent事件也早于TLS_CallBack函数的执行。这个ExceptionEvent事件不是一般调试器的默认断点,但 是通过设置(比如看雪的OllyICE中,选择“选项”->“调试选项”->“事件”在“设置第一次暂停于”的单选项中选择“系统断点”), 我们就可以在这里把程序断下来,这样就能在TLS_CallBack函数执行之前断下程序。

二、TLS_callback的编程实现
有网友把《TLS callbacks》中给出的那个tls.cpp贴在自己的博客里备忘,不过我冒昧的说一句,这段代码的精华在于那个ulnfeat.h,tls.cpp 中的东西反而并不太重要。同时这个方法太过依赖于链接器。我个人认为效果不是很好,所以在这里给出一个比较带普遍性的代码。
由于没有什么API能用来直接在PE文件中添加TLS_CallBack函数,所以我们添加TLS_CallBack函数多少有些暴力。不过由于汇编代码 网上已经到处都是了,所以我给出一个C++的代码。归根到底,不论是这个C++的代码,还是汇编代码,原理和要做的事都是一样的

#include  " stdafx.h "
#include 
< windows.h >
#include 
< winnt.h >
// 下面这行告诉链接器在PE文件中要创建TLS目录
#pragma comment(linker,  " /INCLUDE:__tls_used " )
/* 这是PIMAGE_TLS_CALLBACK()函数的原型,其中第一个和第三个参数保留,第二个参数决定函数在那种情况下 */
void  NTAPI my_tls_callback(PVOID h, DWORD reason, PVOID pv)
{
    
/* 一共有四个选项DLL_PROCESS_ATTACH、DLL_THREAD_ATTACH、DLL_THREAD_DETACH和DLL_PROCESS_DETACH。
    DLL_PROCESS_ATTACH,是指新进程创建时,在初始化主线程时执行
    DLL_THREAD_ATTACH,是指在新进程初始化时执行,但是不包括主线程
    DLL_THREAD_DETACH,是指在所有的线程终止时执行,但是同样不包括主线程
    DLL_PROCESS_DETACH,是指在进程终止时执行。
    详见微软发布的《Microsoft Portable Executable and Common Object File Format Specification v8》
*/
    
// 仅在进程初始化创建主线程时执行的代码
     if ( reason  ==  DLL_PROCESS_ATTACH ){
    MessageBox(NULL,L
" hi,this is tls callback " ,L " title " ,MB_OK);
    }
    
return ;
}
/* 下面这段是创建一个tls段
".CRT$XLB"的含义是:
.CRT表明是使用C RunTime机制
$后面的XLB中
X表示随机的标识
L表示是TLS callback section
B可以被换成B到Y的任意一个字母,但是不能使用".CRT$XLA"和".CRT$XLZ"
因为".CRT$XLA"和".CRT$XLZ"是用于tlssup.obj的
$是给链接器的。LINK 做的一个重要的任务,就是把所有具有相同名字的段合并成为一个单独的段(这也就是连接程序名字的由来之一),合并的做法就是简单地把每个段中的数据按顺序前后放到一个连续的空间就可以了。这样在最终运行的时候,程序看到的CRT$XL?段也就是一个连续的数组,而不是多个数组。
*/
#pragma data_seg(
" .CRT$XLB " )
/* 如果要定义多个TLS_CallBack函数,可以把下面这句写成:
PIMAGE_TLS_CALLBACK p_thread_callback [] = {tls_callback_A, tls_callback_B, tls_callback_C,0};
其中tls_callback_B和tls_callback_C应该是你定义好的其他
TLS_callBack函数,这些函数会被依次执行
*/
PIMAGE_TLS_CALLBACK p_thread_callback 
=  my_tls_callback;
#pragma data_seg()
int  main( void )
{
    MessageBox(NULL,L
" hi,this is main() " ,L " title " ,MB_OK);
    
return   0 ;
}




要是你使用的是VS2003,你直接编译后运行就可以了,不过要是你使用的是VS2005,你还要打开项目属性窗口,把“Whole Programe Optimization”一栏改成“No Whole Programe Optimization”才行。否则,编译器会认为你定义的my_tls_callback函数从来没有被使用过,在编译时就根本不编译这个函数。不 信,你可以看看VS2005的汇编输出。

三、TLS_CallBack的手工添加
既然我们现在已经知道了,在程序中要加入一个TLS CallBack都要做一些什么,我们就可以手工往一个已有的PE文件中添加一个TLS CallBack。我们先来理一下思路:
首先要有一个函数充当TLS CallBack(废话),然后要有一张TLS CallBack的表,把充当TLS CallBack的这个函数的起始地址写到这张表的第一项中去(当然也可以添加多个TLS CallBack函数),最后在PE头部注册一个TLS目录就齐活了。
说干就干。我们感染Windows的纸牌游戏(这个程序位于%SystemRoot%\system32\sol.exe):
用OllyDbg打开sol.exe,我们发现01006d2e这个位置以下是空的,如下图,所以我们选择这个地方写我们的TLS CallBack,写法和写shellcode差不多,我就不多废话了。我的目的是要让纸牌程序运行之前弹出一个对话框。

写完了shellcode,01006D56这个位置以下为空,所以在这里写上TLS_CallBack函数表。

同时跳过8个字节(函数指针4个字节,NULL终止符一个DWORD,4个字节),写TLS目录。TLS的目录格式如下:

我们只关心其中2项:“Address of Index”和“Address of Callbacks”。“Address of Callbacks”不用说,就填01006D56。但是“Address of Index”要注意了,要找一个可写的段的空白处填上。因为loader在初始化时会往那个地址上写东西,要是在一个不可写段(比如.text)上的话, 加载PE文件时就会崩溃,这点要注意。我看数据段的01007900处空着,就往那里写好了。

存盘,右键->复制到可执行文件->所有修改。
最后要注册TLS目录,TLS目录现在是01006D5E,减去镜像基址01000000,得6D5E。,所以在1A8这个位置(不明白的请详细看PE格式)上写上5E 6D。

存盘后,运行一下。呵呵TLS_CallBack已经运行了。
四、TLS_CallBack的检测
把刚才修改的那个纸牌文件扔到IDA里去,按照IDA作者讲的那个办法按“Ctrl+E”试试。嗯,对。IDA确实没有认出这个TLS_callback 来。用VS2005的Dumpbin加“/tls”参数也没能认出我们添加的TLS_callback函数来。这样就比较讨厌了。恶意软件的作者可以手工 /或者编程往恶意软件中添加TLS_callback,甚至可以使用恶意软件感染正常的PE文件。而我们却检测不出!
不过别紧张,我们回顾一下,要调用TLS_callback函数,在PE头部中必须要有TLS目录,目录中的“Address of Callbacks”字段必须要指向各个TLS_callback函数的起始地址,这样问题就好办了。我们可以用这个原理写一个IDA或者调试器的插件, 在加载PE文件时自动进行识别,如果存在TLS_callback,则通知用户。这样就能比较好的防止我们在分析恶意软件时因为TLS_callback 中招。等下次有空了,写一个放上来。
坑挖好了,喝口水先。
土鳖抗铁牛☺

posted on 2010-09-27 12:48 零度 阅读(1996) 评论(1)  编辑 收藏 引用

Feedback

# re: TLS_CallBack 2012-10-22 13:30 jjyy
介绍得不错  回复  更多评论
  

只有注册用户登录后才能发表评论。