Relocation(重定位)是一種將程式中的一些地址修正為執行時可用的實際地址的機制。在程式編譯過程中,由於程式中使用了各種全域性變數和函數,這些變數和函數的地址還沒有確定,因此它們的地址只能暫時使用一個相對地址。當程式被載入到記憶體中執行時,這些相對地址需要被修正為實際的絕對地址,這個過程就是重定位。
在Windows作業系統中,程式被載入到記憶體中執行時,需要將程式中的各種記憶體地址進行重定位,以使程式能夠正確地執行。Windows系統使用PE(Portable Executable)檔案格式來儲存可執行程式,其中包括重定位資訊。當程式被載入到記憶體中時,系統會解析這些重定位資訊,並將程式中的各種記憶體地址進行重定位。
重定位表一般出現在DLL
中,因為DLL
都是動態載入,所以地址不固定,DLL的入口點在整個執行過程中至少要執行2次,一次是在開始時執行初始化工作,一次則是在結束時做最後的收尾工作,重定位表則是解決DLL的地址問題,為了能找到重定位表首先我們需要使用PeView
工具查詢DataDirectory
資料目錄表,在其中找到Base relocation
欄位,裡面的0x00001800
則是重定位表基地址;
我們通過使用WinHex
工具定位到0x00001800
即可看到重定位表資訊,如下圖中的1000
代表的是重定位RVA
地址,綠色的0104
代表的則是重定位塊的長度,後面則是每兩個位元組代表一個重定位塊,0A是重定位地址,30則是重定位的型別,以此順序向下排列。
重定位表也是分頁排列的,每一頁大小都是1000
位元組,通過使用FixRelocPage
命令即可查詢到當前程式中的重定位塊資訊,並以第一個為例,查詢一下起始地址RVA為1000
的頁上,有哪些重定位結構,如下圖所示;
其中的重定位RVA
地址0000100A
是用標黃色的1000
加上標藍色的0xA
得到的。而修正RVA地址00003000
加上模組基地址63FF0000+3000
得到的則是第一個被修正的記憶體地址,讀者可使用x64dbg
跳轉到該程式內自行確認。
重定位表的修復原理與IAT修復完全一致,我們需要分別讀入脫殼前與脫殼後的兩個程式,接著通過迴圈正確的重定位表資訊,並依次覆蓋到脫殼後的程式內,以此實現對重定位表的修復功能,實現程式碼如下所示;
#include <windows.h>
#include <stdio.h>
struct TypeOffset
{
WORD Offset : 12; // 低12位元代表重定位地址
WORD Type : 4; // 高4位元代表重定位型別
};
DWORD FileSize = 0; // 定義檔案大小
DWORD FileBase = 0; // 儲存檔案的基地址
// 定義全域性變數,來儲存DOS,NT,Section頭
PIMAGE_DOS_HEADER DosHeader = nullptr;
PIMAGE_NT_HEADERS NtHeader = nullptr;
PIMAGE_FILE_HEADER FileHead = nullptr;
// 將RVA轉換為FOA的函數
DWORD RVAtoFOA(DWORD rva)
{
auto SectionTables = IMAGE_FIRST_SECTION(NtHeader); // 獲取區段表
WORD Count = NtHeader->FileHeader.NumberOfSections; // 獲取區段數量
for (int i = 0; i < Count; ++i)
{
// 判斷是否存在於區段中
DWORD Section_Start = SectionTables[i].VirtualAddress;
DWORD Section_Ends = SectionTables[i].VirtualAddress + SectionTables[i].SizeOfRawData;
if (rva >= Section_Start && rva < Section_Ends)
{
// 找到之後計算位置並返回值
return rva - SectionTables[i].VirtualAddress + SectionTables[i].PointerToRawData;
}
}
return -1;
}
// 開啟PE檔案
bool OpenPeFile(LPCSTR FileName)
{
// 開啟檔案
HANDLE Handle = CreateFileA(FileName, GENERIC_READ, NULL,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (Handle == INVALID_HANDLE_VALUE)
return false;
// 獲取檔案大小
FileSize = GetFileSize(Handle, NULL);
// 讀取檔案資料
DWORD OperSize = 0;
FileBase = (DWORD)new BYTE[FileSize];
ReadFile(Handle, (LPVOID)FileBase, FileSize, &OperSize, NULL);
// 獲取DOS頭並判斷是不是一個有效的DOS檔案
DosHeader = (PIMAGE_DOS_HEADER)FileBase;
if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE)
return false;
// 獲取 NT 頭並判斷是不是一個有效的PE檔案
NtHeader = (PIMAGE_NT_HEADERS)(FileBase + DosHeader->e_lfanew);
if (NtHeader->Signature != IMAGE_NT_SIGNATURE)
return false;
// 判斷是不是一個32位元檔案
if (NtHeader->OptionalHeader.Magic != 0x010B)
return false;
CloseHandle(Handle);
return true;
}
// 修復重定位表
void RepairFixReloc(char new_file[])
{
DWORD base = NtHeader->OptionalHeader.ImageBase;
// 1. 獲取重定位表的 rva
DWORD RelocRVA = NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress;
// 2. 獲取重定位表
auto Reloc = (PIMAGE_BASE_RELOCATION)(FileBase + RVAtoFOA(RelocRVA));
// 3. 遍歷重定位表中的重定位塊,以0結尾
while (Reloc->SizeOfBlock != 0)
{
// 3.1 輸出分頁基址
printf("[↓] 分頁基址: 0x%08X \n\n", Reloc->VirtualAddress);
// 3.2 找到重定位項
auto Offset = (TypeOffset*)(Reloc + 1);
// 3.3 計算重定位項的個數
// Reloc->SizeOfBlock 儲存的是整個重定位塊的大小 結構體 + 重定位項陣列
// Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) 得到單個陣列大小
// 上面的結果 \ 2 = 重定位項的個數,原因是重定位項的大小為兩個位元組
DWORD Size = (Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
// 3.4 遍歷所有的重定位項
for (DWORD i = 0; i < Size; ++i)
{
DWORD Type = Offset[i].Type; // 獲取重定位型別,只關心為3的型別
DWORD pianyi = Offset[i].Offset; // 獲取重定位的偏移值
DWORD rva = pianyi + Reloc->VirtualAddress; // 獲取要重定位的地址所在的RVA
DWORD foa = RVAtoFOA(rva); // 獲取要重定位的地址所在的FOA
DWORD fa = foa + FileBase; // 獲取要重定位的地址所在的fa
DWORD addr = *(DWORD*)fa; // 獲取要重定位的地址
DWORD new_addr = addr - base + 0x1500000; // 計算重定位後的資料: addr - oldbase + newbase
// 將重定位後的資料寫回緩衝區(檔案)
if (Offset[i].Type == 3)
*(DWORD*)fa = new_addr;
printf("\t [->] 重定位RVA: 0x%08X | 重定位FOA: 0x%08X | 重定位地址: 0x%08X | 修正地址: 0x%08X \n", rva, foa, addr, new_addr);
}
// 找到下一個重定位塊
Reloc = (PIMAGE_BASE_RELOCATION)((DWORD)Reloc + Reloc->SizeOfBlock);
}
// 儲存修正後的檔案
NtHeader->OptionalHeader.ImageBase = 0x1500000;
// 開啟一個新檔案
HANDLE new_handle = CreateFileA(new_file, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (new_handle == INVALID_HANDLE_VALUE)
return;
DWORD OperSize = 0;
// 儲存修正好的程式
BOOL ret = WriteFile(new_handle, (LPVOID)FileBase, FileSize, &OperSize, NULL);
if (ret == TRUE)
{
printf("\n\n");
CloseHandle(new_handle);
printf("[*] 修復 %s 檔案 \t 寫入基址: %08X \t 總長度: %d \t 寫入長度: %d \n", new_file, FileBase, FileSize, OperSize);
}
}
void Banner()
{
printf(" ____ _ _ _ ____ _ \n");
printf("| __ ) _ _(_) | __| | | _ \\ ___| | ___ ___ \n");
printf("| _ \\| | | | | |/ _` | | |_) / _ \\ |/ _ \\ / __|\n");
printf("| |_) | |_| | | | (_| | | _ < __/ | (_) | (__ \n");
printf("|____/ \\__,_|_|_|\\__,_| |_| \\_\\___|_|\\___/ \\___|\n");
printf(" \n");
printf("Reloc 重定位錶快速修復工具 \t By: LyShark \n");
printf("Usage: BuildFix [原檔案位置] [修復後檔案位置] \n\n\n");
}
int main(int argc, char* argv[])
{
Banner();
if (argc == 3)
{
bool flag = OpenPeFile(argv[1]);
if (true == flag)
{
RepairFixReloc(argv[2]);
}
}
return 0;
}
執行上述程式,讀者可自行傳入脫殼前的程式與脫殼後的程式,此時則會實現自動替換,如下圖所示;
本文作者: 王瑞
本文連結: https://www.lyshark.com/post/3c1b31b5.html
版權宣告: 本部落格所有文章除特別宣告外,均採用 BY-NC-SA 許可協定。轉載請註明出處!