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\\notepad.exe"; //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;
}
}
} 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[3].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;
bool isBounded = false;
if (table->TimeDateStamp == 0xffffffff)
isBounded = true; DWORD table1 = (DWORD)Buffer + RVA2FOA(table->OriginalFirstThunk);
DWORD table2 = (DWORD)Buffer + RVA2FOA(table->FirstThunk); cout << "-------------------------------" << endl;
while (1) {
cout << "FirstThunk:";
DWORD data = *(DWORD*)table2;
if (data == 0)
break;
if (isBounded) {
cout << (data & 0x7fffffff) << endl;
}
else if (((data & 0x80000000) >> 31) == 1)
cout << (data & 0x7fffffff);
else {
PIMAGE_IMPORT_BY_NAME tmp = (PIMAGE_IMPORT_BY_NAME)((DWORD)Buffer + RVA2FOA(data));
cout << "HINT:" << hex << tmp->Hint << "-";
cout << "Name:" << tmp->Name;
}
table2 += 4; cout << "OrigionalThunk:";
DWORD data1 = *(DWORD*)table1;
if (((data1 & 0x80000000) >> 31) == 1)
cout << (data1 & 0x7fffffff) << endl;
else {
PIMAGE_IMPORT_BY_NAME tmp = (PIMAGE_IMPORT_BY_NAME)((DWORD)Buffer + RVA2FOA(data1));
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(); //Free Buffer
free(Buffer);
return 0;
}

[PE]结构分析与代码实现的更多相关文章

  1. [PE结构分析] 10.基址重定位

    源代码如下: typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; // WORD Type ...

  2. [PE结构分析] 6.IMAGE_SECTION_HEADER

    IMAGE_SECTION_HEADER 的源代码如下: typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAM ...

  3. [PE结构分析] 5.IMAGE_OPTIONAL_HEADER

    结构体源代码如下: typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // +18h WORD Magic; // 标志字, ...

  4. PE结构分析(二)

    在表中,我们知道了0x01 4c对应的平台结构是i386: 我们接着分析下一个字段,给出PE结构图 向后推移2个字节,现在来到(图片高亮部分): 高亮部分对应IMAGE_NT_HEADERS结构的Nu ...

  5. [PE结构分析] 9.导出表 IMAGE_EXPORT_DIRECTORY

    typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; // 未使用,总为0 DWORD TimeDateStamp; // 文 ...

  6. [PE结构分析] 8.输入表结构和输入地址表(IAT)

    在 PE文件头的 IMAGE_OPTIONAL_HEADER 结构中的 DataDirectory(数据目录表) 的第二个成员就是指向输入表的.每个被链接进来的 DLL文件都分别对应一个 IMAGE_ ...

  7. [PE结构分析] 7.相对虚拟地址(RVA)和文件偏移间的转换

    RVA是相对虚拟地址(Relative Virtual Address)的缩写.RVA是当PE 文件被装载到内存中后,某个数据位置相对于文件头的偏移量. 例如:导入表的位置和大小可以从PE文件头中IM ...

  8. [PE结构分析] 11.资源表结构

    资源表是一个树形结构,可以设置成2的31次方的层数,Windows 使用了3级: 类型->名称->语言 其中涉及到四个结构: Data Description Resource Direc ...

  9. PE头的应用---插入代码到EXE或DLL文件中

    三.代码实现(DELPHI版本),采用第三种方式实现代码插入. 1. 定义两个类,一个用来实现在内存中建立输入表:一个用来实现对PE头的代码插入. DelphiCode: const MAX_SECT ...

随机推荐

  1. Jackson & fastJson的使用

    Jackson import lombok.Data; @Data public class Student { private Long id; private String name; priva ...

  2. PTA 7-2 哈夫曼编码 (30分)

    PTA 7-2 哈夫曼编码 (30分) 给定一段文字,如果我们统计出字母出现的频率,是可以根据哈夫曼算法给出一套编码,使得用此编码压缩原文可以得到最短的编码总长.然而哈夫曼编码并不是唯一的.例如对字符 ...

  3. 美化CMD

    配置 Windows Terminal 的步骤 前提:在微软商店下载两个软件 Windows Terminal PowerShell(因为最好使用 PowerShell7 ,否则以下命令可能执行不了) ...

  4. 部署一个支持Dapr 的Kubernetes APISIX Ingress

    在这篇文章中,我将展示如何创建一个 APISIX控制器,该控制器在 Kubernetes 集群中公开启用 Dapr 的应用程序. 本质上,APISIX控制器将配置相同的标准 Dapr annotati ...

  5. Django 小实例S1 简易学生选课管理系统 10 老师课程业务实现

    Django 小实例S1 简易学生选课管理系统 第10节--老师课程业务实现 点击查看教程总目录 作者自我介绍:b站小UP主,时常直播编程+红警三,python1对1辅导老师. 课程模块中,老师将要使 ...

  6. python实现轮廓发现

    目录: (一)轮廓发现的介绍 (二)代码实现 (1)使用直接使用阈值方法threshold方法获取二值化图像来选择轮廓 (2)使用canny边缘检测获取二值化图像 (一)轮廓发现的介绍与API的介绍 ...

  7. <C#任务导引教程>练习四

    //27,创建一个控制台应用程序,声明两个DateTime类型的变量dt,获取系统的当前日期时间,然后使用Format格式化进行规范using System;class Program{    sta ...

  8. [loj6734]图上的游戏

    考虑原图是一条链的情况-- 思路:随机一个点$x$,将其所在段(边集)再划分为两段,重复此过程即可得到该链 实现上,(从左到右)维护每一段的左端点和边集,二分找到最后一个删除后$x$到根不连通的段,那 ...

  9. [cf1458C]Latin Square

    维护$n^{2}$个三元组$(x,y,z)$,每一个三元组描述$a_{x,y}=z$ 对于RLDU这四个操作,即将所有三元组的$x$或$y$执行$\pm 1$(模$n$意义下) 对于IC这两个操作,即 ...

  10. 面试官又整新活,居然问我for循环用i++和++i哪个效率高?

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 前几天,一个小伙伴告诉我,他在面试的时候被面试官问了这么一个问题: 在for循环中,到底应该用 i++ 还是 ++i ? 听到这,我感觉这面试官 ...