PE文件解析 基础篇
Posted lsgxeva
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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文件
" ); 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文件
" ); 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文件解析 基础篇的主要内容,如果未能解决你的问题,请参考以下文章