1. 程式人生 > 其它 >PE檔案結構分析程式(C++)

PE檔案結構分析程式(C++)

技術標籤:PEc++

該程式用與對PE檔案的標頭檔案以及資料目錄表進行分析,並輸出各個資料目錄表以及標頭檔案的關鍵欄位

執行環境

Microsoft Visual Studio Professional 2017 Windows10

專案格式

在這裡插入圖片描述

標頭檔案 Entry.h

#pragma once
#include <stdio.h>
#include <Windows.h>

//計算資料目錄表起始位置到檔案頭的偏移
DWORD RvaToOffset(DWORD dwRva, char *buffer);
//解析匯入表的函式
void ImportTable(char *
buffer); //解析匯出表的函式 void ExportTable(char * buffer); //解析TLS表的函式 void TLSTable(char* buffer); //解析延遲匯入表的函式 void DelayImportTable(char *buffer); //解析重定位表的函式 void RelocTable(char *buffer); //解析資源表的函式 void ResourceTable(char *buffer); //解析其他表(以Debug表為例) void DataTable(char *buffer);

原始檔 Entry.cpp

在執行前需要修改分析的PE檔案的路徑,執行時可以選擇除錯以及一個一個的功能除錯,便於分析.

#include "Entry.h"

int main()
{
	//檔案讀取
	FILE * pFile = NULL;
	char * buffer;
	int nFileLength = 0;
	pFile = fopen("D:\\Program Files\\Edrawsoft\\Edraw MindMaster(簡體中文)\\MindMaster.exe", "rb");
	//pFile = fopen("D:\\Program Files\\Edrawsoft\\Edraw MindMaster(簡體中文)\\MindMaster.exe", "rb");
//D:\\Program Files\\Edrawsoft\\Edraw MindMaster(簡體中文)\\MindMaster.exe fseek(pFile, 0, SEEK_END); nFileLength = ftell(pFile); rewind(pFile); int imageLength = nFileLength * sizeof(char) + 1; buffer = (char *)malloc(imageLength); memset(buffer, 0, nFileLength * sizeof(char) + 1); fread(buffer, 1, imageLength, pFile); //MS-DOS頭解析 PIMAGE_DOS_HEADER ReadDosHeader; ReadDosHeader = (PIMAGE_DOS_HEADER)buffer; printf("MS-DOS Info:\n"); printf("MZ標誌位:%x\n", ReadDosHeader->e_magic); printf("PE頭偏移:%x\n", ReadDosHeader->e_lfanew); printf("==================================================================\n"); printf("PE Header Info:\n"); PIMAGE_NT_HEADERS32 ReadNTHeaders; //PE頭解析 ReadNTHeaders = (PIMAGE_NT_HEADERS32)(buffer + ReadDosHeader->e_lfanew); //PE頭標誌 printf("PE標誌位:%x\n", ReadNTHeaders->Signature); //標準PE頭欄位 printf("執行平臺:%x\n", ReadNTHeaders->FileHeader.Machine); //擴充套件PE頭欄位 printf("ImageBase:%x\n", ReadNTHeaders->OptionalHeader.ImageBase); printf("==================================================================\n"); printf("Section Header Info:\n"); //區段解析遍歷 PIMAGE_SECTION_HEADER ReadSectionHeader = IMAGE_FIRST_SECTION(ReadNTHeaders); PIMAGE_FILE_HEADER pFileHeader = &ReadNTHeaders->FileHeader; for (int i = 0; i < pFileHeader->NumberOfSections; i++) { printf("Name(區段名稱):%s\n", ReadSectionHeader[i].Name); printf("VOffset(起始的相對虛擬地址):%08X\n", ReadSectionHeader[i].VirtualAddress); printf("VSize(區段大小):%08X\n", ReadSectionHeader[i].SizeOfRawData); printf("ROffset(檔案偏移):%08X\n", ReadSectionHeader[i].PointerToRawData); printf("RSize(檔案中區段大小):%08X\n", ReadSectionHeader[i].Misc.VirtualSize); printf("標記(區段的屬性):%08X\n\n", ReadSectionHeader[i].Characteristics); } printf("==================================================================\n"); //分析函式 //ImportTable(buffer); //ExportTable(buffer); //TLSTable(buffer); //DelayImportTable(buffer); //RelocTable(buffer); //ResourceTable(buffer); //DataTable(buffer); free(buffer); return 0; } //dwRva是某個資料目錄表的VirtualAddress //buffer是讀取到的PE檔案緩衝區 DWORD RvaToOffset(DWORD dwRva, char * buffer)// 計算偏移的函式,將RVA轉化成偏移 { //解析Dos頭 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer; //PE頭 PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer); //區段表 PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt); //判斷是否落在了頭部當中 if (dwRva < pSection[0].VirtualAddress) { return dwRva; } for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++) { //VirtualAddress 起始地址 //Size 長度 //VirtualAddress + Size 結束地址 //判斷是否落在某個區段內 if (dwRva >= pSection[i].VirtualAddress && dwRva <= pSection[i].VirtualAddress + pSection[i].Misc.VirtualSize) { //dwRva - pSection[i].VirtualAddress是資料目錄表起始地址到區段起始地址的偏移(OFFSET) //pSection[i].PointerToRawData 區段到檔案頭的偏移(OFFSET) //返回的是資料目錄表起始地址到檔案頭的偏移(OFFSET) return dwRva - pSection[i].VirtualAddress + pSection[i].PointerToRawData; } } } //列印匯入表 void ImportTable(char * buffer) { //Dos PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer; //PE PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer); //定位匯入表 PIMAGE_DATA_DIRECTORY pImportDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_IMPORT); //填充結構 PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(RvaToOffset(pImportDir->VirtualAddress, buffer) + buffer); //通過迴圈來獲取各個匯入的DLL的檔案,匯入表類似陣列 while (pImport->Name != NULL) { //完成匯入表的解析 //由於再結構中指向的名字(name)是一個地址,所以還需要計算 char *szDllName = (char *)(RvaToOffset(pImport->Name, buffer) + buffer); printf("DLL名稱:%s\n", szDllName); printf("日期時間標誌:%08X\n", pImport->TimeDateStamp); printf("ForwarderChain:%08X\n", pImport->ForwarderChain); printf("名稱OFFSET:%08X\n", pImport->Name); printf("FirstThunk:%08X\n", pImport->FirstThunk); printf("OriginalFirstThunk:%08X\n\n", pImport->OriginalFirstThunk); //完成動態連結庫的解析 //指向匯入地址表的RVA PIMAGE_THUNK_DATA pIat = (PIMAGE_THUNK_DATA)(RvaToOffset(pImport->OriginalFirstThunk, buffer) + buffer); DWORD index = 0; DWORD ImprotOffset = 0; //被匯入函式的序號 while (pIat->u1.Ordinal != 0) { printf("ThunkRva:%08X\n", pImport->OriginalFirstThunk + index); ImprotOffset = RvaToOffset(pImport->OriginalFirstThunk, buffer); //打印出偏移量 printf("ThunkOffset:%08X\n", ImprotOffset + index); index += 4; if ((pIat->u1.Ordinal & 0x80000000) != 1) { PIMAGE_IMPORT_BY_NAME pName = (PIMAGE_IMPORT_BY_NAME)(RvaToOffset(pIat->u1.AddressOfData, buffer) + buffer); //名稱 printf("API名稱:%s\n", pName->Name); //序號 printf("Hint:%04X\n", pName->Hint); //被匯入函式的地址 printf("ThunkValue:%08X\n\n", pIat->u1.Function); } //向下迴圈 pIat++; } //向下迴圈 pImport++; } } void ExportTable(char * buffer) { //Dos頭 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer; //PE頭 PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer); //定位資料目錄表中的匯出表 PIMAGE_DATA_DIRECTORY pExportDir = pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT; //填充匯出表結構 PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(RvaToOffset(pExportDir->VirtualAddress, buffer) + buffer); //計算指向模組名 char * szName = (char *)(RvaToOffset(pExport->Name, buffer) + buffer); if (pExport->AddressOfFunctions == 0) { printf("當前沒有匯出表!\n"); return; } printf("匯出表OFFSET:%08X\n", RvaToOffset(pExportDir->VirtualAddress, buffer)); printf("特徵值:%08X\n", pExport->Characteristics); printf("基:%08X\n", pExport->Base); printf("名稱OFFSET:%08X\n", pExport->Name); printf("名稱:%s\n", szName); printf("函式數量:%08X\n", pExport->NumberOfFunctions); printf("函式名數量:%08X\n", pExport->NumberOfNames); printf("函式地址:%08X\n", pExport->AddressOfFunctions); printf("函式名稱地址:%08X\n", pExport->AddressOfNames); printf("函式名稱序號地址:%08X\n", pExport->AddressOfNameOrdinals); //函式數量 DWORD dwNumOfFun = pExport->NumberOfFunctions; //函式名數量 DWORD dwNumOfNames = pExport->NumberOfNames; //基 DWORD dwBase = pExport->Base; //匯出地址表 PDWORD pEat32 = (PDWORD)(RvaToOffset(pExport->AddressOfFunctions, buffer) + buffer); //匯出名稱表 PDWORD pEnt32 = (PDWORD)(RvaToOffset(pExport->AddressOfNames, buffer) + buffer); //匯出序號表 PWORD pId = (PWORD)(RvaToOffset(pExport->AddressOfNameOrdinals, buffer) + buffer); for (DWORD i = 0; i < dwNumOfFun; i++) { if (pEat32[i] == 0) { continue; } DWORD Id = 0; for (; Id < dwNumOfNames; Id++) { if (pId[Id] == i) { break; } } if (Id == dwNumOfNames) { printf("Id:%x Address:0x%08X Name[NULL]\n", i + dwBase, pEat32[i]); } else { char * szFunName = (char *)(RvaToOffset(pEnt32[Id], buffer) + buffer); printf("Id:%x Address:0x%08X Name[%s]\n", i + dwBase, pEat32[i], szFunName); } } } void TLSTable(char * buffer) { //Dos頭 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer; //PE頭 PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer); //定位資料目錄表中的TLS表 PIMAGE_DATA_DIRECTORY pTLSDir = (pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_TLS); //填充TLS結構,通過計算RVA來對其進行填充 PIMAGE_TLS_DIRECTORY pTLS32 = (PIMAGE_TLS_DIRECTORY)(RvaToOffset(pTLSDir->VirtualAddress, buffer) + buffer); printf("資料塊開始VA:%08X\n", pTLS32->StartAddressOfRawData); printf("資料塊結束VA:%08X\n", pTLS32->EndAddressOfRawData); printf("索引變數VA:%08X\n", pTLS32->AddressOfIndex); printf("回調錶VA:%08X\n", pTLS32->AddressOfCallBacks); printf("填零大小:%08X\n", pTLS32->SizeOfZeroFill); printf("特徵值:%08X\n", pTLS32->Characteristics); } //解析延遲匯入表的函式 void DelayImportTable(char * buffer) { //Dos頭 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer; //PE頭 PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer); //定義資料目錄表中的延遲匯入表 PIMAGE_DATA_DIRECTORY pImportDir = (pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT); //填充延遲匯入表的資料結構 PIMAGE_DELAYLOAD_DESCRIPTOR pDelayLoad = (PIMAGE_DELAYLOAD_DESCRIPTOR)(RvaToOffset(pImportDir->VirtualAddress, buffer) + buffer); //列印屬性 while (pDelayLoad->DllNameRVA != NULL) { char* szDllName = (char*)(RvaToOffset(pDelayLoad->DllNameRVA, buffer) + buffer); printf("DllName:%s\n", szDllName); printf("Attributes:%08X\n", pDelayLoad->Attributes); printf("ModuleHandleRVA:%08X\n", pDelayLoad->ModuleHandleRVA); printf("ImportAddressTableRVA:%08X\n", pDelayLoad->ImportAddressTableRVA); printf("ImportNameTableRVA:%08X\n", pDelayLoad->ImportNameTableRVA); printf("BoundImportAddressTableRVA:%08X\n", pDelayLoad->BoundImportAddressTableRVA); printf("UnloadInformationTableRVA:%08X\n", pDelayLoad->UnloadInformationTableRVA); printf("TimeDateStamp:%08X\n\n", pDelayLoad->TimeDateStamp); pDelayLoad++; } } void RelocTable(char * buffer) { typedef struct _TYPE { WORD Offset : 12; // (1) 大小為12Bit的重定位偏移 WORD Type : 4; // (2) 大小為4Bit的重定位資訊型別值 }TYPE, *PTYPE; //首先從dos頭開始定址 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer; //再從PE頭開始定址 PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer); //根據拓展頭的資料目錄定位資料目錄的頭 PIMAGE_DATA_DIRECTORY pRelocDir = (pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_BASERELOC); //2 找到基址重定位表,填充重定位表的結構 PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION) (RvaToOffset(pRelocDir->VirtualAddress, buffer) + buffer); //定位區段 PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt); PIMAGE_FILE_HEADER pFileHeader = &pNt->FileHeader; //開始迴圈 while (pReloc->SizeOfBlock != 0) { //找到本1000位元組的起始位置 DWORD dwCount = (pReloc->SizeOfBlock - 8) / 2; //本塊內需要重定位的個數。 DWORD dwRva = pReloc->VirtualAddress; PTYPE pRelocArr = (PTYPE)(pReloc + 1); // 陣列:儲存需要重定位的每一個點的偏移; printf("RVA:%08X\n", dwRva); printf("專案:%X H/%d D\n", pReloc->SizeOfBlock, pReloc->SizeOfBlock); printf("區段:%s\n\n", pSection->Name); //找到下一個1000位元組的結構體 pReloc = (PIMAGE_BASE_RELOCATION) ((char*)pReloc + pReloc->SizeOfBlock); for (int i = 0; i < dwCount; i++) { PDWORD pData = (PDWORD)(RvaToOffset(pRelocArr[i].Offset + dwRva, buffer) + buffer); DWORD DAD = RvaToOffset(dwRva + pRelocArr[i].Offset, buffer); printf("RVA:%08X\n", dwRva + pRelocArr[i].Offset); printf("區段:%08X\n", *pData); printf("偏移:%08X\n\n", DAD); } } } void ResourceTable(char * buffer) { //系統定義的資源順序 char * g_ResType[0x11] = { (char*)"NULL", (char*)"滑鼠指標", (char*)"點陣圖", (char*)"圖示", (char*)"選單", (char*)"對話方塊", (char*)"字串列表", (char*)"字型目錄", (char*)"字型", (char*)"快捷鍵", (char*)"非格式化資源", (char*)"訊息列表", (char*)"滑鼠指標組", (char*)"NULL", (char*)"圖示組", (char*)"NULL", (char*)"版本資訊", }; //Dos頭 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer; //PE頭 PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer); //拓展PE頭位置 PIMAGE_OPTIONAL_HEADER pOPtionHeader = (PIMAGE_OPTIONAL_HEADER)&pNt->OptionalHeader; //在資料目錄表中定位資源表的位置 PIMAGE_DATA_DIRECTORY pResDir = pOPtionHeader->DataDirectory + IMAGE_DIRECTORY_ENTRY_RESOURCE; //填充資源表的資料結構 PIMAGE_RESOURCE_DIRECTORY pFirst = (PIMAGE_RESOURCE_DIRECTORY)(RvaToOffset(pResDir->VirtualAddress, buffer) + buffer); //資源總數=資源ID條目個數+資源名稱條目個數 DWORD dwResNum = pFirst->NumberOfIdEntries + pFirst->NumberOfNamedEntries; //定位資源具體資訊,結構填充 PIMAGE_RESOURCE_DIRECTORY_ENTRY pFirstEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pFirst + 1); printf("根目錄名稱入口:%04X\n", pFirst->NumberOfNamedEntries); printf("根目錄ID入口:%04X\n\n", pFirst->NumberOfIdEntries); //根據資源總數判斷需要迴圈多少次 for (int i = 0; i < dwResNum; i++) { //ID不是字串,代表該資源是系統內建資源 if (pFirstEntry->NameIsString != 1) { if (pFirstEntry->Id < 0x10) { printf("ResType:%s\n", g_ResType[pFirstEntry->Id]); } else { printf("ResType:%d\n", g_ResType[pFirstEntry->Id]); } } // ID是字串,代表該資源是開發者的資源 else { PIMAGE_RESOURCE_DIR_STRING_U pResName = (PIMAGE_RESOURCE_DIR_STRING_U) (pFirstEntry->NameOffset + (DWORD)pFirst); wchar_t * EpName = new wchar_t[pResName->Length + 1]; memset(EpName, 0, sizeof(wchar_t)*(pResName->Length + 1)); wcsncpy_s(EpName, pResName->Length + 1, pResName->NameString, pResName->Length); } //判斷欄位是否為1,若為1則表示這個資源是個目錄 if (pFirstEntry->DataIsDirectory == 1) { //資源表的資料結構 PIMAGE_RESOURCE_DIRECTORY pSecond = (PIMAGE_RESOURCE_DIRECTORY)(pFirstEntry->OffsetToDirectory + (DWORD)pFirst); //資源總數 DWORD dwSecondCount = pSecond->NumberOfIdEntries + pSecond->NumberOfNamedEntries; //資源的具體資訊的結構 PIMAGE_RESOURCE_DIRECTORY_ENTRY pSecondEntry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pSecond + 1); //解析資源型別下的每個資源,列印選中目錄 printf("名稱入口:%04X\n", pSecond->NumberOfNamedEntries); printf("ID入口:%04X\n", pSecond->NumberOfIdEntries); //根據資源總數迴圈 for (int i = 0; i < dwSecondCount; i++) { //解析資源ID if (pSecondEntry->NameIsString != 1) { //沒名字是ID printf("Id:%d\n", pSecondEntry->Id); } else { //有名字是字串 PIMAGE_RESOURCE_DIR_STRING_U pResName = (PIMAGE_RESOURCE_DIR_STRING_U) (pSecondEntry->NameOffset + (DWORD)pFirst); wchar_t * pName = new wchar_t[pResName->Length + 1]; memset(pName, 0, sizeof(wchar_t)*(pResName->Length + 1)); wcsncpy_s(pName, pResName->Length + 1, pResName->NameString, pResName->Length); printf("Name:%s\n\n", pName); delete[]pName; } //判斷是目錄 if (pSecondEntry->DataIsDirectory == 1) { //填充資源表的資料結構 PIMAGE_RESOURCE_DIRECTORY pThird = (PIMAGE_RESOURCE_DIRECTORY) (pSecondEntry->OffsetToDirectory + (DWORD)pFirst); //填充具體資訊結構 PIMAGE_RESOURCE_DIRECTORY_ENTRY pThirdEnty = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pThird + 1); pThirdEnty->Name; //解析每個資源的資訊 if (pThirdEnty->DataIsDirectory != 1) { //第三層資源表的結構 PIMAGE_RESOURCE_DATA_ENTRY pStcData = (PIMAGE_RESOURCE_DATA_ENTRY) (pThirdEnty->OffsetToData + (DWORD)pFirst); char* pResbuf = (char *) (RvaToOffset(pStcData->OffsetToData, buffer) + buffer); DWORD StcDataOffset = RvaToOffset(pStcData->OffsetToData, buffer); printf("RVA:%08X\n", pStcData->OffsetToData); printf("Offset:%08X\n", StcDataOffset); printf("Size:%08X\n\n", pStcData->Size); } } pSecondEntry++; } printf("============================================\n"); } pFirstEntry++; } } //解析其他表(以Debug表為例) void DataTable(char * buffer) { //Dos頭 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buffer; //PE頭 PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buffer); //定位資料目錄表 PIMAGE_DATA_DIRECTORY pExportDir = pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_DEBUG;//可以通過替換巨集來對其他資料目錄進行分析 printf("DEBUG RVA %08X\nDEBUG SIZE %08X\n", pExportDir->VirtualAddress, pExportDir->Size); }