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 -m32 gcc.c -o gcc
编译。因为 MinGW 的特性使得 GCC 编译的程序含有.tls
节区以及关于程序运行时的 tls 回调函数spa
使用VC++6.0
编译 Demo 代码。MSVC 编译器编译的程序节区就不多了,而且没有.tls
节以及 tls 回调函数指针
前置知识点
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
而后找到NT Header->Optional Header->Data[9]
,由VirtualAddr
定位到TLS的整个结构体
,要将 RVA 转为文件偏移
根据 TLS 的结构体定义能够知道,tls 节区的内存偏移在 0x408000,转化成文件偏移就是 0x2A00,回调函数的指针指向0x407020,转换成文件偏移就是0x2820
由IDA
看到咱们自写的tls()
函数的地址是0x401510
,咱们将这个地址调到0x407020对应的文件偏移0x2820后面
,P.S.前面的那两个回调函数应该是运行时相关的,编译器自定义的 tls 回调与咱们无关
保存以后运行,能够看到tls函数会先于main函数运行
前置知识点
见上方
具体操做
因为 MSVC 编译的程序是没有 tls 节区的因此咱们须要添加一个新节区,添加以前先看看文件的对齐值,内存对齐为0x1000,文件对齐为0x1000
,但并不必定全部节区的内存偏移等同于文件偏移,由于可能有的节区内存所占大小大于文件所占大小
,这就致使该节区以后节区的内存和文件偏移不一致
先将NT Header->File Header->NumberOfSection
的数量加1,而后将NT Header->Optional Header->SizeOfImage
的值加0x1000
而后在文件最后添加0x1000个00
,做为新节区
再去修改新节区的头部,偏移值要根据前面的节区填写,好比:前一个节区的内存偏移为0x2B000,文件偏移为0x29000,那么新节区的内存偏移为0x2C000,文件偏移为0x2A000
,注意还须要可写的这个属性
再而后就是填写数据目录表,首先我是把TLS的结构体放在.tls节区起始地址的
,因此NT Header->Optional Header->Data[9]
中的VirtualAddress
填写0x2C000
即 .tls 节区的内存偏移值,而后Size
填写0x18
最后就是填写 TLS 结构体的内容了,自写的 tls 函数地址在0x401020
。TLS 结构体的前三项能够直接填写当前位置的内存偏移,第四项须要注意的是指针类型
双击运行,tls 函数先于 main 函数运行
略微总结一下 PE 文件怎么找到的 TLS 回调函数
关于 .tls 节区的说明,其实并不必定须要 .tls 节区,将 TLS 结构体的Start
和End
地址填写为其它有效内存区域也是能够的。.tls
节区主要是用来存储 tls 回调函数须要用到的一些数据