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 回調函數須要用到的一些數據