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、看雪學院、吾愛破解上等資料,在此一併感謝!