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

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

  1. [系统安全] 十六.PE文件逆向基础知识(PE解析、PE编辑工具和PE修改)

    [系统安全] 十六.PE文件逆向基础知识(PE解析.PE编辑工具和PE修改) 文章来源:https://masterxsec.github.io/2017/05/02/PE%E6%96%87%E4%B ...

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

    PE是Portable Executable File Format(可移植的运行体)简写,它是眼下Windows平台上的主流可运行文件格式. PE文件里包括的内容非常多,详细我就不在这解释了,有兴趣 ...

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

    [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析 标签: webkit内核JavaScriptCore 2015-03-26 23:26 2285 ...

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

    看到HorkeyChen写的文章<[WebKit] JavaScriptCore解析--基础篇(三)从脚本代码到JIT编译的代码实现>,写的很好,深受启发.想补充一些Horkey没有写到的 ...

  5. C语言读取写入CSV文件 [一]基础篇

    本系列文章目录 [一] 基础篇 [二] 进阶篇--写入CSV [三] 进阶篇--读取CSV 什么是CSV? CSV 是一种以纯文本形式存储的表格数据,具体介绍如下(来自维基百科): 逗号分隔值(Com ...

  6. PE文件解析器的编写(二)——PE文件头的解析

    之前在学习PE文件格式的时候,是通过自己查看各个结构,自己一步步计算各个成员在结构中的偏移,然后在计算出其在文件中的偏移,从而找到各个结构的值,但是在使用C语言编写这个工具的时候,就比这个方便的多,只 ...

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

    先看一下官方的基本介绍,短短几句就塞满了关键字. SquirrelFish,正式名称是JavaScriptCore,包括register-based(基于寄存器的虚拟机), direct-thread ...

  8. Python数据类型解析(基础篇)

    Python语言的类型   数字类型   字符串类型   元组类型   列表类型   文件类型  字典类型     1.数字类型   Python有三种数字类型:整数,浮点数,复数 Python中的整 ...

  9. 3D游戏常用技巧Normal Mapping (法线贴图)原理解析——基础篇

    http://www.cnblogs.com/wangchengfeng/p/3470310.html

随机推荐

  1. 从零搭建HBase集群

    本文从零开始搭建大数据集群,涉及Linux集群安装搭建,Hadoop集群搭建,HBase集群搭建,Java接口封装,对接Java的C#类库封装 Linux集群搭建与配置 Hadoop集群搭建与配置 H ...

  2. Qt-网易云音乐界面实现-9 照片墙功能

    最近车也买了,不过倒是没有想象的那么开心,车真的是想消耗品啊. 写这个专题了,本来是想好好的磨练一下自己,不过可能要在在理就GG了.腻味了. 还是先看下效果图吧 这个照片墙还差点东西,不过我个人认为需 ...

  3. SpringBoot文件上传异常之提示The temporary upload location xxx is not valid

    原文: 一灰灰Blog之Spring系列教程文件上传异常原理分析 SpringBoot搭建的应用,一直工作得好好的,突然发现上传文件失败,提示org.springframework.web.multi ...

  4. php-7.1.11-64位

    php-7.1.11-Win32-VC14-x64.zip 链接:https://pan.baidu.com/s/1w8-fJo8-oWrriHyWpU5Fpg 提取码:bd0e 复制这段内容后打开百 ...

  5. oracle的多表合并查询-工作心得

    本随笔文章,由个人博客(鸟不拉屎)转移至博客园 发布时间: 2018 年 11 月 29 日 原地址:https://niaobulashi.com/archives/oracle-select-al ...

  6. 在CentOS7上部署PostgreSQL11数据库系统

    在数据库上的选择,也是基于了稳定性为前提.其实选择的范围并不是太大,基本可以选择的范围也就是SQLServer.MySQL.PostgreSQL这三种.SQL Server是微软的商业数据库,无论是性 ...

  7. VS2017+CMake+OpenCV下报错 set OpenCV_FOUND to FALSE

    问题 在 VS 2017 中使用Cmake 管理项目, 使用 opencv 库, 在find package的时候出现能找到 OpenCVConfig.cmake的文件,但是设置 OpenCV_Fou ...

  8. Dingo Api 1.0在laravel5.2中的简单应用

    Dingo Api是为基于laravel的开发提供了一系列工具集,这些工具集可以帮助开发者快速构建API.Dingo Api最新的版本是2.0.0-alpha1,这个版本需要php7.0以上的php版 ...

  9. Daily Scrum10 11.14

    昨天的任务已经完成,但是我们在完成任务的过程中确实遇到了困难.昨天我们发现无法连接sqlserver的时候,给罗杰老师发了邮件.老师也给我们提出了建议,给我们提供了一些参考.所以今天大家都在研究如何解 ...

  10. Chapter 9 软件实现

    软件实现包括代码设计.设计审查.代码编写.代码走查.代码编译和单元测试等活动.程序设计语言有很多,从机器语言到高级语言一直发展.软件编码需要遵循一些规范,JAVA代码有适当的空行,代码行及行内空格.分 ...