PE文件格式分析基础实战
本篇文章是在对PE文件格式有了一定了解的基础上,对最简单的PE文件进行分析,以此巩固和验证前面学到的知识。借助python的pefile包简化分析实验。其中分析的PE文件是由print(“hello world”);在Devcpp下编译生成的可执行文件。代码如下
#include <stdio.h> int main(){ printf("hello world"); return 0; }
首先放上文章结尾处的PE文件结构结论图示
安装pefile包
pip install pefile
读取pe文件基本信息
import pefile
import os
import string
import shutil# 文件和文件集合的高级操作
pefile_path = "a.exe"
pe = pefile.PE(pefile_path)
print(pe)
DOS头
----------DOS_HEADER----------
[IMAGE_DOS_HEADER]
0x0 0x0 e_magic: 0x5A4D
0x2 0x2 e_cblp: 0x90
0x4 0x4 e_cp: 0x3
0x6 0x6 e_crlc: 0x0
0x8 0x8 e_cparhdr: 0x4
0xA 0xA e_minalloc: 0x0
0xC 0xC e_maxalloc: 0xFFFF
0xE 0xE e_ss: 0x0
0x10 0x10 e_sp: 0xB8
0x12 0x12 e_csum: 0x0
0x14 0x14 e_ip: 0x0
0x16 0x16 e_cs: 0x0
0x18 0x18 e_lfarlc: 0x40
0x1A 0x1A e_ovno: 0x0
0x1C 0x1C e_res:
0x24 0x24 e_oemid: 0x0
0x26 0x26 e_oeminfo: 0x0
0x28 0x28 e_res2:
0x3C 0x3C e_lfanew: 0x80
e_lfanew的值就是NT头(也叫PE头),指向0x80,所以NT头的文件偏移(Offset)就是0x80。从下面的NT头起始地址得以验证。
NT头
----------NT_HEADERS----------
[IMAGE_NT_HEADERS]
0x80 0x0 Signature: 0x4550
----------FILE_HEADER----------
[IMAGE_FILE_HEADER]
0x84 0x0 Machine: 0x8664
0x86 0x2 NumberOfSections: 0x11
0x88 0x4 TimeDateStamp: 0x61CD19BA [Thu Dec 30 02:30:18 2021 UTC]
0x8C 0x8 PointerToSymbolTable: 0x18000
0x90 0xC NumberOfSymbols: 0x5B8
0x94 0x10 SizeOfOptionalHeader: 0xF0
0x96 0x12 Characteristics: 0x27
Flags: IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE, IMAGE_FILE_LINE_NUMS_STRIPPED, IMAGE_FILE_RELOCS_STRIPPED
----------OPTIONAL_HEADER----------
[IMAGE_OPTIONAL_HEADER64]
0x98 0x0 Magic: 0x20B
0x9A 0x2 MajorLinkerVersion: 0x2
0x9B 0x3 MinorLinkerVersion: 0x18
0x9C 0x4 SizeOfCode: 0x1E00
0xA0 0x8 SizeOfInitializedData: 0x1C00
0xA4 0xC SizeOfUninitializedData: 0xC00
0xA8 0x10 AddressOfEntryPoint: 0x1500
0xAC 0x14 BaseOfCode: 0x1000
0xB0 0x18 ImageBase: 0x400000
0xB8 0x20 SectionAlignment: 0x1000
0xBC 0x24 FileAlignment: 0x200
0xC0 0x28 MajorOperatingSystemVersion: 0x4
0xC2 0x2A MinorOperatingSystemVersion: 0x0
0xC4 0x2C MajorImageVersion: 0x0
0xC6 0x2E MinorImageVersion: 0x0
0xC8 0x30 MajorSubsystemVersion: 0x5
0xCA 0x32 MinorSubsystemVersion: 0x2
0xCC 0x34 Reserved1: 0x0
0xD0 0x38 SizeOfImage: 0x22000
0xD4 0x3C SizeOfHeaders: 0x600
0xD8 0x40 CheckSum: 0x2C948
0xDC 0x44 Subsystem: 0x3
0xDE 0x46 DllCharacteristics: 0x0
0xE0 0x48 SizeOfStackReserve: 0x200000
0xE8 0x50 SizeOfStackCommit: 0x1000
0xF0 0x58 SizeOfHeapReserve: 0x100000
0xF8 0x60 SizeOfHeapCommit: 0x1000
0x100 0x68 LoaderFlags: 0x0
0x104 0x6C NumberOfRvaAndSizes: 0x10
DllCharacteristics:
IMAGE_FILE_HEADER.NumberOfSections=0x11
区段(节)数量为17个,后面一定会有17个节头表项(区段表项)
pefile包解析出来的结果是在IMAGE_OPTIONAL_HEADER64
之后没有体现IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
内容,而是直接跟着17个PE Sections
的区段表项。
但是从偏移地址可以看出其中空缺了一部分0x104~0x188,如下所示
从01edit中查看这部分内容,如下所示,就是数据目录表项的内容(16个表项,最后一个为全0).
在python输出的内容后面也能看到
----------Directories----------
[IMAGE_DIRECTORY_ENTRY_EXPORT]
0x108 0x0 VirtualAddress: 0x0
0x10C 0x4 Size: 0x0
[IMAGE_DIRECTORY_ENTRY_IMPORT]
0x110 0x0 VirtualAddress: 0x8000
0x114 0x4 Size: 0x7C8
[IMAGE_DIRECTORY_ENTRY_RESOURCE]
0x118 0x0 VirtualAddress: 0x0
0x11C 0x4 Size: 0x0
[IMAGE_DIRECTORY_ENTRY_EXCEPTION]
0x120 0x0 VirtualAddress: 0x5000
0x124 0x4 Size: 0x234
[IMAGE_DIRECTORY_ENTRY_SECURITY]
0x128 0x0 VirtualAddress: 0x0
0x12C 0x4 Size: 0x0
[IMAGE_DIRECTORY_ENTRY_BASERELOC]
0x130 0x0 VirtualAddress: 0x0
0x134 0x4 Size: 0x0
[IMAGE_DIRECTORY_ENTRY_DEBUG]
0x138 0x0 VirtualAddress: 0x0
0x13C 0x4 Size: 0x0
[IMAGE_DIRECTORY_ENTRY_COPYRIGHT]
0x140 0x0 VirtualAddress: 0x0
0x144 0x4 Size: 0x0
[IMAGE_DIRECTORY_ENTRY_GLOBALPTR]
0x148 0x0 VirtualAddress: 0x0
0x14C 0x4 Size: 0x0
[IMAGE_DIRECTORY_ENTRY_TLS]
0x150 0x0 VirtualAddress: 0xA020
0x154 0x4 Size: 0x28
[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG]
0x158 0x0 VirtualAddress: 0x0
0x15C 0x4 Size: 0x0
[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]
0x160 0x0 VirtualAddress: 0x0
0x164 0x4 Size: 0x0
[IMAGE_DIRECTORY_ENTRY_IAT]
0x168 0x0 VirtualAddress: 0x81EC
0x16C 0x4 Size: 0x1B0
[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT]
0x170 0x0 VirtualAddress: 0x0
0x174 0x4 Size: 0x0
[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR]
0x178 0x0 VirtualAddress: 0x0
0x17C 0x4 Size: 0x0
[IMAGE_DIRECTORY_ENTRY_RESERVED]
0x180 0x0 VirtualAddress: 0x0
0x184 0x4 Size: 0x0
到此得出结论PE文件部分结构
DOS头(其中包括DOS头的MZ标识部分和DOS Stub加载模块部分)后面紧跟着NT头,NT头包含Signature字段、IMAGE_FILE_HEADER结构体、IMAGE_OPTIONAL_HEADER32结构体。
在IMAGE_OPTIONAL_HEADER32结构体中的最后一个元素就是数据目录表(一个结构体数组),每个结构体中描述了数据块起始RVA地址和大小。可以用公式把RVA转成Offset(文件偏移地址)来找到这些数据目录表项中指向的数据块的文件偏移地址,在稍后会进行分析。
下面接着看从0x188处开始的IMAGE_SECTION_HEADER
以第一个区段表项为例
[IMAGE_SECTION_HEADER]
0x188 0x0 Name: .text
0x190 0x8 Misc: 0x1C60
0x190 0x8 Misc_PhysicalAddress: 0x1C60
0x190 0x8 Misc_VirtualSize: 0x1C60
0x194 0xC VirtualAddress: 0x1000
0x198 0x10 SizeOfRawData: 0x1E00
0x19C 0x14 PointerToRawData: 0x600
0x1A0 0x18 PointerToRelocations: 0x0
0x1A4 0x1C PointerToLinenumbers: 0x0
0x1A8 0x20 NumberOfRelocations: 0x0
0x1AA 0x22 NumberOfLinenumbers: 0x0
0x1AC 0x24 Characteristics: 0x60500020
Flags: IMAGE_SCN_ALIGN_1024BYTES, IMAGE_SCN_ALIGN_16BYTES, IMAGE_SCN_ALIGN_1BYTES, IMAGE_SCN_ALIGN_2048BYTES, IMAGE_SCN_ALIGN_256BYTES, IMAGE_SCN_ALIGN_32BYTES, IMAGE_SCN_ALIGN_4096BYTES, IMAGE_SCN_ALIGN_4BYTES, IMAGE_SCN_ALIGN_64BYTES, IMAGE_SCN_ALIGN_8192BYTES, IMAGE_SCN_ALIGN_8BYTES, IMAGE_SCN_ALIGN_MASK, IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ
Entropy: 5.812202 (Min=0.0, Max=8.0)
MD5 hash: 0f72c726b7b38f1ecc59860cf2bb3590
SHA-1 hash: 4f880ea203a0e93c736ed5a381fe99ab1f23c7e7
SHA-256 hash: e1daded1b379cb1595af17e00cdee8b1ee9daad1ced4d866cc776f02d7aeafff
SHA-512 hash: 098c76066bd5f70bba7a897aad1e7c913b5f923c73602575f30ca9b3b30575fa3aca47455b418291550c4092b25fd3090fd62107ee9f935c4502b0ca86e80deb
PointerToRawData=0x600,SizeOfRawData=0x1E00
.text节的文件偏移地址为0x600,大小为0x1E00
最后一个区段表项内容如下
[IMAGE_SECTION_HEADER]
0x408 0x0 Name: /92
0x410 0x8 Misc: 0x520
0x410 0x8 Misc_PhysicalAddress: 0x520
0x410 0x8 Misc_VirtualSize: 0x520
0x414 0xC VirtualAddress: 0x21000
0x418 0x10 SizeOfRawData: 0x600
0x41C 0x14 PointerToRawData: 0x17A00
0x420 0x18 PointerToRelocations: 0x0
0x424 0x1C PointerToLinenumbers: 0x0
0x428 0x20 NumberOfRelocations: 0x0
0x42A 0x22 NumberOfLinenumbers: 0x0
0x42C 0x24 Characteristics: 0x42100040
Flags: IMAGE_SCN_ALIGN_1024BYTES, IMAGE_SCN_ALIGN_16BYTES, IMAGE_SCN_ALIGN_1BYTES, IMAGE_SCN_ALIGN_256BYTES, IMAGE_SCN_ALIGN_4096BYTES, IMAGE_SCN_ALIGN_4BYTES, IMAGE_SCN_ALIGN_64BYTES, IMAGE_SCN_ALIGN_MASK, IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_DISCARDABLE, IMAGE_SCN_MEM_READ
Entropy: 1.384952 (Min=0.0, Max=8.0)
MD5 hash: c792483200c7bd47e0810b5db34918c1
SHA-1 hash: 60c426bd47d1e32908dec26c7f3e584e9108e784
SHA-256 hash: 079531fc13743c7eaeed23f8af25731b120eeab425dcea38573d3316aa7ab3ed
SHA-512 hash: 5c290766cef7422600372eb50f6b82d2b58a8ca0aab90767e0dc4140e70ea5c1b063a30624864cdc6544c2f7316bff69db686cfb118111733b5c85db2ec722c8
起始地址为0x408,一个[IMAGE_SECTION_HEADER]占40个字节,得到区段头表(节头表)结束位置在0x430-0x1的地方,0x430开始就是其他的内容。
但是0x430~0x600地址范围内的内容全0,如下所示
观察IMAGE_OPTIONAL_HEADER64中FileAlignment=0x200,即对齐为0x200,所以这里是因为对齐方式的原因才会出现全0,并且从0x600开始就是第一个区段表项中指向的数据在文件中的偏移地址。
到此得出结论节头表(区段头表)后面紧跟着的就是节的具体信息,但是会遵从对齐规则
并且第一个区段从0x600开始,占大小为0x1e00,结束位置在0x2400-0x01处,从0x2400开始正好就是第二个区段表项中指向的文件偏移地址。
而最后一个区段表项结束位置在0x17A00+0x600-0x1处。从0x18000再往后就是其他的内容。
下面再看一下上面提到过的数据目录表,如下所示
没有从文件向外导出任何内容,所以导出表内容全空。有导入内容,虚拟地址为0x8000,下面先转换成文件偏移地址。
用LordPE打开查看这个PE文件的区段信息,如下所示
得到导入表的文件偏移地址为0x3400,并且可以看到它是.idata节(区段)。
那就再回到刚才的17个区段表项去寻找,果然找到相关信息,如下所示
这个节的起始地址正好也是0x3400,这就证实了这个导入表的数据被当作了一个节(区段)。
再回去看0x18000开始的位置是什么
PointerToSymbolTable指向COFF符号表偏移指针,可以看到这是符号表的开始位置。
COFF符号表是一个由记录组成的数组,每个记录长18个字节。定义如下
typedef struct _IMAGE_SYMBOL
{
union
{
BYTE ShortName[8]; //8字节名称
struct
{
DWORD Short; //过长时为0
DWORD Long; //偏移量
}Name;
PBYTE LongName[2]; //两个指针,其中第二个是指向过长的节名称的,第一个为0(union)
}N;
DWORD Value; //与符号相关的值
SHORT SectionNumber; //节表的索引,以标识定义这个符号的节
WORD Type; //一个表示类型的数字
BYTE StorageClass; //一个标识储存类别的枚举类型值
BYTE NumberOfAuxSymbol;//在最后一个记录后的辅助符号表数量
}IMAGE_SYMBOL;
那么COFF符号表位置为0x18000~0x1e6f0。
再往后就是很多可打印的字符串组成的内容,如下所示
根据PE文件格式,猜测后面的内容都是附加的一些数据了。
最后根据以上实验结果,结合网上查询到的PE文件格式,画出一个更精细的PE文件格式图示,
如下所示
- 点赞
- 收藏
- 关注作者
评论(0)