1. 程式人生 > 其它 >[PE]結構分析與程式碼實現

[PE]結構分析與程式碼實現

PE結構淺析

知識導向:
程式最開始是存放在磁碟上的,執行程式首先需要申請4GB的記憶體,將程式從磁碟copy到記憶體,但不是直接複製,而是進行拉伸處理。

這也就是為什麼會有一個檔案中地址和一個VirtualAddress,即所謂的FOA和VA
RVA是相對地址,也就是相對於可選頭中ImageBase的存放地址,檔案中的VA都是RVA

先上pe總覽圖

:)好吧,看上去還是挺複雜的,但慢慢分析,還是可以大概分析清楚的

  • DOS_HEADER
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

e_magic 用於標識是否是可執行檔案
e_lfanew NT頭的偏移,也就是說DOS頭和NT頭之間不是連續的,中間有一部分的空閒空間可用於存放說明資訊

通過偏移得到NT頭所在的位置,NT頭中主要是檔案頭和可選頭

NT頭的第一個DWORD 是NT頭簽名,用於說明可執行檔案的型別,例如PE32 PE64等

  • FileHeader 檔案頭
typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

NumberOfSections 節的數量,用於解析節
SizeOfOptionalHeader 可選頭的大小,32位為F0 64位為E0

比鄰的是OptionalHeader 可選頭,說是可選頭,實際是必需的

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;
	
    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;	
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

ImageBase 程式載入到記憶體時,預設的存放位置,但有可能實際情況不一樣。(可能設定的預設位置已經被佔據了)
FileAlignment 檔案在磁碟上的對齊大小
SectionAlignment 檔案在記憶體中的對齊大小
什麼是檔案對齊?什麼是記憶體對齊?為什麼要對齊?
SizeOfImage 檔案在記憶體中對齊後的大小
SizeOfHeaders 頭和節表在記憶體對齊後的大小

  • SectionHeader
typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc; //可以與實際不一致
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

知識拓展
union聯合體
聯合體宣告和結構體宣告差不多,唯一的區別在於底層儲存。union中的資料在底層按最大的元素分配一塊記憶體,其餘所有屬性共享這一片記憶體。
可以參考:https://www.cnblogs.com/leezhxing/p/4619185.html


每個節的大小都一樣,這也就是為什麼檔案頭中需要設定節的數量,如果不設定,解析器迴圈讀多少次節表才停止呢?
節儲存的時候不是連續的,所以需要節表中確定節的位置和大小

DWORD   VirtualAddress;	//節在記憶體中的RVA
DWORD   SizeOfRawData;	//節在磁碟上的大小
DWORD   PointerToRawData;//節在檔案中的偏移

可選頭中最後的16個表

- 匯出表
- 匯入表

- 重定位表
- 匯入繫結表

#include<iostream>
#include<Windows.h>
using namespace std;
#define ESize 0x2000	//設定拓展節的大小



char FilePath[] = "E:\\Code\\DLL01\\Debug\\DLL01.dll"; //Target File
char SaveFilePath[] = "E:\\Code\\DLL01\\Debug\\DLL03.dll"; //Save File

char* Buffer;
int FileSize;

PIMAGE_DOS_HEADER dosHeader;
PIMAGE_FILE_HEADER fileHeader;
PIMAGE_OPTIONAL_HEADER optionalHeader;
PIMAGE_SECTION_HEADER NewsectionHeader;

void readFile2Buffer() {
	

	FILE* fp = fopen(FilePath,"rb");
	fseek(fp, 0, SEEK_END);
	FileSize = ftell(fp);
	rewind(fp);

	Buffer = (char*)malloc(sizeof(char)*(FileSize+ ESize));
	fread(Buffer,1,FileSize,fp);

	fclose(fp);

}

void readBuffer2File(int BufferSize,char* Buffer) {
	FILE* fp = fopen(SaveFilePath, "wb");
	fwrite(Buffer,1,BufferSize,fp);
	fclose(fp);
}

DWORD RVA2FOA(DWORD RVA) {
	//在header節
	if (RVA < optionalHeader->SizeOfHeaders) {
		return RVA;
	}
	//在其他節
	DWORD  Alignment = optionalHeader->SectionAlignment;
	for (int i = 0; i < fileHeader->NumberOfSections; i++) {
		NewsectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)Buffer +dosHeader->e_lfanew + 4 + 20 + fileHeader->SizeOfOptionalHeader + i * 40);
		if (RVA >= NewsectionHeader->VirtualAddress && RVA < (NewsectionHeader->VirtualAddress + (NewsectionHeader->SizeOfRawData / Alignment + 1) * Alignment)) {
			return NewsectionHeader->PointerToRawData + RVA- NewsectionHeader->VirtualAddress;
		}
	}
}

DWORD FOA2RVA(DWORD FOA) {
	//在header節
	if (FOA < optionalHeader->SizeOfHeaders) {
		return FOA;
	}
	//在其他節
	DWORD  Alignment = optionalHeader->FileAlignment;
	for (int i = 0; i < fileHeader->NumberOfSections; i++) {
		NewsectionHeader = (PIMAGE_SECTION_HEADER)(Buffer+dosHeader->e_lfanew + 4 + 0x14 + fileHeader->SizeOfOptionalHeader + i * 40);		
		if (FOA >= NewsectionHeader->PointerToRawData && FOA < ( NewsectionHeader->PointerToRawData + (NewsectionHeader->SizeOfRawData / Alignment+1)* Alignment)) {
			return FOA - NewsectionHeader->PointerToRawData+NewsectionHeader->VirtualAddress;
		}
	}
}


/*
	建立一個新節,並返回該節的FOA
*/
DWORD CreateNewSection() { 
	//如果在新增節表資訊時,出現大小不夠的情況,那麼所有的資料都必須往後移,豈不是。。。
	//所以在這裡僅對能存放下的情況進行研究
	DWORD realSize = dosHeader->e_lfanew + 0xF8 + 0x28 * fileHeader->NumberOfSections;
	DWORD AlignSize = optionalHeader->SizeOfHeaders;
	if (AlignSize - realSize < 0x28) {
		cout << "Memory is not enough" << endl;
		return 0;
	}
	//獲取新節表頭位置
	PIMAGE_SECTION_HEADER NewsectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + realSize);
	DWORD FileEndAddress = 0x00;
	DWORD VirtualEndAddress = 0x00;
	//獲取起初存放位置為最後的節的資訊(正常情況下,節表和節的順序是對應的,但避免惡意修改,做個比較
	for (int i = 0; i < fileHeader->NumberOfSections; i++) {
		PIMAGE_SECTION_HEADER Section = (PIMAGE_SECTION_HEADER)(Buffer + dosHeader->e_lfanew + 0x78 + 0x10 * 8 + 0x28 * i);
		if ((DWORD)Section->PointerToRawData + 1 > FileEndAddress) {
			if(Section->SizeOfRawData % optionalHeader->FileAlignment != 0)
				FileEndAddress = Section->PointerToRawData + ((Section->SizeOfRawData / optionalHeader->FileAlignment + 1) * optionalHeader->FileAlignment);
			else
				FileEndAddress = Section->PointerToRawData + ((Section->SizeOfRawData / optionalHeader->FileAlignment) * optionalHeader->FileAlignment);
			
			if(Section->SizeOfRawData % optionalHeader->SectionAlignment != 0)
				VirtualEndAddress = Section->VirtualAddress + ((Section->SizeOfRawData / optionalHeader->SectionAlignment + 1) * optionalHeader->SectionAlignment);
			else
				VirtualEndAddress = Section->VirtualAddress + ((Section->SizeOfRawData / optionalHeader->SectionAlignment) * optionalHeader->SectionAlignment);
		}
	}
	// 
	//更新節的相關資訊-新增一個大小為0x1000的節
	NewsectionHeader->Name[0] = 'N';
	NewsectionHeader->Name[1] = 'e';
	NewsectionHeader->Name[2] = 'w';
	NewsectionHeader->Name[3] = '\0';

	NewsectionHeader->Misc.VirtualSize = ESize;
	NewsectionHeader->PointerToRawData = FileEndAddress;
	NewsectionHeader->SizeOfRawData = ESize;
	NewsectionHeader->VirtualAddress = VirtualEndAddress;

	//更新FileHeader的相關資訊
	fileHeader->NumberOfSections = fileHeader->NumberOfSections + 1; 
	optionalHeader->SizeOfHeaders += 0x28;

	return NewsectionHeader->PointerToRawData;
}

void PEParse() {
	if (*(WORD*)Buffer != IMAGE_DOS_SIGNATURE) {
		cout << "Error Format" << endl;
		return;
	}
	//dosHeader
	dosHeader = (PIMAGE_DOS_HEADER)Buffer;

	if (*(PDWORD)(Buffer +dosHeader->e_lfanew) != IMAGE_NT_SIGNATURE) {
		cout << "Not PE" << endl;
		return;
	}
	//fileHeader
	fileHeader = (PIMAGE_FILE_HEADER)(Buffer + dosHeader->e_lfanew + 4);
	cout << "NumberOfSections:" << fileHeader->NumberOfSections<<endl;
	cout << "SizeOfOptionalHeader:" << fileHeader->SizeOfOptionalHeader<<endl;

	//optionalHeader
	optionalHeader = (PIMAGE_OPTIONAL_HEADER)(Buffer + dosHeader->e_lfanew + 24);


	//NewsectionHeader
	for (int i = 0; i < fileHeader->NumberOfSections; i++) {
		NewsectionHeader = (PIMAGE_SECTION_HEADER)(Buffer+dosHeader->e_lfanew + 4 + 0x14 + fileHeader->SizeOfOptionalHeader + i * 40);
		cout << "************NewsectionHeader**************" << endl;
		cout << "VirtualAddress:" << NewsectionHeader->VirtualAddress << endl;
		cout << "PointerToRawData:" << NewsectionHeader->PointerToRawData << endl;
		cout << "SizeOfRawData:" << NewsectionHeader->SizeOfRawData << endl;
	}
	cout << "************************************************" << endl;
	cout << "*********        PE 解析完畢       ************" << endl;
	cout << "************************************************" << endl;
	cout << endl;
	cout << endl;
}
/*
	#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
	#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
	#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
	#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
	#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
	#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
	#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
	//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
	#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
	#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
	#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
	#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
	#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
	#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
	#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
	#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor
*/



DWORD ExportTable() {
	/*
		typedef struct _IMAGE_EXPORT_DIRECTORY {
			DWORD   Characteristics;
			DWORD   TimeDateStamp;
			WORD    MajorVersion;
			WORD    MinorVersion;
			DWORD   Name;
			DWORD   Base;					//Base of sequence
			DWORD   NumberOfFunctions;
			DWORD   NumberOfNames;
			DWORD   AddressOfFunctions;     // RVA from base of image	DWORD	
			DWORD   AddressOfNames;         // RVA from base of image	DWORD
			DWORD   AddressOfNameOrdinals;  // RVA from base of image	WORD
		} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
	*/
	cout << "===============================" << endl;
	cout << "IMAGE_DIRECTORY_ENTRY_BASERELOC" << endl;
	cout << "===============================" << endl;

	if (optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0) {
		cout << "No export table!!!" << endl;
		return 0;
	}

	cout << "VirtualAddress:" << hex << optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress<<endl;
	cout << "Size:" << hex << optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size << endl;
	cout << sizeof(IMAGE_EXPORT_DIRECTORY) << endl;

	DWORD FOA = RVA2FOA(optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
	PIMAGE_EXPORT_DIRECTORY ExportTable = (PIMAGE_EXPORT_DIRECTORY)(FOA + (DWORD)Buffer);
	cout << "^^^^^^^^^^^^^Export Table^^^^^^^^^^^^^^" << endl;
	cout << "Export Table Name:" << (char*)((DWORD)Buffer+RVA2FOA(ExportTable->Name)) << endl;
	cout << "Base:" << ExportTable->Base << endl;
	cout << "NumberOfFunctions:" << ExportTable->NumberOfFunctions << endl;
	cout << "NumberOfNames:" << ExportTable->NumberOfNames << endl;

	cout << (DWORD)Buffer << endl;
	cout << RVA2FOA(ExportTable->AddressOfNames) << endl;


	for (int i = 0; i < ExportTable->NumberOfNames; i++) {
		cout << "==============Export Function============" << endl;
		cout << "FunctionName:" << (char*)((DWORD)Buffer + RVA2FOA(*(DWORD*)((DWORD)Buffer + RVA2FOA(ExportTable->AddressOfNames) + i * 4))) << endl;
		//cout << "FunctionOrd:" << (*(WORD*)((DWORD)Buffer + RVA2FOA(ExportTable->AddressOfNameOrdinals))+i*2) << endl;
		cout << "FunctionAddr(RVA):" << *(DWORD*)((DWORD)Buffer + RVA2FOA(ExportTable->AddressOfFunctions) + 4 * (*(WORD*)((DWORD)Buffer + RVA2FOA(ExportTable->AddressOfNameOrdinals) + i * 2)))<<endl;
	}
	return (DWORD)ExportTable-(DWORD)Buffer;
}

DWORD RelocationTable() {
	cout << "===============================" << endl;
	cout << "IMAGE_DIRECTORY_ENTRY_BASERELOC" << endl;
	cout << "===============================" << endl;

	if (optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress == 0 && optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size == 0) {
		cout << "Relocation Table Is Null" << endl;
		return 0;
	}
	
	DWORD VirtualAddress = optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
	DWORD SizeOfBlock = optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;

	DWORD FOA = (DWORD)Buffer+RVA2FOA(VirtualAddress);
	while (*((DWORD*)FOA) != 0 || *((DWORD*)FOA + 1) != 0) {
		DWORD VirtualAddress = *((DWORD*)FOA);
		DWORD SizeOfBlock = *((DWORD*)FOA + 1);

		cout << "*********************************" << endl;
		cout << "VirtualAddress:" << hex <<VirtualAddress << endl;
		for (int i = 0; i < (SizeOfBlock - 8) / 2; i++) {
			WORD li = *((WORD*)(FOA + 8) + i);
			printf("*%02X* RVA:%08X ATTR:%d\n", i, VirtualAddress + (li & 0x0fff), (li & 0xf000) >> 12);
		}
		FOA += SizeOfBlock;
	}
	return RVA2FOA(VirtualAddress); //返回重定位表在檔案中的位置
}

void ImportTable() {
	DWORD importTable = (DWORD)Buffer + RVA2FOA(optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);	
	//下一個匯入表為全0匯入表結束
	while (1) {
		PIMAGE_IMPORT_DESCRIPTOR table = (PIMAGE_IMPORT_DESCRIPTOR)importTable;
		if (
			table->FirstThunk == 0&&
			table->OriginalFirstThunk == 0
		) {
			cout << "*******************" << endl;
			cout << "End of Import Table" << endl;
			cout << "*******************" << endl;
			break;
		}

		cout << (char*)((DWORD)Buffer + RVA2FOA(table->Name)) << endl;
		
		DWORD table1 = (DWORD)Buffer + RVA2FOA(table->OriginalFirstThunk);
		DWORD table2 = (DWORD)Buffer + RVA2FOA(table->FirstThunk);

		cout << "-------------------------------" << endl;
		cout << "FirstThunk:" << hex << table2 << endl;
		while (1) {
			DWORD data = *(DWORD*)table2;
			if (data == 0)
				break;
			if (((data & 0x80000000) >> 31) == 1)
				cout << (data & 0x7fffffff) << endl;
			else {
				PIMAGE_IMPORT_BY_NAME tmp = (PIMAGE_IMPORT_BY_NAME)((DWORD)Buffer + RVA2FOA(data));
				cout << "HINT:" << hex << tmp->Hint << "-";
				cout << "Name:" << tmp->Name << endl;
			}
			table2 += 4;
		}

		cout << "-------------------------------" << endl;
		cout << "OriginalFirstThunk:" << hex << table1 << endl;
		while (1) {
			DWORD data = *(DWORD*)table1;
			if (data == 0)
				break;
			if (((data & 0x80000000) >> 31) == 1)
				cout << (data & 0x7fffffff) << endl;
			else {
				PIMAGE_IMPORT_BY_NAME tmp = (PIMAGE_IMPORT_BY_NAME)((DWORD)Buffer + RVA2FOA(data));
				cout << "HINT:" << hex << tmp->Hint<<"-";
				cout << "Name:" << tmp->Name << endl;
			}
			table1 += 4;
		}

		

		importTable += sizeof(IMAGE_IMPORT_DESCRIPTOR);
	}
}




int main() {
	//Copy File to FileBuffer
	readFile2Buffer();
	//PE Parse
	PEParse();
	/*
		Print Export Table
	*/
	//ExportTable();

	/*
		Print Relocation Table
	*/
	//RelocationTable();

	/*
		Print Import Table
	*/
	ImportTable();

	/*
		Print Bound Address Table
	*/







	//Free Buffer
	free(Buffer);
	return 0;
}