近期发现了一个超牛逼的大佬,和大佬写的超详细的pe总结,所以就根据(抄袭)大佬文章做一些pe总结来巩固学习。

先挂上大佬文章链接:https://www.52pojie.cn/forum.php?mod=viewthread&tid=1391994&page=1&extra=#37411342_pe%E5%9C%A8c%E4%B8%AD%E7%9A%84%E5%AE%9A%E4%B9%89

可执行文件的格式

Windows平台

PE(Portable Executable)文件结构

Linux平台

ELF(Executable and Linking Format)文件结构


常见的可执行文件

.exe .dll .sys都是windows下常见的可执行文件。(他们都遵循pe文件结构大二的格式)


常见的非可执行文件
  • .txt .png .mp4等等都是非可执行文件,它们都需要使用其它可执行文件的软件进行加载
  • .txt可以使用Notepad、UltraEdit等 文本 工具查看
  • .png可以使用PhotoShop等 图片 工具查看
  • .mp4可以使用PotPlayer等 播放器 工具查看

为什么要学习pe
  • pe是windows下可执行文件必须遵循的规范
  • 对软件的加壳与脱壳都基于pe
  • EXE文件如何加载到内存中也涉及PE的知识
  • 一个合格的逆向人员,必须熟悉PE

如何识别pe文件

不管是.exe .dll还是.sys ,他们都是pe文件,所以他们的前两个字节一定都是4D 5A(ASCII码为MZ)

我们用winhex打开三种文件观察一下(这里直接没用大佬的三张图)

exe文件

dll文件

sys文件


3Ch位置的数据(不做总结了)

小总结

如果一个文件,它的头两个字节为4D 5A(ASCII码为MZ),并且通过3Ch位置的数据再找到的位置里的数据为PE则基本可以断定这个文件是Windows下的可执行文件(满足PE结构)


反例

随便拉一个图片png文件进来看看

image-20210315134549954

很显然,开头的两个字节就已经表明它不是一个PE文件了

PS:识别可执行文件不能通过文件的后缀名来判断,而应该采用上述的方式进行判断,因为后缀名是可以改的。


通过前面列举的知识,我们已经知道如何识别一个pe文件了,但是为什么是查看一个文件的前2个字节和3Ch的位置,以及其它位置数据作用呢。

这便是PE结构所规定

下面是PE文件的总体结构

pe文件结构


可以看到先前判断PE文件特征里的头两个字节对应这里的文件头:DOS ‘MZ’ HEADER

后面根据3Ch得到的50 45(对应ACSII码为PE)对应这里的PE文件头中的”PE”


(接下来是重点)


pe在c中的定义

pe文件结构自然也是一种数据结构(比较复杂的数据结构)

在c语言中的winnt.h这个头文件中定义了pe文件结构相关的结构体。

所以我们可以直接通过c语言中的pe的定义来更好的学习pe

(直接cv大佬)

随便创建一个空的控制台项目,然后引入winnt.h这个头文件

#include<winnt.h>
int main(int argc, char* argv[])
{
return 0;
}

如下图

image-20210612103747813

然后在引入的头文件处右键转到文档

image-20210612103935331

image-20210612104002548

接下来大佬就会根据这个头文件来学习pe文件的总体结构了


DOS部首

该部分结构对应winnt.h中的**_IMAGE_DOS_HEADER**结构体

可以在先前打开的winnt.h中Ctrl+F搜索_IMAGE_DOS_HEADER

查找后得到

image-20210612104222424

这里贴上大佬提出来的字段

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;

这里我们只需要关注这两个字段就好了

WORD   e_magic                *    "MZ标记" 用于判断是否为可执行文件.
DWORD e_lfanew; * PE头相对于文件的偏移,用于定位PE文件

PE文件头(NT头)

该部分结构对应winnt.h中的**_IMAGE_NT_HEADERS**结构体

使用同样的方法得到对应的代码:

 复制代码 隐藏代码typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

可以看到PE文件头对应的结构体中还包含了其它结构体,这里依旧只介绍大体作用,细节留作之后的笔记

这里的PE文件头相对于先前的DOS部首则是给Windows使用的


块表(节表)

该部分结构对应winnt.h中的**_IMAGE_SECTION_HEADER**结构体

使用同样的方法得到对应的代码:

 复制代码 隐藏代码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;

块表主要用来表示当前文件一共分为几个部分,和后面的块相对应

块表决定了后面的块,每一块从哪里开始,里面存储的数据是什么等等


块部分是由前面的块表决定的,是具体的存储数据的部分

image-20210315144652675


总结

image-20210315195059766

PE