type
status
date
slug
summary
tags
category
icon
password

PE文件结构

PE文件结构示意图:
notion image
 
notion image
 
notion image
 
 
 
 
首先要明白我们使用的exe文件本质是是一些01组成的数据,我们要深入了解这些东西,必须要去了解这些0,1在各个位置处有什么作用
首先先来整理一下所用到的工具: 1、studype:可以看到各个区段的结构
2、olldbg:查看展开到内存里面的布局
3、010editor:查看静态二进制结构
4、PEview:查看PE文件各个部分的具体值
 
下面就用notpad++来举例:
首先先来看第一部分DOS头部分:

DOS_HEADER:

notion image
 
💡
结构体
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE 头 WORD e_magic; // 魔数 WORD e_cblp; // 文件最后一页的字节数 WORD e_cp; // 文件页数 WORD e_crlc; // 重定位条目数 WORD e_cparhdr; // 头部段落数 WORD e_minalloc; // 所需最小附加段落数 WORD e_maxalloc; // 所需最大附加段落数 WORD e_ss; // 初始(相对)SS值 WORD e_sp; // 初始SP值 WORD e_csum; // 校验和 WORD e_ip; // 初始IP值 WORD e_cs; // 初始(相对)CS值 WORD e_lfarlc; // 重定位表的文件地址 WORD e_ovno; // 覆盖号 WORD e_res[4]; // 保留字 WORD e_oemid; // OEM标识符(用于e_oeminfo) WORD e_oeminfo; // OEM信息;特定于e_oemid WORD e_res2[10]; // 保留字 LONG e_lfanew; // 新的exe头的文件地址(PE文件的起始位置) } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
对于上面的重要部分,我也给出了标红处理,可以看到上面的类型来去找相关位置的数据,下面打开010去看看这些值都是什么意思
notion image
可以发现e_magic对应的值是4D5A,也就是MZ,然后e_lfanew 对应的值是00000110
这个其实是一个地址,我们去找他对应的地址是什么,是5045,也就是PE,这个其实是PE文件的标识头,如果开头不是4D5A或者找的地址不是PE,那么系统就认为这个不是一个有效的PE文件,那么就不会去执行这个
 
 

PE文件头(IMAGE_NT_HEADERS)

 
notion image
 
💡
typedef struct _IMAGE_NT_HEADERS64 { DWORD Signature; //PE标识,也就是PE\0\0 IMAGE_FILE_HEADER FileHeader;//标准PE头 IMAGE_OPTIONAL_HEADER64 OptionalHeader;//扩展PE头 } IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
这里的标准PE头和扩展PE头其实是一个结构体,里面有许多的PE结构,里面有着各自的含义
标准PE头有20个字节,这里面包含着PE文件物理分布的信息,比如节数目、可选文件头大小、机器类型,下面来看看PEi奥准文件头:

IMAGE_FILE_HEADER:

 
notion image
💡
typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; //节的数量 DWORD TimeDateStamp; //时间戳,可以修改 DWORD PointerToSymbolTable;//符号表指针 DWORD NumberOfSymbols; //符号表指针数量 WORD SizeOfOptionalHeader;//可选PE头的大小 WORD Characteristics; //文件的标识 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
#define IMAGE_SIZEOF_FILE_HEADER   20
 
详细解释:
💡
Machine: 该字段是WORD类型,占用2字节。该字段表示可执行文件的目标CPU类型。
notion image
NumberOfSections:该字段是WORD类型,占用两个字节。该字段表示PE文件的节区的个数。
TimeDataStamp:该字段表明文件是何时被创建的,这个值是自1970年1月1日以来用格林威治时间计算的秒数。
PointerToSymbolTable:该字段很少被使用,这里不做介绍。 NumberOfSymbols:该字段很少被使用,这里不做介绍。
SizeOfOptionalHeader:该字段为WORD类型,占用两个字节。该字段指定IMAGE_OPTION AL_HEADER结构的大小。注意,在计算IMAGE_OPTIONAL_HEADER的大小时,应该从IMAGE_FILE_HEADER结构中的SizeOfOptionalHeader字段指定的值来获取,而不应该直接使用 sizeof ( IMAGE_OPTIONAL_HEADER )来计算。
Characteristics:该字段为WORD类型,占用2字节。该字段指定文件的类型。
notion image
 
具体如下图所示:
 
接下面我们去010去找这个结构体:
notion image
 
选中的这些就是标准PE头:
我们直接看我们关注的第二个结构NumberOfSections,他的值是0008,也就是说有8个节
我们使用StudyPE去查看PE节的数量
notion image
 
再看可选PE头的大小,00F0,有240个字节的大小,是64位程序
 

IMAGE_OPTIONAL_HEADER:(扩展PE头)

这个结构体并不是像名字那样可有可无,而是里面有的结构是可选的,这个结构体是非常重要的,也是必须要有的,这个结构体共有96个字节和一个结构体
 
 
notion image
 
选中的这些就是可选PE头的部分,可选PE头就在标准PE头的后面,他的结尾也非常好找,紧跟着就是.text的节
接下来就是分析具体的文件结构: 首先来看magic:020B,说明是64位的
AddressOfEntryPoint 指的是PE装载器准备运行的PE文件的第一条筑牢的RVA,这个东西也是病毒所感染的关键字段(RVA:相对虚拟地址,是相对于基址的偏移
ImageBase PE文件的优先装载地址,就是基址
SectionAlignment 内存中节对齐的粒度
FileAlignment 文件中节对齐的粒度
 
AddressOfEntryPoint和ImageBase可以组成程序入口地址
也就是ImageBase+RVA
 
对于64位程序而言,如果你计算的程序入口地址与调试器的不同,不用担心,这是因为64位程序会默认开启地址随机化,这就导致我们计算的他是一个比较小的值,但是调试器上显示是7FFF这些很大的,我们就可以使用PE工具把他关闭,就是DIE工具里面
 
对齐粒度: 可以这么理解,就是每个节的大小不一致,但是我为了每一次访问的时候可以不用动态的去找每一个节的起始位置,那么我就设置一个统一的尺寸,使其每一个节的大小是一样的,这属于空间换时间的意思,所以导致PE文件中会有许多的00 或者CC

结构体数组:

下面来看一下这个数据目录数组,先来看一下他的定义
上面这个就是这个数据目录数组其中一个的数据结构,它是由许多个这样结构的数据结构组成的,每一个都是占8个字节,一个是地址一个是大小下面是这个结构体的全部数据类型
 
一个数据结构有着两个变量,显而易见的就肯定是我们可以和根据这个地址去找到这样的一个区域去找他的含义,这个区域是什么呢?他其实就是前面所提到的表和节这些东东,具体来看一些:
对于上面地这个函数,我们一般都是用RtlImageDirectoryEntryToData()这个函数
 
使用这个函数我们就可以一项项地去找到我们需的这些表

导出表:

 
先来解释一下什么是导出表,导出表就是本身的函数可以供其他程序来使用地函数地集合,一般都是dll文件(也叫动态链接库),
一般而言,exe程序是没有导出表的,因为exe文件大多是自己用的,他会调用dll文件来供他使用
notion image
下面是我找的一个dll文件,我们从下面可以看到它可以按照序号进行查询,也可以根据名字来查询,在上面的表格中,我们可以看到每一个导出表的各个数据项的含义,这些内容也就是导出表的内容,接下来具体来看一下这个导出表的结构
💡
导出表结构…解释:
  1. Characteristics:现在没有用到,一般为0。
  1. TimeDateStamp:导出表生成的时间戳,由连接器生成。
  1. MajorVersion,MinorVersion:看名字是版本,实际貌似没有用,都是0。
  1. Name:模块的名字。
  1. Base:序号的基数,按序号导出函数的序号值从Base开始递增。
  1. NumberOfFunctions:所有导出函数的数量。
  1. NumberOfNames:按名字导出函数的数量。
  1. AddressOfFunctions:一个RVA,指向一个DWORD数组,数组中的每一项是一个导出函数的RVA,顺序与导出序号相同。
  1. AddressOfNames:一个RVA,依然指向一个DWORD数组,数组中的每一项仍然是一个RVA,指向一个表示函数名字。
  1. AddressOfNameOrdinals:一个RVA,还是指向一个WORD数组,数组中的每一项与AddressOfNames中的每一项对应,表示该名字的函数在AddressOfFunctions中的序号。
 
下面通过一个例子来具体来看一下这个扩展PE文件头的结构
就像我们前面说的,先去找那个结构体数组,然后8个字节一个结构体去查看具体的含义
notion image
 
从010里面可以看到这框起来的正好是那个结构体的全部剩余部分,然后它对应着我们上面的那个数据 https://www.notion.so/PE-107d3b7afb384350846a7376bc858fb0?pvs=4#f60f81d1e5b141c5bbf589ed4dacfd83
如果有那他就有地址,如果没有就是00000000
我们还可以从上面的截图中我们还能发现他的对应地址处存放着他的含义,就那第一个举例子,在58f60处有着00000000ffffffff,而这些数据又对应着这些https://www.notion.so/PE-107d3b7afb384350846a7376bc858fb0?pvs=4#ebb462c5670241fbb616dd5325a6afe1
notion image
 

导入表:

下面来介绍一些导入表,导入表是供自己使用的函数,在 PE 文件加载时,会根据这个表里的内容加载依赖的 DLL ( 模块 ),并填充所需函数的地址。
 
 

节区:

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
强网杯2023_dotdot复现IDA疑难杂症
Loading...