PE文件结构

PE 文件概述

PE(Portable Executable)是 Windows 系统中可执行文件的标准格式,包括:

  • EXE(可执行文件)
  • DLL(动态链接库)
  • SYS(系统驱动)
  • OCX(ActiveX 控件)

文件结构

1. DOS 头(DOS Header)

1
2
3
4
5
typedef struct _IMAGE_DOS_HEADER {
    WORD e_magic;    // DOS签名 "MZ"
    // ... 其他DOS头字段
    LONG e_lfanew;   // PE头的偏移
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

特点:

  • 保持与 DOS 程序兼容
  • e_magic 必须为 “MZ”
  • e_lfanew 指向 PE 头

2. PE 头(PE Header)

1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;                 // PE签名 "PE\0\0"
    IMAGE_FILE_HEADER FileHeader;    // 文件头
    IMAGE_OPTIONAL_HEADER64 OptionalHeader; // 可选头
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

2.1 文件头(File Header)

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_FILE_HEADER {
    WORD Machine;              // 目标CPU类型
    WORD NumberOfSections;     // 节数量
    DWORD TimeDateStamp;       // 创建时间戳
    DWORD PointerToSymbolTable; // 符号表指针
    DWORD NumberOfSymbols;     // 符号数量
    WORD SizeOfOptionalHeader; // 可选头大小
    WORD Characteristics;      // 文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

2.2 可选头(Optional Header)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
typedef struct _IMAGE_OPTIONAL_HEADER64 {
    WORD Magic;                        // 魔数
    BYTE MajorLinkerVersion;          // 链接器版本
    BYTE MinorLinkerVersion;
    DWORD SizeOfCode;                 // 代码大小
    DWORD SizeOfInitializedData;      // 已初始化数据大小
    DWORD SizeOfUninitializedData;    // 未初始化数据大小
    DWORD AddressOfEntryPoint;        // 入口点RVA
    DWORD BaseOfCode;                 // 代码基址
    ULONGLONG ImageBase;              // 默认加载基址
    DWORD SectionAlignment;           // 节对齐
    DWORD FileAlignment;              // 文件对齐
    // ... 其他字段
    IMAGE_DATA_DIRECTORY DataDirectory[16]; // 数据目录
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

3. 节表(Section Table)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
typedef struct _IMAGE_SECTION_HEADER {
    BYTE Name[8];              // 节名称
    union {
        DWORD PhysicalAddress;
        DWORD VirtualSize;     // 虚拟大小
    } Misc;
    DWORD VirtualAddress;      // 虚拟地址(RVA)
    DWORD SizeOfRawData;       // 原始大小
    DWORD PointerToRawData;    // 文件偏移
    DWORD PointerToRelocations;
    DWORD PointerToLinenumbers;
    WORD NumberOfRelocations;
    WORD NumberOfLinenumbers;
    DWORD Characteristics;     // 节属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

常见节:

  • .text:代码节
  • .data:数据节
  • .rdata:只读数据
  • .idata:导入表
  • .edata:导出表
  • .reloc:重定位信息

重要数据目录

1. 导入表(Import Directory)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD Characteristics;
        DWORD OriginalFirstThunk; // INT
    };
    DWORD TimeDateStamp;
    DWORD ForwarderChain;
    DWORD Name;                   // DLL名称
    DWORD FirstThunk;            // IAT
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;

导入函数信息:

1
2
3
4
5
6
7
8
typedef struct _IMAGE_THUNK_DATA64 {
    union {
        ULONGLONG ForwarderString;
        ULONGLONG Function;      // 函数地址
        ULONGLONG Ordinal;       // 序号
        ULONGLONG AddressOfData; // 函数名称
    } u1;
} IMAGE_THUNK_DATA64, *PIMAGE_THUNK_DATA64;

2. 导出表(Export Directory)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD Characteristics;
    DWORD TimeDateStamp;
    WORD MajorVersion;
    WORD MinorVersion;
    DWORD Name;                    // DLL名称
    DWORD Base;                    // 序号基数
    DWORD NumberOfFunctions;       // 函数数量
    DWORD NumberOfNames;          // 名称数量
    DWORD AddressOfFunctions;     // 函数地址表
    DWORD AddressOfNames;         // 函数名称表
    DWORD AddressOfNameOrdinals;  // 序号表
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

3. 重定位表(Relocation Directory)

1
2
3
4
5
typedef struct _IMAGE_BASE_RELOCATION {
    DWORD VirtualAddress;    // 页面RVA
    DWORD SizeOfBlock;       // 块大小
    // WORD TypeOffset[1];   // 重定位项数组
} IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;

加载过程

  1. 映射文件

    • 创建节映射
    • 设置页面保护
  2. 重定位处理

    • 计算基址差值
    • 修正地址引用
  3. 导入表处理

    • 加载依赖 DLL
    • 解析函数地址
  4. 初始化

    • TLS 回调
    • 入口点执行

分析技巧

1. 文件分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 解析DOS头
dos_header = read_struct(file, 0, IMAGE_DOS_HEADER)
if dos_header.e_magic != 0x5A4D:  # "MZ"
    raise Exception("Invalid DOS signature")

# 解析PE头
pe_offset = dos_header.e_lfanew
nt_headers = read_struct(file, pe_offset, IMAGE_NT_HEADERS64)
if nt_headers.Signature != 0x4550:  # "PE\0\0"
    raise Exception("Invalid PE signature")

2. 节分析

1
2
3
4
5
6
# 遍历节表
section_offset = pe_offset + sizeof(IMAGE_NT_HEADERS64)
for i in range(nt_headers.FileHeader.NumberOfSections):
    section = read_struct(file, section_offset, IMAGE_SECTION_HEADER)
    # 分析节属性和内容
    section_offset += sizeof(IMAGE_SECTION_HEADER)

3. RVA转换

1
2
3
4
5
6
def rva_to_raw(rva, sections):
    for section in sections:
        if (rva >= section.VirtualAddress and
            rva < section.VirtualAddress + section.VirtualSize):
            return rva - section.VirtualAddress + section.PointerToRawData
    return None

安全特性

1. ASLR

1
2
// 支持ASLR的DLL特征
#define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040

2. DEP

1
2
// 支持DEP的DLL特征
#define IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100

3. 完整性检查

1
2
3
4
// 检查节校验和
DWORD calculate_checksum(BYTE* data, DWORD size) {
    // 实现校验和算法
}

调试技巧

  1. 断点设置

    • 入口点断点
    • TLS回调断点
    • 导入函数断点
  2. 内存保护

    • 设置页面保护
    • 监控内存访问
  3. API监控

    • IAT钩子
    • 内联钩子

工具使用

  1. PE查看器

    • CFF Explorer
    • PE Bear
    • Resource Hacker
  2. 调试器

    • x64dbg
    • WinDbg
    • IDA Pro
  3. 分析工具

    • Process Monitor
    • API Monitor
    • Dependencies

57.12k 字
43篇文章