1. 程式人生 > >C++PE檔案格式解析類(輕鬆製作自己的PE檔案解析器)

C++PE檔案格式解析類(輕鬆製作自己的PE檔案解析器)

PE是Portable Executable File Format(可移植的執行體)簡寫,它是目前Windows平臺上的主流可執行檔案格式。

PE檔案中包含的內容很多,具體我就不在這解釋了,有興趣的可以參看之後列出的參考資料及其他相關內容。

最近我也在學習PE檔案格式,參考了許多資料,用C++封裝了一個高效方便的PE檔案格式解析的類。

該類對想學PE檔案結構的朋友可算一份可貴的資料,程式碼均很易懂,考慮較全面,具有一定的通用性。

同時該類也可以讓想建立自己的PE檔案解析軟體的朋可以輕鬆在此基礎上實現。

最後,錯誤在所難免,如果大家發現有錯誤,歡迎大家指正。

以下是該類中介面函式的定義程式碼(完整程式碼附於之後下載連結中):

<pre name="code" class="cpp">class CPeFile
{
public:
	CPeFile();
	~CPeFile();

	// 以下函式無特殊說明任何時候均可用
public:
	//將PE檔案附加到物件,成功返回IMAGE_DOS_SIGNATURE、IMAGE_OS2_SIGNATURE、IMAGE_OS2_SIGNATURE_LE、IMAGE_NT_SIGNATURE之一;失敗返回0UL(未知型別),1UL(檔案操作失敗),2(其他錯誤),【僅在沒有PE檔案附加到類物件時可用】
	DWORD Attach(LPCTSTR lpszFilePath);
	//若已有PE檔案附加到類物件則釋放關聯
	void Detach();
	//獲取Attach資訊,成功返回IMAGE_DOS_SIGNATURE、IMAGE_OS2_SIGNATURE、IMAGE_OS2_SIGNATURE_LE、IMAGE_NT_SIGNATURE之一;未成功返回0UL
	DWORD GetAttachInfo() const;

	// 以下函式無特殊說明Attach成功後均可用
public:
	//獲取檔案控制代碼(注:不應在外部執行CloseHandle等有副作用的操作)
	HANDLE GetFileHandle() const;
	//獲取記憶體對映檔案頭部地址
	DWORD_PTR GetMappedFileStart() const;
	//獲取記憶體對映檔案頭部指定偏移位置(不應大於檔案大小,否則返回的指標可能引起錯誤)
	DWORD_PTR GetMappedFileOffset(DWORD dwFoa) const;
	//獲取DOS頭
	const IMAGE_DOS_HEADER* GetDosHeader() const;
	//獲取DOS的入口地址
	DWORD GetDosEntryPoint() const;

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE
public:
	//獲取PE檔案頭(如果是64位程式,返回的實際是const IMAGE_NT_HEADER64*)【型別為IMAGE_OS2_SIGNATURE、IMAGE_OS2_SIGNATURE_LE時仍可用,但操作需小心】
	const IMAGE_NT_HEADERS32* GetNtHeader() const;
	//返回檔案是否為64位(PE32+)
	BOOL Is64Bit() const;
	//獲取PE檔案頭中的載入基地址ImageBase(64位程式返回的是ULONGLONG型別,32位程式可將返回值轉換成DWORD型別)
	ULONGLONG GetImageBase() const;
	//獲取PE檔案頭中的DataDirectory
	const IMAGE_DATA_DIRECTORY* GetDataDirectory() const;
	//獲取各DataDirectory入口的RVA
	DWORD GetDataDirectoryEntryRva(DWORD dwIndex) const;
	//獲取節表(節表的數量可由lpSectionNum傳出)
	const IMAGE_SECTION_HEADER* GetSectionHeader(LPWORD lpSectionNum = NULL) const;
	//將RVA轉換為FOA(可由lpFoa傳出FOA、可由lpSection傳出節序號,不在節中節序號為-1)
	BOOL RvaToFoa(DWORD dwRva, LPDWORD lpFoa = NULL, LPWORD lpSection = NULL) const;
	//將FOA轉換為RVA(可由lpRva傳出RVA、可由lpSection傳出節序號,不在節中節序號為-1)
	BOOL FoaToRva(DWORD dwFoa, LPDWORD lpRva = NULL, LPWORD lpSection = NULL) const;
	//將VA轉換為RVA(VA應大於ImageBase,函式中不檢查)
	DWORD VaToRva(DWORD dwVa) const;
	DWORD VaToRva(ULONGLONG ullVa) const;
	//將RVA轉換為VA(64位程式返回的是ULONGLONG型別,32位程式可將返回值轉換成DWORD型別)
	ULONGLONG RvaToVa(DWORD dwRva) const;

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE
public:
	//讀取PE中匯出表
	BOOL ReadExport();
	//讀取PE中匯入表
	BOOL ReadImport();
	//讀取PE中資源表
	BOOL ReadResource();
	//讀取PE中異常表
	BOOL ReadException();
	//讀取PE中屬性證書表
	BOOL ReadSecurity();
	//讀取PE中基址重定位表
	BOOL ReadBaseRelocation();
	//讀取PE中除錯資料
	BOOL ReadDebug();
	//讀取PE中執行緒區域性儲存表
	BOOL ReadTLS();
	//讀取PE中載入配置表
	BOOL ReadLoadConfig();
	//讀取PE中繫結匯入表
	BOOL ReadBoundImport();
	//讀取PE中延遲載入匯入表
	BOOL ReadDelayImport();
	//清理PE中匯出表
	void ClearExport();
	//清理PE中匯入表
	void ClearImport();
	//清理PE中資源表
	void ClearResource();
	//清理PE中異常表
	void ClearException();
	//清理PE中屬性證書表
	void ClearSecurity();
	//清理PE中基址重定位表
	void ClearBaseRelocation();
	//清理PE中除錯資料
	void ClearDebug();
	//清理PE中執行緒區域性儲存表
	void ClearTLS();
	//清理PE中載入配置表
	void ClearLoadConfig();
	//清理PE中繫結匯入表
	void ClearBoundImport();
	//清理PE中延遲載入匯入表
	void ClearDelayImport();
	//清理所有
	void ClearAll();
	//返回是否讀取了PE中匯出表
	BOOL IsReadExport() const;
	//返回是否讀取了PE中匯入表
	BOOL IsReadImport() const;
	//返回是否讀取了PE中資源表
	BOOL IsReadResource() const;
	//返回是否讀取了PE中異常表
	BOOL IsReadException() const;
	//返回是否讀取了PE中屬性證書表
	BOOL IsReadSecurity() const;
	//返回是否讀取了PE中基址重定位表
	BOOL IsReadBaseRelocation() const;
	//返回是否讀取了PE中除錯資料
	BOOL IsReadDebug() const;
	//返回是否讀取了PE中執行緒區域性儲存表
	BOOL IsReadTLS() const;
	//返回是否讀取了PE中載入配置表
	BOOL IsReadLoadConfig() const;
	//返回是否讀取了PE中繫結匯入表
	BOOL IsReadBoundImport() const;
	//返回是否讀取了PE中延遲載入匯入表
	BOOL IsReadDelayImport() const;

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE且ReadExport成功
public:
	//獲取匯出表
	const IMAGE_EXPORT_DIRECTORY* GetExportDirectory() const;
	//獲取匯出表中各匯出函式地址陣列(數量可由lpFuncNum傳出)
	const DWORD* GetExportFunction(LPDWORD lpFuncNum = NULL) const;
	//獲取匯出表中被定義了名稱的各匯出函式名稱地址陣列(數量可由lpNameNum傳出)
	const DWORD* GetExportName(LPDWORD lpNameNum = NULL) const;
	//獲取匯出表中被定義了名稱的各匯出函式的索引(數量可由lpNameNum傳出)
	const WORD* GetExportNameOrdinal(LPDWORD lpNameNum = NULL) const;
	//解析匯出函式地址陣列中dwIndex項,返回值小於NumberOfNames為按名稱匯出(數值為序號),返回值等於NumberOfNames則為按序號匯出
	DWORD ParseExportFunction(DWORD dwIndex) const;

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE且ReadImport成功
public:
	//獲取各匯入表(匯入表數量可由lpImportDescriptorNum傳出)
	const IMAGE_IMPORT_DESCRIPTOR* GetImportDescriptor(LPDWORD lpImportDescriptorNum = NULL) const;
	//獲取第iImpoert個匯入表中的IMAGE_THUNK_DATA32結構(64位程式實際是IMAGE_THUNK_DATA64)(數量可由lpCount傳出)
	const IMAGE_THUNK_DATA32* GetImportThunkData(DWORD iImport, LPDWORD lpCount = NULL) const;
	//解析某個IMAGE_THUNK_DATA32結構(64位程式實際是IMAGE_THUNK_DATA64),返回結果:1表示按序號匯入(lpParam可傳出序號);2表示按名稱匯入(lpParam可傳出對應IMAGE_IMPORT_BY_NAME的FOA);0失敗【只需要IMAGE_NT_SIGNATURE即可用】
	int ParseThunkData(const IMAGE_THUNK_DATA32* lpThunk, LPDWORD lpParam = NULL) const;

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE且ReadResource成功
public:
	//獲取第一層資源的ID,返回1表示第一層是目錄,返回2表示第一層是資料,返回0表示無資源
	int GetFirstResourceId(PIDTYPE lpFirstID) const;
	//獲取下一層資源的ID,返回1表示下一層是目錄,返回2表示下一層是資料,返回0表示無下一層
	int GetNextResourceId(IDTYPE Id, DWORD iRes, PIDTYPE NextID) const;
	//解析Id對應的目錄層,lpEntryNum可傳出陣列數量,lpLevel可傳出第幾級目錄,lpResourceEntry傳出本層對應的IMAGE_RESOURCE_DIRECTORY_ENTRY陣列
	const IMAGE_RESOURCE_DIRECTORY* ParseResourceDirectory(IDTYPE Id, LPDWORD lpEntryNum = NULL, LPDWORD lpLevel = NULL, IMAGE_RESOURCE_DIRECTORY_ENTRY** lpResourceEntry = NULL) const;
	//解析dwId對應的資料層
	const IMAGE_RESOURCE_DATA_ENTRY* ParseResourceData(IDTYPE Id) const;
	//解析某個IMAGE_RESOURCE_DIRECTORY_ENTRY結構中Name成員,返回結果:1(dwParam為ID);2(dwParam為對應IMAGE_RESOURCE_DIR_STRING_U的FOA)【只需要IMAGE_NT_SIGNATURE即可用】
	int ParseResourceDirectoryEntry(const IMAGE_RESOURCE_DIRECTORY_ENTRY* lpEntry, LPDWORD dwParam) const;

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE且ReadException成功
public:
	//獲取異常表(數量可由lpRuntimeFunctionNum傳出)
	const IMAGE_RUNTIME_FUNCTION_ENTRY* GetRuntimeFunction(LPDWORD lpRuntimeFunctionNum = NULL) const;

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE且ReadSecurity成功
public:
	//獲取屬性證書表(數量可由lpCertificateNum傳出)
	const WIN_CERTIFICATE* const* GetCertificate(LPDWORD lpCertificateNum = NULL) const;

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE且ReadBaseRelocation成功
public:
	//獲取各基址重定位表(數量可由lpBaseRelocationNum傳出)
	const IMAGE_BASE_RELOCATION* const* GetBaseRelocation(LPDWORD lpBaseRelocationNum = NULL) const;
	//獲得某個基址重定位表中的重定位塊(數量可由lpCount傳出,包括對齊用的)
	const WORD* GetBaseRelocationBlock(const IMAGE_BASE_RELOCATION* lpBaseRelocation, LPDWORD lpCount = NULL) const;
	//解析某個基址重定位表後的某一項,返回的是高4位的值,低12位的值可由lpParam傳出【任何時候均後可用】
	static WORD ParseBaseRelocationBlock(WORD wBaseRelocationBlock, LPWORD lpParam = NULL);

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE且ReadDebug成功
public:
	//獲取除錯資料(數量可由lpDebugDirectoryNum傳出)
	const IMAGE_DEBUG_DIRECTORY* GetDebugDirectory(LPDWORD lpDebugDirectoryNum = NULL) const;
	//獲取第dwIndex項除錯資訊起始地址,未獲取到返回NULL
	LPCVOID GetDebugInfoStart(DWORD dwIndex);

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE且ReadTLS成功
public:
	//獲取執行緒區域性儲存表(如果是64位程式,返回的實際是const IMAGE_TLS_DIRECTORY64*)
	const IMAGE_TLS_DIRECTORY32* GetTLSDirectory() const;
	//獲取執行緒區域性儲存表回撥函式陣列的指標(如果是64位程式,返回的實際是const ULONGLONG*)(數量可由lpCallbackNum傳出)
	const DWORD* GetTLSCallback(LPDWORD lpCallbackNum = NULL) const;

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE且ReadLoadConfig成功
public:
	//獲取載入配置表(如果是64位程式,返回的實際是const IMAGE_LOAD_CONFIG_DIRECTORY64*)
	const IMAGE_LOAD_CONFIG_DIRECTORY32* GetLoadConfigDirectory() const;

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE且ReadBoundImport成功
public:
	//獲取繫結匯入表(數量可由lpBoundImportNum傳出)
	const IMAGE_BOUND_IMPORT_DESCRIPTOR* const* GetBoundImportDescriptor(LPDWORD lpBoundImportNum = NULL) const;
	//獲取第iBoundImpoert個繫結匯入表(數量可由lpRefNum傳出)
	const IMAGE_BOUND_FORWARDER_REF* GetBoundImportForwarderRef(DWORD iBoundImport, LPDWORD lpRefNum = NULL) const;

	// 以下函式無特殊說明僅用於IMAGE_NT_SIGNATURE且ReadDelayImport成功
public:
	//獲取延遲載入匯入表(數量可由lpDelayImportNum傳出)
	const IMAGE_DELAYLOAD_DESCRIPTOR* GetDelayImportDescriptor(LPDWORD lpDelayImportNum = NULL) const;


	/*--------------------------------------------------------------------------------------------------------------------*/
	//其他私有成員略

};


註釋部分已經詳細說了各功能,主要操作如下:

1、呼叫Attach成員函式使類物件附加到一個PE檔案

2、通過ReadXXX 讀取需要的目錄(包括匯入表、匯出表、資源表、基址重定位....)

3、呼叫相關處理函式獲取相應資訊(根據讀取內容的不同而不同,具體參看註釋)

4、將獲得的資料做你想要的操作(如顯示出來等)

5、ClearXXX釋放資源(可選,物件析構時會自動呼叫)

6、Detach釋放對該檔案的關聯(可選,物件析構時會自動呼叫)

目前基本資料目錄中的大部分都可以獲取,.Net部分(IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)還未實現,之後會繼續完善

之後的連結中附有一個Demo演示如何使用該類,如仍有不清楚的朋友可以回覆尋問我

最後發一個自己寫的獲取PE檔案資訊的軟體,可以Dump出PE檔案的各資訊,該軟體是用該PE解析類實現的,具體程式碼較多,初看並不易懂,所以在這不提供了國。

該軟體我會附載之後的下載連結裡,可以方便獲取PE檔案中各資訊,由於精力有限,寫的是控制檯下的,有興趣的朋友可以自己實現一個GUI版本的~

軟體中,只需在控制檯下輸入PE檔案路徑,或者將檔案拖拽進視窗(插入.lnk快捷方式也行),程式會將需要的資訊輸出來。

以下附上軟體的一部分截圖:






哈,囉嗦了這麼多,接下來就留給各位去體驗吧,其實PE檔案沒想像那麼難~

相關資源下載地址:

(內含PE檔案解析類原始碼檔案、簡單DEMO、以及自已實現的控制檯PE檔案檢視器)

參考書籍:

《Windows PE權威指南》戚利

《加密與解密(第三版)》段鋼

《軟體加密技術內幕》看雪學院

《Windows環境下32位組合語言程式設計(典葳版)》羅雲彬

《加密與解密》吳強

《逆向工程核心原理》李承遠

參考軟體:

Stud_PE

LordPE

StudyPE+

eXeScope-ha

CFF Explorer

Resource Hacker

PEID

PEview

ExeinfoPe

OllyDbg

IDA Pro

參考原始碼:

PEDump

libpe-master

The Portable Executable File Format from Top to Bottom

同時參考了CSDN、看雪學院、吾愛破解上等資料,在此一併感謝!