【PE进阶】手动添加TLS回调函数

前情提要

Demo代码windows

#include <windows.h>
int tls(){
    MessageBox(NULL,"This is TLS CallBack!","TLS Success",MB_OK);
    return 0;
}
int main(){
    MessageBox(NULL,"This is Main!","Main",MB_OK);
    return 0;
}

测试思路
tls()中的弹窗先于main()中的弹窗!关于整个 tls 的原理就不细说了,你须要有些PE格式的基础以及tls的了解,否则看着很头疼。P.S.实际操做比看别人操做要困难不少,光看是不会进步的安全

具体方法
使用微软VisualStudio的话很简单,网上教程不少。先定义一个 TLS 回调函数void NTAPI TLS_CALLBACK,再添加节区#pragma data_seg (".tls")就好了!不过咱们这篇讲得是手动添加.tls节区以及手动定义回调函数。由于 MinGW 和 MSVC 编译器编译的 PE 文件格式有所区别,因此须要分这两种状况函数

编译器对比测试

  • GCC

使用gcc -m32 gcc.c -o gcc编译。因为 MinGW 的特性使得 GCC 编译的程序含有.tls节区以及关于程序运行时的 tls 回调函数spa

clipboard.png

clipboard.png

  • MSVC

使用VC++6.0编译 Demo 代码。MSVC 编译器编译的程序节区就不多了,而且没有.tls节以及 tls 回调函数指针

clipboard.png

GCC

前置知识点
1、数据目录表对应的表项及其宏定义调试

#define IMAGE_DIRECTORY_ENTRY_EXPORT    0.导出表
#define IMAGE_DIRECTORY_ENTRY_IMPORT    1.导入表 
#define IMAGE_DIRECTORY_ENTRY_RESOURCE    2.资源目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION    3.异常目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY    4.安全目录
#define IMAGE_DIRECTORY_ENTRY_BASERELOC    5.重定位基本表
#define IMAGE_DIRECTORY_ENTRY_DEBUG    6.调试目录
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT    7.描术字串
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR    8.机器值
#define IMAGE_DIRECTORY_ENTRY_TLS    9.TLS目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10.载入配值目录
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT    11.绑定输入表
#define IMAGE_DIRECTORY_ENTRY_IAT    12.导入地址表
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT    13.延迟载入描述
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR    14.COM信息

2、节区 Header 的结构体code

typedef struct _IMAGE_SECTION_HEADER {
    BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; /* 节区名 */
    union {
        DWORD PhysicalAddress;
        DWORD VirtualSize; /* 节区在内存中占用的大小与文件中的大小不一样 */
    } Misc;
    DWORD VirtualAddress; /* 节区在内存中的偏移 */
    DWORD SizeOfRawData; /* 节区在文件在占用的大小 */
    DWORD PointerToRawData; /* 节区在文件中的偏移 */
    DWORD PointerToRelocations;
    DWORD PointerToLinenumbers;
    WORD NumberOfRelocations;
    WORD NumberOfLinenumbers;
    DWORD Characteristics; /* 节区的属性 */
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

3、TLS 的结构体教程

typedef struct _IMAGE_TLS_DIRECTORY32 { 
    DWORD StartAddressOfRawData; /* tls节区的起始地址 */
    DWORD EndAddressOfRawData; /* tls节区的最终地址 */
    DWORD AddressOfIndex; /* tls节区的索引 */
    DWORD AddressOfCallBacks; /* 指向回调函数的指针 */
    DWORD SizeOfZeroFill;
    DWORD Characteristics;
} IMAGE_TLS_DIRECTORY32;

4、内存偏移手动转文件偏移
根据节区头部的内存偏移量定位某个 RVA 在哪一个节区中,而后利用该节区的文件偏移加上相对于该节区的内存偏移便可获得任意内存偏移相对应的文件偏移索引

具体操做
首先了解下程序的对齐值,gcc编译的程序内存对齐是0x1000,文件对齐是0x200

clipboard.png

而后找到NT Header->Optional Header->Data[9],由VirtualAddr定位到TLS的整个结构体,要将 RVA 转为文件偏移

clipboard.png

clipboard.png

根据 TLS 的结构体定义能够知道,tls 节区的内存偏移在 0x408000,转化成文件偏移就是 0x2A00,回调函数的指针指向0x407020,转换成文件偏移就是0x2820

clipboard.png

clipboard.png

IDA看到咱们自写的tls()函数的地址是0x401510,咱们将这个地址调到0x407020对应的文件偏移0x2820后面,P.S.前面的那两个回调函数应该是运行时相关的,编译器自定义的 tls 回调与咱们无关

clipboard.png

clipboard.png

保存以后运行,能够看到tls函数会先于main函数运行

clipboard.png

MSVC

前置知识点
见上方

具体操做
因为 MSVC 编译的程序是没有 tls 节区的因此咱们须要添加一个新节区,添加以前先看看文件的对齐值,内存对齐为0x1000,文件对齐为0x1000,但并不必定全部节区的内存偏移等同于文件偏移,由于可能有的节区内存所占大小大于文件所占大小,这就致使该节区以后节区的内存和文件偏移不一致

clipboard.png

先将NT Header->File Header->NumberOfSection的数量加1,而后将NT Header->Optional Header->SizeOfImage的值加0x1000

clipboard.png

clipboard.png

而后在文件最后添加0x1000个00,做为新节区

clipboard.png

再去修改新节区的头部,偏移值要根据前面的节区填写,好比:前一个节区的内存偏移为0x2B000,文件偏移为0x29000,那么新节区的内存偏移为0x2C000,文件偏移为0x2A000,注意还须要可写的这个属性

clipboard.png

再而后就是填写数据目录表,首先我是把TLS的结构体放在.tls节区起始地址的,因此NT Header->Optional Header->Data[9]中的VirtualAddress填写0x2C000即 .tls 节区的内存偏移值,而后Size填写0x18

clipboard.png

最后就是填写 TLS 结构体的内容了,自写的 tls 函数地址在0x401020。TLS 结构体的前三项能够直接填写当前位置的内存偏移,第四项须要注意的是指针类型

clipboard.png

双击运行,tls 函数先于 main 函数运行

clipboard.png

总结

略微总结一下 PE 文件怎么找到的 TLS 回调函数

  • 先是数据目录表找到 TLS 结构体所在的位置
  • TLS 结构体会指示 TLS 的起始地址,这个地址一般是 .tls 所在的地址
  • TLS 结构体中还会有最重要的回调函数指针,根据指针的地址就能定位到回调函数的地址

关于 .tls 节区的说明,其实并不必定须要 .tls 节区,将 TLS 结构体的StartEnd地址填写为其它有效内存区域也是能够的。.tls节区主要是用来存储 tls 回调函数须要用到的一些数据

END

相关文章
相关标签/搜索