DLL注入有多種方式,今天介紹的這一種注入方式是通過修改導入表,增加一項導入DLL以及導入函數,我們知道當程序在被運行起來之前,其導入表中的導入DLL與導入函數會被遞歸讀取加載到目標空間中,我們向導入表增加導入函數同樣可以實現動態加載,本次實驗用到的工具依然是上次編寫的PE結構解析器。
解析器下載與使用:https://www.cnblogs.com/LyShark/p/12960816.html
增加空間插入DLL
1.首先我們先來編寫一個簡易的DLL文件,這里可以使用C/C++或其他任何一種語言。
2.其次由於要操作導入表,我們需要再次復習一下導入結構的定義 _IMAGE_IMPORT_DESCRIPTOR
該IID結構,是一個數組,默認最后一項以全部為0作為結束符。
要添加IID數組的話,需要修改此處IID數組,增加一塊區域,但這塊內存一般與IID中的OriginalFirstThunk和FirstThun都有關聯,這就導致我們必須整體將其移動到一個新的位置才可以,不能被覆蓋。
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
IMAGE_THUNK_DATA32
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
默認情況下一個_IMAGE_IMPORT_DESCRIPTOR
結構的大小為20字節也就是0x14
我們再來看一下數據目錄表中導入表的位置與大小。
Import RVA = 0x7604
Size = 0xc8
也就是說我們需要找一塊或者是自己增加一塊空間,該空間的大小應該在 0xc8 + 0x14(一個結構大小) = 0xDC
的位置。
再來看看文件與節表的對其參數,其中內存對齊 0x00001000
而文件對其值為 0x00000200
這里我們就直接擴展最后一個節,並按照文件對齊值為它增加0x200字節的空白區域,使用WinHEX操作,找到文件末尾,右鍵選擇編輯粘貼0字節,然后輸入512也就是十六進制的200.
由於是直接擴展的最后一個rsrc區段,所以定位到這塊空白區域只需要:
文件偏移 0x8400 + 0x8000 = 0x10400
內存偏移 0xB000 + 0x8000 = 0x13000
3.接着找到原IID結構的位置,如上我們知道原始RVA為0x7604,計算 0x7604 - 0x1000 + 0x400 = 0x6A04
大小為0xC8,也就是從0x6A04 - 0x6AB4
拷貝下來,右鍵選擇復制十六進制數值。
然后將其粘貼到偏移為0x1040也就是剛才開辟的空間上面,這里需要注意了:由於我們需要增加一個IID結構,所以先要空出黃色部分的空間,其次最后的紅色部分全部填充0標志結束符。
在原來的0x6a04出構建新的IID結構,
dllname 文件偏移 = 0x6a14 RVA = 0x6a14 - 0x400 + 0x1000 = 0x7614
import_by_name = 文件偏移 = 0x6a20 RVA = 0x7620
PE文件加載前,OriginalFirstThunk和FirstThunk都指向Import_by_name結構數組,現在可以填充他們了。
OriginalFirstThunk = 文件偏移:0x6a04 相對RVA:0x7604
first Thunk 文件偏移:0x6a0c 相對RVA: 0x760c
開始填充結構數據,如下所示。
黃色代表:OriginalFirstThunk
淡紅色代表:TimeDateStamp
綠色代表:FirstThunk
黃色代表:ForwarderChain
紫色代表:dllname
大紅代表:導出msg函數名稱
最后轉到我們后面導入表的位置,在后面填充這些位置。
7604 = OriginalFirstThunk
7614 = dllname
760c = FirstThunk RVA
接着修正PE文件頭,由於改變了PE頭中輸入表的位置,這里輸入表也需要修正,改為13000大小則是dc
接着修正text節,加上0x8000000寫屬性,將其屬性改為 20000E0 即可。
最后修正輸入表的位置,即可實現DLL插入。
上面這種方式有時可能會出錯,例如改完后出現,原因是讀取不到節區,沒有注冊,有時可以有時就會報錯,不同系統之間隨機出現。
添加新節並插入DLL
解決的辦法是自己新建一個節,並設置好屬性,先來仿寫一個.hack節,我們在 .rsrc 后面直接添加一個新的。
.hack 虛擬偏移:虛擬偏移(.rsrc) + 虛擬大小(.hack) => 0x0001B000 + 0x00001000 = 0001C000 + 2000 = 1e000
上面公式中為什么需要加上2000 ? 這是因為需要湊夠整數,字節對齊,不然也是不知別的,例如。
- 0x00011000 + 0x00004337 = 15337 直接取整 16000
- 0x0001A000 + 0x00000AB9 = 1AAB9 直接取 1B000
- 0x0001B000 + 0x000025A0 = 1D5A0 取整 1E000
.hack 實際偏移:實際偏移(.rsrc) + 實際大小(.rsrc) => 0x00007800 + 0x00002600 = 00009E00
計算得到.hack虛擬偏移:0x0001C000 實際偏移:0x00009E00 找到節表位置,開始仿寫。
在文件末尾,插入1000個0字節填充
最后修正鏡像大小,綠色節區數目加1 6改7,藍色鏡像大小加1000,藍色改為 0001F000,改完保存。
改完后,運行看看,沒問題了,改的很成功,已經識別了。
接着找到導入表,所在位置 0x00006DE0 長度 0x50,winhex跟過去。
選中,拷貝下來。
放到開辟的空間中,文件偏移 9e00 處。
修正當前輸入表的位置測試是否可以正確識別,新輸入表的FOA是 9e00 將其轉為RVA = 0x0001E000 大小先保持不變。
改完后,使用工具驗證一下,嗯,確實改掉了,程序依舊能打開,這次搬家很成功。
接着在9e90處構建新的IID結構數組,計算得出。
OriginalFirstThunk 文件偏移 = 9e90 相對RVA: 0x0001E090
TimeDateStamp 單獨占用一個存儲空間,默認填充為0
TimeDateStamp 文件偏移:9E94 相對RVA: 0x0001E094
由於默認情況下OriginalFirstThunk和FirstThunk都指向Import_BY_name結構數組,所以兩者是一致的。
FirstThunk 文件偏移:9E98 相對RVA: 0x0001E098
ForWarderChain 文件偏移:9E9C 相對RVA: 0x0001E09C
DllName 文件偏移:9EA0 相對RVA: 0x0001E0A0
導出函數名稱:文件偏移:9EB4 相對RVA: 0x0001E0B4
接着將這些數據填充到指定的位置。我們就在輸入表的下方構建這個結構吧。
最后我們轉到導入表上,將這個結構構造到導入表的最后。
originalFirstThunk = 1e090
dllname = 1e0a0
FirstThunk = 1e098
由於增加了一個結構,所以需要修正導入表的大小,跳轉到160處,修正 50 + 20 = 70
確保最后一個節可讀可寫可執行。
確保綁定輸入解除,也就是將1B0 - 1B8位置的內容全部清除掉。
打開,成功的執行了加載MsgDll.dll 文件,因為根目錄沒有這個文件所以報錯,但已經可以加載成功了
使用工具來驗證一下,導入表結構已經可以完美的讀取到了,說明我們的計算沒問題。
再次分析導入表
我知道你很模糊,這里有必要再深入分析一下,這次我把他們分開來寫,這樣看起來會更清晰。
首先老樣子,定位到節表位置,執行命令 PETools c://win32.exe --ShowDataDirectory
winhex跳轉到 0x00006DE0 這個位置上,總長度是 0x00000050 我們來分析一下第一個導入表結構,從而讓你對齊更加清晰。
兩個黃色部分,分別是 OriginalFirstThunk 和 FirstThunk 在加載到內存之前其是相同的。
User32.dll存放位置
RVA = 01a54a 轉成 --> FOA = 0x0000714A
714a 所對應的是 user32.dll 導入模塊。
IMAGE_IMPORT_DESCRIPTOR 中的 Name 指向了 0x0000714A 里面就是存放着 user32.dll字符串
指向函數LoadIconA
RVA = 1a15c 轉成 --> FOA = 0x00006D5C
6d5c里面存放着 1a53e
將其轉化為FOA = 713e
她所對應的是 LoadIconA
我們來構建這樣一個結構單元,如下圖。
將單元填充好
表關系如下,與微軟結構定義相同。
關系如圖。
添加成功了。
反寫也可識別,OriginalFirstThunk 和 FirstThunk 一致。
x64dbg 也可以動態加載進來
調用也沒問題。
注意節區屬性必須可讀可寫,改為E0000E0。
導出表解析
導出表默認在DLL中出現較多,EXE文件中出現導出表雖然可以但很奇怪,導出表在數據目錄表第一個成員中,IMAGE_EXPORT_DIRECTORY IED結構,使用PETools工具查看如下。
導出表結構定義如下
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp; // 文件生成時間
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; // 模塊真實名稱
DWORD Base;
DWORD NumberOfFunctions; // 導出元素個數
DWORD NumberOfNames; // 導出元素個數
DWORD AddressOfFunctions; // 指向函數地址的數組
DWORD AddressOfNames; // 函數名字的指針地址
DWORD AddressOfNameOrdinals; // 指向輸出序列號地址的數組
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
winhex 標號
第二項 5ba6031a 指向 TimeDateStamp
計算后得到一個時間戳,將其轉為十進制 1537606426 轉為時間日期則,沒啥可說的。
第五項是Name字段:2280 是RVA 將其轉為FOA
跳轉到1080處發現時該DLL的原始名稱,這個名稱就算把名字改掉,名稱不會變。
第七八項,代表的時導出函數的個數。
第九項,2258 將其轉為FOA跳轉過去看看。0x00001058 此處指向228C
將228C轉為FOA 0x0000108C 跳過去看看,第一個導出函數名字。
再將2268轉為FOA看看 0x00001068 對應2288
再次將2288轉為FOA 0x00001088 跳過去看看,同樣是第一個函數的字符串。
分析
首先RVA=2258 計算FOA = 0x00001058 其指向函數的RVA地址,程序裝入內存后會被展開。
展開后格式如下,紅色指向黑色位置,我們有四個函數所以是四個函數的地址。
RVA = 2268 其FOA = 0x00001068 它指向函數名字的指針地址。
2268 指向了 1068 而1068中存放了四個指針,分別指向四個導出函數的名稱。
拿2288為例子,轉換后變成FOA = 1088 以此類推。
重定位表的解析(拓展)
重定位表在PE中是.reloc,通常情況下EXE可執行文件在映射內存時會獨占虛擬空間,所以EXE時不需要重定位信息的,只有DLL才會需要,因為DLL內存不固定,很可能一個DLL被注入到多個進程中,此時就需要對DLL中的數據進行重定位,以確保特定函數還能夠被調用到。
重定位表的查找是通過數據目錄表的 IMAGE_DIRECTORY_ENTRY_BASERELOC
的條目來查找的,重定位數據采用按頁分割的,每個塊存放4KB(1000h)的重定位數據,每個重定位塊大小為4字節對齊,他們以一個 IMAGE_BASE_RELOCATION
結構。
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
首先使用PETools解析,來看一下這個結構。
找到位置 FOA = 0x00001800 大小是 0x00000128 也就是重定位表的位置。
如果想要通過手工找到該位置,其位於PE偏移 1a0
位置,此處的第一個DWORD是重定位的RVA,第二個位置則是重定位大小。
使用PETools計算的出其FOA = 0x00001800 同樣可以定位到重定位表的位置上。
重定位表的分析,第一個黃色代表重定位數據開始的RVA地址,第二個代表重定位塊長度,最后一個
TYpeOffset 是一個數組,數組中每項大小為2字節,高四位代表重定位類型,低12位代表重定位地址,他與VirtualAddress相加得到的是需要修正位置的數據指針。
如下,其中后面的WORD類型,每一個都是一個重定位數據。
以第一個300e為例,將其轉為FOA = 0x0000140E,定位過去,程序沒被裝載,這里為空,裝在后可能會重定位修復。
PETools解析后,可對比,計算結果無誤。