C/C++ 手工實現IAT導入表注入劫持


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 ? 這是因為需要湊夠整數,字節對齊,不然也是不知別的,例如。

  1. 0x00011000 + 0x00004337 = 15337 直接取整 16000
  2. 0x0001A000 + 0x00000AB9 = 1AAB9 直接取 1B000
  3. 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解析后,可對比,計算結果無誤。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM