首页
社区
课程
招聘
[原创]PE文件解析 系列文章(一)
发表于: 2018-10-3 15:25 16684

[原创]PE文件解析 系列文章(一)

2018-10-3 15:25
16684
  • 之前学习了PE格式,为了更好的理解,决定写一个类似LoadPE的小工具。
  • 编译器是VS2015,采用MFC框架。
  • 此系列文章采用边介绍知识点,边写代码的形式,以免变的无聊丧失兴趣。
  • PE知识请参照《加密与解密》第10章
文章有错误或则不清楚的地方还请您指出。
文章有错误或则不清楚的地方还请您指出。


  • 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)
的偏移量,所以你可以发现前三个概念的关系 :  虚拟地址(VA)=   基地址+ 相对虚拟地址(RVA)
       文件偏移地址(FOA):当PE文件储存在某个磁盘当中的时候,某个数据的位置相对于文件头的偏移量。
       入口点(OEP):首先明确一个概念就是OEP是一个RVA,,然后使用 OEP + Imagebase == 入口点的VA,通常情况下,OEP指向的不是main函数。


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



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

2.DOS头部
每个PE文件都是以DOS头开始的,IMAGE_DOS_HEADER 结构如下所示
(最左边是文件头的偏移量。) 
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


(最左边是文件头的偏移量。) 
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文件





IMAGE_NT_HEADERS STRUCT  结构体
IMAGE_NT_HEADERS STRUCT 
{
+0h       DWORD    Signature  
+4h       IMAGE_FILE_HEADER    FileHeader 
+18h      IMAGE_OPTIONAL_HEADER32   OptionalHeader   
} IMAGE_NT_HEADERS ENDS

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“。如上图所示。  
在一个PE文件中Signature字段被设置为4550h,ASCII码为”PE00“。如上图所示。  


  • IMAGE_FILE_HEADER  结构体
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;//文件的属性值
}


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 结构体
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;

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;


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2019-1-24 13:21 被admin编辑 ,原因:
上传的附件:
收藏
免费 7
支持
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  junkboy   +1.00 2018/10/03
最新回复 (14)
雪    币: 11716
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
支持
2018-10-3 16:10
0
雪    币: 2305
活跃值: (332)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
3
junkboy 支持
感谢
2018-10-3 18:46
0
雪    币: 3359
活跃值: (10992)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
4
赞,我之前学习完elf格式,也想按自己想法写个解析程序的,先忽略不同系统、不同CPU的区别,但不会界面,没有完成就停了
2018-10-3 21:21
0
雪    币: 775
活跃值: (2292)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
5
EntryPoint 就叫EP,OEP是Original EntryPoint
2018-10-3 21:32
0
雪    币: 2305
活跃值: (332)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
6
AperOdry EntryPoint 就叫EP,OEP是Original EntryPoint
您说的是AddressOfEntryPoint么
2018-10-4 08:19
0
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
贼鸡儿帅
2018-10-4 14:23
0
雪    币: 2305
活跃值: (332)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
8
xinpoo 赞,我之前学习完elf格式,也想按自己想法写个解析程序的,先忽略不同系统、不同CPU的区别,但不会界面,没有完成就停了
我也是刚学 很吃力
2018-10-4 20:11
0
雪    币: 7415
活跃值: (1524)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
感谢
2018-10-5 07:31
0
雪    币: 175
活跃值: (131)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
支持~
2018-10-8 10:28
0
雪    币: 1522
活跃值: (33)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
支持~
2018-10-21 10:04
0
雪    币: 1
活跃值: (1385)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
支持~ 
2018-12-26 23:46
0
雪    币: 729
活跃值: (388)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
2019-1-11 01:34
0
雪    币: 5
活跃值: (276)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
厉害厉害 学习了
2019-4-22 11:31
0
雪    币: 1378
活跃值: (3067)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
good~
2019-10-14 16:37
0
游客
登录 | 注册 方可回帖
返回
//