看了 《逆向工程核心原理》以后決定自己實(shí)現(xiàn)一個(gè) PE 頭讀取的代碼猖败,將 PE 的 DOS 頭和 NT 頭放到一個(gè)結(jié)構(gòu)體的內(nèi)存布局里。
一、結(jié)構(gòu)體定義
首先定義一個(gè)簡(jiǎn)單的 HEADERS,將內(nèi)存布局做一個(gè)整體上的劃分,主要?jiǎng)澐殖?dos_header 頭遏片、 dos_stub 代碼段和 nt 頭:
typedef struct _PE_HEADERS {
CUSTOM_IMAGE_DOS_HEADER dos_header;
CUSTOM_IMAGE_DOS_STUB dos_stub;
CUSTOM_IMAGE_NT_HEADERS nt_header;
}PE_HEADERS, * PPE_HEADERS;
1、DOS 頭
DOS 頭里面比較關(guān)鍵的兩個(gè)段分別是 e_magic 和 e_lfanew撮竿,這個(gè) e_magic 是 DOS 可執(zhí)行文件的設(shè)計(jì)者的名字縮寫(xiě)吮便,而 e_lfanew 是 NT 頭的偏移,結(jié)構(gòu)體定義如下:
typedef struct _CUSTOM_IMAGE_DOS_HEADER { // DOS 頭幢踏,用于兼容 DOS 文件
WORD e_magic; // 設(shè)計(jì) DOS 可執(zhí)行文件的微軟開(kāi)發(fā)人員的名字縮寫(xiě) "MZ"
WORD e_cblp; //
WORD e_cp; //
WORD e_crlc; //
WORD e_cparhdr; //
WORD e_minalloc; //
WORD e_maxalloc; //
WORD e_ss; //
WORD e_sp; //
WORD e_csum; //
WORD e_ip; //
WORD e_cs; //
WORD e_lfarlc; //
WORD e_ovno; //
WORD e_res[4]; //
WORD e_oemid; //
WORD oeminfo; //
WORD e_res2[10]; //
LONG e_lfanew; // NT 頭的偏移
} CUSTOM_IMAGE_DOS_HEADER;
e_magic 作為一個(gè)魔數(shù)髓需,在實(shí)現(xiàn)讀取的過(guò)程中有一個(gè)理解就是它用來(lái)指明讀入數(shù)據(jù)的時(shí)候是否讀到了正確的偏移,之后的 NT Header 也會(huì)有對(duì)應(yīng)的魔數(shù)房蝉,如果對(duì)應(yīng)結(jié)構(gòu)體的對(duì)應(yīng)魔數(shù)是正確的僚匆,就意味著我們的讀取邏輯讀到的這個(gè)地址偏移是正確的。
舉個(gè)例子搭幻,假如內(nèi)存中有一塊 20Bytes 的數(shù)據(jù)咧擂,我們要把這個(gè)數(shù)據(jù)復(fù)制到自己的結(jié)構(gòu)體中,這塊數(shù)據(jù)的第一個(gè) BYTE 長(zhǎng)度的數(shù)據(jù)值是 0x20檀蹋,如果我們復(fù)制之后結(jié)構(gòu)體的第一個(gè) BYTE 不是 0x20松申,那很大可能我們復(fù)制的地址值之類(lèi)的偏移寫(xiě)錯(cuò)了~
2、DOS Stub
這塊數(shù)據(jù)是用來(lái)兼容 DOS 運(yùn)行的,在 DOS 中運(yùn)行這個(gè)文件將會(huì)跑 DOS Stub 下的代碼贸桶,我定義的結(jié)構(gòu)體如下:
typedef struct _CUSTOM_IMAGE_DOS_STUB { // DOS 存根舅逸,大小不固定,目前看來(lái)是 dos 下執(zhí)行的代碼
LPBYTE e_dos_code; // 或許是 dos 下的執(zhí)行代碼
LONG e_dos_code_size; // *非 PE 內(nèi)數(shù)據(jù)皇筛,用來(lái)記錄 dos 代碼的尺寸
} CUSTOM_IMAGE_DOS_STUB;
其中 e_dos_code 指向?qū)?yīng) dos_code 的首地址堡赔。
3、NT 頭
DOS stub 之后跟著的是 NT 頭设联,NT 頭的數(shù)據(jù)比較多,同樣灼捂,取重要部分加注釋?zhuān)?/p>
typedef struct _CUSTOM_IMAGE_NT_HEADERS { // NT 頭數(shù)據(jù)
DWORD nt_signature; // NT 簽名結(jié)構(gòu)體
CUSTOM_IMAGE_FILE_HEADER nt_file_header; // NT 文件頭
CUSTOM_IMAGE_OPTIONAL_HEADER32 nt_optional_header; // NT 可選頭
} CUSTOM_IMAGE_NT_HEADERS;
1) NT 頭簽名
這個(gè)數(shù)同樣是一個(gè)魔數(shù)离例,值為 10B 或者 20B。
2) NT 文件頭
typedef struct _CUSTOM_IMAGE_FILE_HEADER { // 文件頭數(shù)據(jù)
WORD nt_machine; // cpu 架構(gòu) Machine 碼數(shù)據(jù)悉稠,用以標(biāo)識(shí)比如 INTEL 386 架構(gòu)等等
WORD nt_number_of_sections; // 指出文件中存在的節(jié)區(qū)數(shù)量宫蛆,這里的節(jié)區(qū)指的是代碼、數(shù)據(jù)或者資源等數(shù)據(jù)分區(qū)
DWORD nt_time_date_stamp; // 文件創(chuàng)建時(shí)間的猛,但有些開(kāi)發(fā)工具提供了設(shè)置的手段耀盗,所以不一定準(zhǔn)
DWORD nt_pointer_to_symbol_table; // 符號(hào)表的指針
DWORD nt_number_of_symbols; // 符號(hào)的數(shù)量
WORD nt_size_of_optional_header; // 用來(lái)指出可選頭的長(zhǎng)度
WORD nt_characteristics; // 用于標(biāo)識(shí)文件的屬性,文件是否是可運(yùn)行的形態(tài)卦尊、是否為 DLL 文件等等信息叛拷,bit OR 的形式
} CUSTOM_IMAGE_FILE_HEADER;
3)NT 可選頭
typedef struct _CUSTOM_IMAGE_DATA_DIRECTORY { // 該結(jié)構(gòu)體僅僅用于定義一段數(shù)據(jù)的存儲(chǔ)
DWORD virtual_address; // 數(shù)據(jù)的虛擬地址
DWORD size; // 數(shù)據(jù)的長(zhǎng)度
} CUSTOM_IMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 // 為什么定義在這里,因?yàn)橥獠靠赡芤?
typedef struct _CUSTOM_IMAGE_OPTIONAL_HEADER { // 可選頭內(nèi)容
WORD opt_magic; // 可選頭魔數(shù)岂却,HEADER32 是 10B忿薇,如果是 HEADER64 就是 20B
BYTE opt_major_linker_version; //
BYTE opt_minor_linker_version; //
DWORD opt_size_of_code; //
DWORD opt_size_of_initialized_data; //
DWORD opt_size_of_uninitialized_data; //
DWORD opt_address_of_entry_point; // 程序最先執(zhí)行的代碼起始地址,程序入口點(diǎn) OEP
DWORD opt_base_of_code; //
DWORD opt_base_of_data; //
DWORD opt_image_base; // 程序裝入虛擬內(nèi)存空間時(shí)優(yōu)先選擇的虛擬內(nèi)存地址躏哩,裝載之后優(yōu)先設(shè)置 EIP 為 ImageBase+AddressOfEntryPoint
DWORD opt_section_alignment; // 指定每個(gè)節(jié)區(qū)在內(nèi)存中的最小單位署浩,內(nèi)存中節(jié)區(qū)大小必須是該值的整數(shù)倍
DWORD opt_file_alignment; // 指定每個(gè)節(jié)區(qū)在文件中的最小單位,文件中節(jié)區(qū)大小必須是該值的整數(shù)倍
WORD opt_major_operating_system_version; //
WORD opt_minor_operating_system_version; //
WORD opt_major_image_version; //
WORD opt_minor_image_version; //
WORD opt_major_subsystem_version; //
WORD opt_minor_subsystem_version; //
DWORD opt_win32_version_value; //
DWORD opt_size_of_image; // 指定 PE Image 在虛擬內(nèi)存中所占空間大小扫尺。一般而言筋栋,文件大小與加載到內(nèi)存中的大小是不同的
DWORD opt_size_of_headers; // 指出整個(gè) PE 頭的大小,必須是 FileAlignment 的整數(shù)倍
DWORD opt_check_sum; //
WORD opt_subsystem; // 值為 1 時(shí)代表系統(tǒng)驅(qū)動(dòng)(.sys)正驻,值為 2 時(shí)代表 GUI 文件窗口應(yīng)用程序(.exe)弊攘,值為 3 時(shí)代表 CUI 文件控制臺(tái)應(yīng)用程序(.exe)
WORD opt_dll_characteristics; //
DWORD opt_size_of_stack_reverse; //
DWORD opt_size_of_stack_commit; //
DWORD opt_size_of_heap_reverse; //
DWORD opt_size_of_heap_commit; //
DWORD opt_loader_flags; //
DWORD opt_number_of_rva_and_sizes; // 指定 data_directory 數(shù)組的個(gè)數(shù),雖然指定的常數(shù)是 16,但實(shí)際數(shù)組大小可能不是 16
CUSTOM_IMAGE_DATA_DIRECTORY opt_data_directory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //
} CUSTOM_IMAGE_OPTIONAL_HEADER32;
二拨拓、讀取邏輯
靜態(tài)大小的數(shù)據(jù)我們直接讀取對(duì)應(yīng)內(nèi)存肴颊,動(dòng)態(tài)大小的數(shù)據(jù)則先從數(shù)據(jù)中拿出大小,然后指定大小去讀對(duì)應(yīng)的數(shù)據(jù)渣磷。
為了讓流程清晰婿着,我的實(shí)現(xiàn)還是按照每一個(gè)小塊進(jìn)行復(fù)制,如果是靜態(tài)塊,直接復(fù)制整塊竟宋,不用像我這樣一塊塊復(fù)制提完。
三、全部代碼
void LoadHeader(PPE_HEADERS _peHeaders, HANDLE _peBaseAddress) {
// GET DOS HEADER
LONG peBlockSize = 0x3c;
HANDLE peSrcHeadersAddress = _peBaseAddress;
HANDLE peCurHeadersAddress = _peHeaders;
memcpy(peCurHeadersAddress, peSrcHeadersAddress, peBlockSize);
peCurHeadersAddress = (BYTE*)peCurHeadersAddress + peBlockSize;
peSrcHeadersAddress = (BYTE*)peSrcHeadersAddress + peBlockSize;
// GET NT HEADER
peBlockSize = sizeof(LONG);
LONG* dosStubSizeHandle = (LONG*)peSrcHeadersAddress;
memcpy(peCurHeadersAddress, peSrcHeadersAddress, peBlockSize);
peCurHeadersAddress = (BYTE*)peCurHeadersAddress + peBlockSize;
peSrcHeadersAddress = (BYTE*)peSrcHeadersAddress + peBlockSize;
// GET DOS STUB
if (dosStubSizeHandle != nullptr) {
peBlockSize = *dosStubSizeHandle - 0x3c - sizeof(LONG);
LPBYTE dosCode = (BYTE*)malloc(peBlockSize);
if (dosCode != nullptr) {
memcpy(dosCode, peSrcHeadersAddress, peBlockSize);
memcpy(peCurHeadersAddress, &dosCode, sizeof(LPBYTE));
}
peCurHeadersAddress = (BYTE*)peCurHeadersAddress + sizeof(LPBYTE);
peSrcHeadersAddress = (BYTE*)peSrcHeadersAddress + peBlockSize;
}
// SET DOS CODE SIZE
memcpy(peCurHeadersAddress, &peBlockSize, sizeof(LONG));
peCurHeadersAddress = (BYTE*)peCurHeadersAddress + sizeof(LONG);
// GET NT SIGNATURE
peBlockSize = sizeof(DWORD);
memcpy(peCurHeadersAddress, peSrcHeadersAddress, peBlockSize);
peCurHeadersAddress = (BYTE*)peCurHeadersAddress + peBlockSize;
peSrcHeadersAddress = (BYTE*)peSrcHeadersAddress + peBlockSize;
// GET NT FILE HEADER
peBlockSize = sizeof(WORD) * 10;
LONG nt_optional_header_size = *((WORD*)peSrcHeadersAddress + 8);
memcpy(peCurHeadersAddress, peSrcHeadersAddress, peBlockSize);
peCurHeadersAddress = (BYTE*)peCurHeadersAddress + peBlockSize;
peSrcHeadersAddress = (BYTE*)peSrcHeadersAddress + peBlockSize;
// GET NT OPTIONAL HEADER OTHER DATA
peBlockSize = nt_optional_header_size - IMAGE_NUMBEROF_DIRECTORY_ENTRIES * sizeof(CUSTOM_IMAGE_DATA_DIRECTORY) - sizeof(DWORD);
memcpy(peCurHeadersAddress, peSrcHeadersAddress, peBlockSize);
peCurHeadersAddress = (BYTE*)peCurHeadersAddress + peBlockSize;
peSrcHeadersAddress = (BYTE*)peSrcHeadersAddress + peBlockSize;
// GET NT DATA DIRECTORIES COUNT
peBlockSize = sizeof(DWORD);
LONG directoriesCount = *((PDWORD)peSrcHeadersAddress);
memcpy(peCurHeadersAddress, peSrcHeadersAddress, peBlockSize);
peCurHeadersAddress = (BYTE*)peCurHeadersAddress + peBlockSize;
peSrcHeadersAddress = (BYTE*)peSrcHeadersAddress + peBlockSize;
// GET DATA DIRECTORIES DATAS
peBlockSize = sizeof(IMAGE_DATA_DIRECTORY) * directoriesCount;
memcpy(peCurHeadersAddress, peSrcHeadersAddress, peBlockSize);
}
PPE_HEADERS LoadFilePeHeaders(const wchar_t * _fileName) {
// Open File
HANDLE fHandle = CreateFile(_fileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
if (INVALID_HANDLE_VALUE == fHandle) {
return nullptr;
}
DWORD fSize = GetFileSize(fHandle, 0);
BYTE* fContent = (BYTE*)malloc(fSize * sizeof(BYTE));
DWORD fReadSize;
BOOL succ = ReadFile(fHandle, fContent, fSize, &fReadSize, 0);
if (!succ) {
CloseHandle(fHandle);
return nullptr;
}
// Load Pe Head
PE_HEADERS headers;
LoadHeader((PPE_HEADERS)&headers, fContent);
CloseHandle(fHandle);
return &headers;
}
int main() {
const wchar_t * filePath = TEXT("C:/Windows/system32/notepad.exe");
PPE_HEADERS headers = LoadFilePeHeaders(filePath);
if (headers == nullptr) {
std::cout << "File open failed! Last error: " << GetLastError() << std::endl;
return 0;
}
std::cout << headers->dos_header.e_magic << std::endl;
return 0;
}