滴水逆向三期 手动解析PE头

发布时间 2023-07-24 15:16:27作者: Mast丶轩

PE文件结构

PE文件由PE头,NT头(由标准PE头(固定20字节)和可选PE头(不固定大小)组成),节表以及节区部分组成。具体图示如下(转载):

下面将该图片PE头,NT头(由标准PE头(固定20字节)和可选PE头(不固定大小)组成),节表以及节区部分进行剖析。

DOS头

typedef struct IMAGE_DOS_HEADER
{
+0h WORD e_magic //DOS可执行文件标记,若其所存值为MZ(4Dh 5Ah),则是可执行文件
+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文件头(即从DOS头起始处向后数这么多个字节处为真正的PE文件开始的地方)
}IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

在DOS头中需要特别记忆上述标红的两个字段,e_magic是一种标记,如果值为MZ(4Dh 5Ah),则是可执行文件;而e_lfanew则是一个RVA(即从DOS头起始处向后数这么多个字节处为真正的PE文件开始的地方),它指向的是PE文件真正开始的地方即(NT头开始的地方)。此处由记事本(NOTEPAD)进行演示。此处可以看到从开头即可看到e_magic字段为十六进制的4D 5A,值为MZ,在0x3C的位置看到 e_lfanew字段为00 01 00 00,由于大小端序的问题,需要从后往前读,即NT头开始的位置指向00000100H的位置。在40H到100H之中的位置所存储的数据这部分数据没有实际的作用,Windows加载器会根据 e_lfanew字段的值跳过DOS头,直接解析NT头并加载可执行文件(纯属个人理解,大佬勿喷),在0100H处看到的第一个字段的值为PE,即NT头的标志,验证成功。

NT头

typedef struct IMAGE_NT_HEADERS
{
+0h DWORD Signature
+4h IMAGE_FILE_HEADER FileHeader //标准PE头
+18h IMAGE_OPTIONAL_HEADER32 OptionalHeader //可选PE头
}IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;

NT头只包含了三个成员,其中FileHeader是标准PE头,占20个字节。OptionalHeader是可选PE头,大小不确定。

标准PE头

0x00 WORD Machine; //运行平台(CPU型号,值为14C(4C 01)即表示是386及后续处理器,为0则是任何处理器)
0x02 WORD NumberOfSections; //文件存在节的总数,若要新增节或者合并节则需要修改这个值
0x04 DWORD TimeDataStamp;//文件创建时间,是从1970年至今的秒数,由编译器填写
0x08 DWORD PointerToSymbolicTable;
0x0C DWORD NumberOfSymbols;
0x10 WORD SizeOfOptionalHeader;//可选PE头的大小,32位PE文件默认为0XE0,64位PE文件默认为0XF0,大小可自定义
0x12 WORD Characteristics;//文件的属性值,由16位值为0或1组成,每个位代表的属性不一样

NumberOfSections记录了该PE文件所存在的节的总数,若后续涉及新增节或者合并节之类的操作就需要修改这个值。
Characteristics的大小有16位,每个位都有不同的含义,用来定义PE文件的属性:

  1. IMAGE_FILE_RELOCS_STRIPPED (0x0001):表示文件中没有重定位信息,意味着可执行文件不能在内存中重定位。

  2. IMAGE_FILE_EXECUTABLE_IMAGE (0x0002):表示文件是一个可执行文件。

  3. IMAGE_FILE_LINE_NUMS_STRIPPED (0x0004):表示文件中没有调试和符号信息的行号。

  4. IMAGE_FILE_LOCAL_SYMS_STRIPPED (0x0008):表示文件中没有调试和符号信息的本地符号记录。

  5. IMAGE_FILE_AGGRESIVE_WS_TRIM (0x0010):表示可执行文件应该进行工作集的最小化。

  6. IMAGE_FILE_LARGE_ADDRESS_AWARE (0x0020):表示可执行文件支持处理器的大内存地址,可以超过2GB的物理内存。

  7. IMAGE_FILE_BYTES_REVERSED_LO (0x0080):表示可执行文件的低位字节顺序反转。

  8. IMAGE_FILE_32BIT_MACHINE (0x0100):表示可执行文件是为32位平台编译的。

  9. IMAGE_FILE_DEBUG_STRIPPED (0x0200):表示文件中没有调试信息。

  10. IMAGE_FILE_SYSTEM (0x1000):表示文件是一个系统文件,它是操作系统的一部分。

  11. IMAGE_FILE_DLL (0x2000):表示文件是一个动态链接库(DLL)。

  12. IMAGE_FILE_UP_SYSTEM_ONLY (0x4000):表示文件只能在单一处理器系统上运行。

PE文件的属性有上述所定义,可能为多个属性叠加,如:0x6000为第11和第12个属性所相加,包含此两点的属性。

可选PE头

 

typedef struct _IMAGE_OPTIONAL_HEADER
{
//
// Standard fields
//
+18h WORD Magic; // 标志字,32位普通可执行文件(010Bh),64位普通可执行文件(020Bh)
+1Ah BYTE MajorLinkerVersion;
+1Bh BYTE MinorLinkerVersion;
+1Ch DWORD SizeOfCode; // 所有含代码的节的总大小,必须是FileAlignment(文件对齐)的整数倍,编译器填写,没用
+20h DWORD SizeOfInitializedData; // 所有含已初始化数据的节的总大小,必须是FileAlignment(文件对齐)的整数倍,编译器填写,没用
+24h DWORD SizeOfUninitializedData; // 所有含未初始化数据的节的大小,必须是FileAlignment(文件对齐)的整数倍,编译器填写,没用
+28h DWORD AddressOfEntryPoint; // 程序执行入口(RVA)
+2Ch DWORD BaseOfCode; // 代码的区块的起始基址(RVA),编译器填写,没用
+30h DWORD BaseOfData; // 数据的区块的起始基址(RVA),编译器填写,没用
//
// NT additional fields. 以下是属于NT结构增加的领域
//
+34h DWORD ImageBase; // 程序的优先装载地址
+38h DWORD SectionAlignment; // 内存对齐(1000h)
+3Ch DWORD FileAlignment; // 文件对齐(200h(旧)1000(新))
+40h WORD MajorOperatingSystemVersion;
+42h WORD MinorOperatingSystemVersion;
+44h WORD MajorImageVersion;
+46h WORD MinorImageVersion;
+48h WORD MajorSubsystemVersion;
+4Ah WORD MinorSubsystemVersion;
+4Ch DWORD Win32VersionValue;
+50h DWORD SizeOfImage; // 映像装入内存后的总尺寸,可以比实际的尺寸大,但必须是SectionAlignment的整数倍
+54h DWORD SizeOfHeaders; // 所有头 + 区块表的尺寸大小 严格按照FileAlignment对齐
+58h DWORD CheckSum; // 映像的校检和,用来检测文件是否被修改,可修改值
+5Ch WORD Subsystem;
+5Eh WORD DllCharacteristics;
+60h DWORD SizeOfStackReserve; // 初始化时的栈大小
+64h DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小
+68h DWORD SizeOfHeapReserve; // 初始化时保留的堆大小
+6Ch DWORD SizeOfHeapCommit; // 初始化时实际提交的堆大小
+70h DWORD LoaderFlags;
+74h DWORD NumberOfRvaAndSizes; // 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16
+78h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

 

 AddressOfEntryPoint是程序执行的入口地址;ImageBase是进程的基址,假设它的值为400000h,则PE文件会被装载到这个地址处(RVA);NumberOfRvaAndSizes定义了数据目录表的项数,一直是16。