windows PE(一)

windows PE(一)

PE指纹

判断是否是PE的标志,如图所示

image-20250723105232354

DOS部分

用VS studio生成一个PE文件用010打开,标出的部分就是DOS部分

image-20250722210947234

可以通过VS studio来看一下结构体的构成,大小为64个字节,其中最关键的有两个,第一个e_magic,这是dos部分的魔数为MZ,第二个e_lfanew,用来寻找PE文件头,其余部分修改均不影响正常运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;

PE文件头

PE文件头的结构体定义如下

1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //PE头标志 4字节
IMAGE_FILE_HEADER FileHeader; //标准PE头 20个字节
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //扩展PE头 32位下224字节(0xE0) 64位下240字节(0xF0)
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

其中标准PE头结构体定义如下

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //运行平台标准码,0代表任意平台,i386:14c, x64:8664 (不可改)
WORD NumberOfSections; //节的数量 (不可改)
DWORD TimeDateStamp; //时间戳 (可改)
DWORD PointerToSymbolTable; //调试相关 (可改)
DWORD NumberOfSymbols; //调试相关 (可改)
WORD SizeOfOptionalHeader; //扩展PE头的大小。32位0xE0,64位0xF0 (不可改)
WORD Characteristics; //文件属性 (不可改)
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

关于文件属性含义如下:

0x0002 可执行文件(Executable)
0x2000 DLL 文件
0x0004 没有 COFF 符号表
0x0100 32位机器(适用于 x86)
0x0020 可重定位代码

多个标志可以按位或组合(|)。

扩展头结构定义如下

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
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//

WORD Magic; //PE32: 10B PE64: 20B
BYTE MajorLinkerVersion; //链接器的大版本 没用(可改)
BYTE MinorLinkerVersion; //链接器的小版本 没用(可改)
DWORD SizeOfCode; //所有含有代码的区块的大小 编译器填入 没用(可改)
DWORD SizeOfInitializedData; //所有初始化数据区块的大小 编译器填入 没用(可改)
DWORD SizeOfUninitializedData; //所有含未初始化数据区块的大小 编译器填入 没用(可改)
DWORD AddressOfEntryPoint; //程序入口RVA
DWORD BaseOfCode; //代码区块起始RVA
DWORD BaseOfData; //数据区块起始RVA

//
// NT additional fields.
//

DWORD ImageBase; //内存镜像基址(程序默认载入基地址)
DWORD SectionAlignment; //内存中对齐大小
DWORD FileAlignment; //文件中对齐大小(提高程序运行效率)
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage; //内存中整个PE文件的映射的尺寸,可比实际值大,必须是SectionAlignment的整数倍
DWORD SizeOfHeaders; //所有的头加上节表文件对齐之后的值
DWORD CheckSum; //映像校验和,一些系统.dll文件有要求,判断是否被修改
WORD Subsystem;
WORD DllCharacteristics; //文件特性,不是针对DLL文件的,16进制转换2进制可以根据属性对应的表格得到相应的属性
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录表,结构体数组
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

节表

节表的结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define IMAGE_SIZEOF_SHORT_NAME              8

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;

了解了节表的结构体之后就能进行FA和RVA地址的相互转换

RVA ↔ FA 转换公式

要在RVA 和 FA 之间转换,你需要节表(Section Table)中的信息,转换中心思想是找到地址在节中的偏移

  1. 找到 RVA 所在的节:

    1
    RVA >= Section.VirtualAddress && RVA <  Section.VirtualAddress + Section.VirtualSize
  2. 转换公式如下:

  • RVA → FA

    1
    FA = (RVA - Section.VirtualAddress) + Section.PointerToRawData
  • FA → RVA

    1
    RVA = (FA - Section.PointerToRawData) + Section.VirtualAddress