PE文件解析 基础篇

PE文件解析 基础篇

来源 https://bbs.pediy.com/thread-247114.htm

前言

  • 之前学习了PE格式,为了更好的理解,决定写一个类似LoadPE的小工具。
  • 编译器是VS2015,采用MFC框架。
  • 此系列文章采用边介绍知识点,边写代码的形式,以免变的无聊丧失兴趣。
  • PE知识请参照《加密与解密》第10章

文章有错误或则不清楚的地方还请您指出。

PE文件格式

1.PE文件基本概念

  • PE文件是windows系统中遵循PE结构的文件,比如以.exe   .dll为后缀名的文件 以及系统驱动文件。(PE结构框架看下图)

PE文件大体分为两部分,头(包括下图中的DOS头,PE文件头,块表)与主体(块),

  • PE文件从磁盘当中像内存中的映射,不是简单的“1对1”的关系,而是“拉长”了。具体的位置表现在块。 但是磁盘上的数据结构与在内存中的结构是一致的。

  • 无论PE文件在磁盘中还是在内存中,都少不了地址的概念,理解一下几个概念至关重要。

虚拟地址(VA): 在一个程序运行起来的时候,会被加载到内存中,并且每个进程都有自己的4GB,这个4GB当中的某个位置叫做**虚拟地址**,由物理地址映射过来的,4GB的空间,并没有全部被用到。

基地址( Imagebase ):       磁盘中的文件加载到内存当中的时候可以加载到任意位置,而这个位置就是程序的基址。EXE默认的加载基址是400000h,DLL文件默认基址是10000000h。需要注意的是基地址不是程序的入口点。

相对虚拟地址(RVA):为了避免PE文件中有确定的内存地址,引入了相对虚拟地址的概念。RVA是在内存中相对与载入地址(基地址)

的偏移量,所以你可以发现前三个概念的关系 :  虚拟地址(VA)=   基地址+ 相对虚拟地址(RVA)

文件偏移地址(FOA):当PE文件储存在某个磁盘当中的时候,某个数据的位置相对于文件头的偏移量。

入口点(OEP):首先明确一个概念就是OEP是一个RVA,,然后使用 OEP + Imagebase == 入口点的VA,通常情况下,OEP指向的不是main函数。

  • 存了张图 比较好的解释了各部分的关系

接下来依次介绍PE结构框图的每个部分

2.DOS头部

每个PE文件都是以DOS头开始的,IMAGE_DOS_HEADER 结构如下所示


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

(最左边是文件头的偏移量。) 

IMAGE_DOS_HEADER STRUCT 

+0h WORD    e_magic        //   MZ(4Dh 5Ah)     DOS可执行文件标记 

+2h     WORD    e_cblp            

+4h WORD    e_cp                         

+6h WORD    e_crlc                      

+8h WORD    e_cparhdr      

+0ah    WORD    e_minalloc       

+0ch    WORD    e_maxalloc  

+0eh    WORD    e_ss           

+10h    WORD    e_sp       

+12h    WORD    e_csum      

+14h    WORD    e_ip        

+16h    WORD    e_cs        

+18h    WORD    e_lfarlc       

+1ah    WORD    e_ovno          

+1ch    WORD    e_res[4]        

+24h    WORD    e_oemid         

+26h    WORD    e_oeminfo    

+29h    WORD    e_res2[10]  

+3ch    DWORD   e_lfanew     //  RVA     指向PE文件头 

} IMAGE_DOS_HEADER ENDS

需要关注的点是结构体的第一个和第二个元素。

e_magic:DOS头的标记位,值为4D5Ah。ASCII为”MZ“,判断一个文件是否为PE文件是会用

e_lfanew:这是一个RVA,代表了PE文件头到基址的偏移量,我们可以用它来找到PE文件头的位置。

我们用010editor打开一个exe文件

3.PE文件头

IMAGE_NT_HEADERS STRUCT  结构体


1

2

3

4

5

6

IMAGE_NT_HEADERS STRUCT 

{

+0h       DWORD    Signature  

+4h       IMAGE_FILE_HEADER    FileHeader 

+18h      IMAGE_OPTIONAL_HEADER32   OptionalHeader   

} IMAGE_NT_HEADERS ENDS

  • Signature  字段

在一个PE文件中Signature字段被设置为4550h,ASCII码为”PE00“。如上图所示。

  • IMAGE_FILE_HEADER  结构体

1

2

3

4

5

6

7

8

9

10

struct IMAGE_FILE_HEADER

{

    WORD Machine; //运行平台

    WORD NumberOfSections; //区块表的个数

    DWORD TimeDataStamp;//文件创建时间,是从1970年至今的秒数

    DWORD PointerToSymbolicTable;//指向符号表的指针

    DWORD NumberOfSymbols;//符号表的数目

    WORD SizeOfOptionalHeader;//IMAGE_NT_HEADERS结构中OptionHeader成员的大小,对于win32平台这个值通常是0x00e0

    WORD Characteristics;//文件的属性值

}

在010 Editor上查看一下

  • IMAGE_OPTIONAL_HEADER 结构体

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

typedef struct _IMAGE_OPTIONAL_HEADER

{

    //

    // Standard fields.  

    //

+18h    WORD    Magic;                   // 标志字, ROM 映像(0107h),普通可执行文件(010Bh)

+1Ah    BYTE    MajorLinkerVersion;      // 链接程序的主版本号

+1Bh    BYTE    MinorLinkerVersion;      // 链接程序的次版本号

+1Ch    DWORD   SizeOfCode;              // 所有含代码的节的总大小

+20h    DWORD   SizeOfInitializedData;   // 所有含已初始化数据的节的总大小

+24h    DWORD   SizeOfUninitializedData; // 所有含未初始化数据的节的大小

+28h    DWORD   AddressOfEntryPoint;     // 程序执行入口RVA

+2Ch    DWORD   BaseOfCode;              // 代码的区块的起始RVA

+30h    DWORD   BaseOfData;              // 数据的区块的起始RVA

    //

    // NT additional fields.    以下是属于NT结构增加的领域。

    //

+34h    DWORD   ImageBase;               // 程序的首选装载地址

+38h    DWORD   SectionAlignment;        // 内存中的区块的对齐大小

+3Ch    DWORD   FileAlignment;           // 文件中的区块的对齐大小

+40h    WORD    MajorOperatingSystemVersion;  // 要求操作系统最低版本号的主版本号

+42h    WORD    MinorOperatingSystemVersion;  // 要求操作系统最低版本号的副版本号

+44h    WORD    MajorImageVersion;       // 可运行于操作系统的主版本号

+46h    WORD    MinorImageVersion;       // 可运行于操作系统的次版本号

+48h    WORD    MajorSubsystemVersion;   // 要求最低子系统版本的主版本号

+4Ah    WORD    MinorSubsystemVersion;   // 要求最低子系统版本的次版本号

+4Ch    DWORD   Win32VersionValue;       // 莫须有字段,不被病毒利用的话一般为0

+50h    DWORD   SizeOfImage;             // 映像装入内存后的总尺寸

+54h    DWORD   SizeOfHeaders;           // 所有头 + 区块表的尺寸大小

+58h    DWORD   CheckSum;                // 映像的校检和

+5Ch    WORD    Subsystem;               // 可执行文件期望的子系统

+5Eh    WORD    DllCharacteristics;      // DllMain()函数何时被调用,默认为 0

+60h    DWORD   SizeOfStackReserve;      // 初始化时的栈大小

+64h    DWORD   SizeOfStackCommit;       // 初始化时实际提交的栈大小

+68h    DWORD   SizeOfHeapReserve;       // 初始化时保留的堆大小

+6Ch    DWORD   SizeOfHeapCommit;        // 初始化时实际提交的堆大小

+70h    DWORD   LoaderFlags;             // 与调试有关,默认为 0 

+74h    DWORD   NumberOfRvaAndSizes;     // 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16

+78h    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];   

// 数据目录表

} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

重要的有

AddressOfEntryPoint: 也就是上文提到的OEP,程序源入口点。

ImageBase: 默认加载基址,

SectionAlignment:  内存当中的块对齐数,一般为0x1000

FileAlignment:磁盘当中块对齐数,一般为0x200

SizeOfHeaders:所有头部大小 也就是DOS头 文件头 以及区块头的总大小 ,文件主体相对文件其实的偏移。

IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]:数据目录表,保存了各种表的RVA及大小。

来看一下数据目录的定义


1

2

3

4

IMAGE_DATA_DIRECTORY STRUCT

      VirtualAddress    DWORD       ?   ; 数据的起始RVA

      Size             DWORD       ?   ; 数据块的长度

IMAGE_DATA_DIRECTORY ENDS

在010 Editor上查看一下

4.写代码操作一下

主要解析了DOS头与PE文件头比较重要的字段,直接放代码。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

//打开文件

m_hFile = CreateFile(

    m_DeleFileName,GENERIC_READ,NULL,NULL,OPEN_EXISTING,

    FILE_ATTRIBUTE_NORMAL,NULL);

DWORD dwSize = GetFileSize(m_hFile, NULL);

PBYTE pBuf = new BYTE[dwSize]{};

//读取

ReadFile(m_hFile,pBuf,dwSize,&dwSize,NULL);

//判断是否为PE文件

m_pDos = PIMAGE_DOS_HEADER(pBuf);

if (m_pDos->e_magic!=IMAGE_DOS_SIGNATURE)

{

    MessageBox(L"不是有效的PE文件 \n");

    CloseHandle(m_hFile);

    m_hFile = NULL;

    return;

}

m_pNTHeader = PIMAGE_NT_HEADERS(pBuf+m_pDos->e_lfanew);

if (m_pNTHeader->Signature!= IMAGE_NT_SIGNATURE)

{

    MessageBox(L"不是有效的PE文件 \n");

    CloseHandle(m_hFile);

    m_hFile = NULL;

    return;

}

//读取文件头信息

m_pFileHeader = &(m_pNTHeader->FileHeader);

m_NumberOfSections.Format(L"%X",m_pFileHeader->NumberOfSections);

m_TimeDateStamp.Format(L"%p", m_pFileHeader->TimeDateStamp);

m_SizeOfOptionalHeader.Format(L"%X", m_pFileHeader->SizeOfOptionalHeader);

//拓展头信息

m_pOptionalHeader = &(m_pNTHeader->OptionalHeader);

m_AddressOfEntryPoint.Format(L"%X",m_pOptionalHeader->AddressOfEntryPoint);

m_SizeOfHeaders.Format(L"%X", m_pOptionalHeader->SizeOfHeaders);

m_ImageBase.Format(L"%X", m_pOptionalHeader->ImageBase);

m_SizeOfImage.Format(L"%X", m_pOptionalHeader->ImageBase);

m_BaseOfCode.Format(L"%X", m_pOptionalHeader->BaseOfCode);

m_DllCharacteristics.Format(L"%X", m_pOptionalHeader->DllCharacteristics);

m_BaseOfData.Format(L"%X", m_pOptionalHeader->BaseOfData);

m_NumberOfRvaAndSizes.Format(L"%X", m_pOptionalHeader->NumberOfRvaAndSizes);

m_SectionAlignment.Format(L"%X", m_pOptionalHeader->SectionAlignment);

m_FileAlignment.Format(L"%X", m_pOptionalHeader->FileAlignment);

m_CheckSum.Format(L"%X", m_pOptionalHeader->CheckSum);

m_Magic.Format(L"%X", m_pOptionalHeader->CheckSum);

m_Subsystem.Format(L"%X", m_pOptionalHeader->Subsystem);

实现的效果如下:

第一部分比较简单,完整代码放到附件。

============== End

原文地址:https://www.cnblogs.com/lsgxeva/p/9822289.html

时间: 2024-11-05 06:25:06

PE文件解析 基础篇的相关文章

C++PE文件格式解析类(轻松制作自己的PE文件解析器)

PE是Portable Executable File Format(可移植的运行体)简写,它是眼下Windows平台上的主流可运行文件格式. PE文件里包括的内容非常多,详细我就不在这解释了,有兴趣的能够參看之后列出的參考资料及其它相关内容. 近期我也在学习PE文件格式,參考了很多资料.用C++封装了一个高效方便的PE文件格式解析的类. 该类对想学PE文件结构的朋友可算一份可贵的资料.代码均非常易懂,考虑较全面,具有一定的通用性. 同一时候该类也能够让想创建自己的PE文件解析软件的朋能够轻松在

[WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析

[WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析 标签: webkit内核JavaScriptCore 2015-03-26 23:26 2285人阅读 评论(1) 收藏 举报  分类: Webkit(34)  JavascriptCore/JIT(3)  版权声明:本文为博主原创文章,未经博主允许不得转载. 看到HorkeyChen写的文章<[WebKit] JavaScriptCore解析--基础篇(三)从脚本代码到JIT编译的代码实现>

QT开发(四十一)——XML文件解析基础

QT开发(四十一)--XML文件解析基础 一.XML文档简介 XML(Extensible Markup Language,可扩展标记语言),是一种通用的文本格式,被广泛运用于数据交换和数据存储,而不是显示数据.XML的标签没有被预定义,用户需要在使用时自行进行定义.XML是W3C(万维网联盟)的推荐标准.相对于数据库表格的二维表示,XML使用的树形结构更能表现出数据的包含关系,作为一种文本文件格式,XML简单明了的特性使得它在信息存储和描述领域非常流行. <?xml version="1

[WebKit内核] JavaScriptCore深度解析--基础篇(一)字节码生成及语法树的构建

看到HorkeyChen写的文章<[WebKit] JavaScriptCore解析--基础篇(三)从脚本代码到JIT编译的代码实现>,写的很好,深受启发.想补充一些Horkey没有写到的细节比如字节码是如何生成的等等,为此成文. JSC对JavaScript的处理,其实与Webkit对CSS的处理许多地方是类似的,它这么几个部分: (1)词法分析->出来词语(Token): (2)语法分析->出来抽象语法树(AST:Abstract Syntax Tree): (3)遍历抽象语法

[WebKit] JavaScriptCore解析--基础篇 (一)JSC与WebCore

先看一下官方的基本介绍,短短几句就塞满了关键字. SquirrelFish,正式名称是JavaScriptCore,包括register-based(基于寄存器的虚拟机), direct-threaded, high-level bytecode engine(字节码引擎).它使用基于内置copy propagation(复制性传播算法)的一次性编译器(one-pass compiler),能够延迟从语法树(Syntax Tree)上生成字节码(Bytecodes). 由此可见JavaScrip

xml文件解析基础和签名

0. 前言 参考文档1:使用dom4j读取xml文件的四种方式 参考文档2:dom4j解析xml文件 参考文档3:String 和 document 的相互转换总结 参考文档4: Java SE 6中 XML 数字签名的标准 Java 接口 参考文档5:XML Security with Digital Signature in JAVA 1.xml文件的解析 1.1 基本的jdk解析xml的原始api解析 声明 1.以下代码块要放到一个类中才能运行 dom类型为:org.w3c.dom.* 解

C++虚函数表解析(基础篇)

原文:http://blog.csdn.net/haoel/article/details/1948051 一.简介 C++中的虚函数的作用主要是实现了多态的机制.虚函数(Virtual Function)其实是通过一张虚函数表(Virtual Table)来实现的.简称为V-Table.在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承.覆盖的问题,保证其容真实反应实际的函数.这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这

android基础知识13:AndroidManifest.xml文件解析

1.重要性 AndroidManifest.xml是Android应用程序中最重要的文件之一.它是Android程序的全局配置文件,是每个 android程序中必须的文件.它位于我们开发的应用程序的根目录下,描述了package中的全局数据,包括package中暴露的组件 (activities, services, 等等),以及他们各自的实现类,各种能被处理的数据和启动位置等重要信息. 因此,该文件提供了Android系统所需要的关于该应用程序的必要信息,即在该应用程序的任何代码运行之前系统所

关于Java面试知识点解析——JVM基础篇

跳槽时时刻刻都在发生,但是我建议大家跳槽之前,先想清楚为什么要跳槽.切不可跟风,看到同事一个个都走了,自己也盲目的开始面试起来(期间也没有准备充分),到底是因为技术原因(影响自己的发展,偏移自己规划的轨迹),还是钱给少了,不受重视. 准备不充分的面试,完全是浪费时间,更是对自己的不负责(如果title很高,当我没说).今天给大家分享下 Java面试知识点解析--JVM基础篇 1)Java 是如何实现跨平台的? 注意:跨平台的是 Java 程序,而不是 JVM.JVM 是用 C/C++ 开发的,是