Windows PE结构

2、PE文件头解析

1、课程目标

  1. 深入理解DOS头结构
  2. 掌握PE文件头(File Header)的各个字段
  3. 掌握可选头(Optional Header)的各个字段
  4. 理解不同字段对程序执行的影响

2、名词解释

名词 全称 解释
DOS Header DOS头 PE文件开头的64字节
e_lfanew - 指向PE签名的偏移
File Header 文件头 描述文件基本属性
Optional Header 可选头 包含加载和运行时信息
Machine 机器类型 目标CPU架构
Characteristics 特征 文件属性标志
Magic 魔数 区分PE32和PE32+
EntryPoint 入口点 程序执行起始地址

3、使用工具

  • CFF Explorer
  • PE-bear
  • 010 Editor
  • x64dbg

4、技术原理

4.1、DOS头结构

typedef struct _IMAGE_DOS_HEADER {
    WORD   e_magic;         // MZ标识 (0x5A4D)
    WORD   e_cblp;          // 最后一页的字节数
    WORD   e_cp;            // 文件页数
    WORD   e_crlc;          // 重定位项数
    WORD   e_cparhdr;       // 头部大小(段落数)
    WORD   e_minalloc;      // 最小额外段落数
    WORD   e_maxalloc;      // 最大额外段落数
    WORD   e_ss;            // 初始SS值
    WORD   e_sp;            // 初始SP值
    WORD   e_csum;          // 校验和
    WORD   e_ip;            // 初始IP值
    WORD   e_cs;            // 初始CS值
    WORD   e_lfarlc;        // 重定位表偏移
    WORD   e_ovno;          // 覆盖号
    WORD   e_res[4];        // 保留
    WORD   e_oemid;         // OEM标识
    WORD   e_oeminfo;       // OEM信息
    WORD   e_res2[10];      // 保留
    LONG   e_lfanew;        // PE头偏移 ★重要★
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

4.2、文件头结构

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;

// Machine类型
#define IMAGE_FILE_MACHINE_I386     0x014c  // x86
#define IMAGE_FILE_MACHINE_AMD64    0x8664  // x64
#define IMAGE_FILE_MACHINE_ARM      0x01c0  // ARM
#define IMAGE_FILE_MACHINE_ARM64    0xAA64  // ARM64

// Characteristics标志
#define IMAGE_FILE_RELOCS_STRIPPED         0x0001  // 无重定位
#define IMAGE_FILE_EXECUTABLE_IMAGE        0x0002  // 可执行
#define IMAGE_FILE_LINE_NUMS_STRIPPED      0x0004  // 无行号
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED     0x0008  // 无本地符号
#define IMAGE_FILE_LARGE_ADDRESS_AWARE     0x0020  // 大地址感知
#define IMAGE_FILE_32BIT_MACHINE           0x0100  // 32位机器
#define IMAGE_FILE_DEBUG_STRIPPED          0x0200  // 无调试信息
#define IMAGE_FILE_DLL                     0x2000  // DLL文件

4.3、可选头结构(PE32)

typedef struct _IMAGE_OPTIONAL_HEADER32 {
    // 标准字段
    WORD    Magic;                    // 0x10B=PE32, 0x20B=PE32+
    BYTE    MajorLinkerVersion;       // 链接器主版本
    BYTE    MinorLinkerVersion;       // 链接器次版本
    DWORD   SizeOfCode;               // 代码段大小
    DWORD   SizeOfInitializedData;    // 已初始化数据大小
    DWORD   SizeOfUninitializedData;  // 未初始化数据大小
    DWORD   AddressOfEntryPoint;      // 入口点RVA ★重要★
    DWORD   BaseOfCode;               // 代码段基址
    DWORD   BaseOfData;               // 数据段基址(PE32+无此字段)
    
    // NT扩展字段
    DWORD   ImageBase;                // 建议加载基址 ★重要★
    DWORD   SectionAlignment;         // 内存对齐 ★重要★
    DWORD   FileAlignment;            // 文件对齐 ★重要★
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;        // 保留
    DWORD   SizeOfImage;              // 映像大小 ★重要★
    DWORD   SizeOfHeaders;            // 头部大小 ★重要★
    DWORD   CheckSum;                 // 校验和
    WORD    Subsystem;                // 子系统 ★重要★
    WORD    DllCharacteristics;       // DLL特征 ★重要★
    DWORD   SizeOfStackReserve;       // 栈保留大小
    DWORD   SizeOfStackCommit;        // 栈提交大小
    DWORD   SizeOfHeapReserve;        // 堆保留大小
    DWORD   SizeOfHeapCommit;         // 堆提交大小
    DWORD   LoaderFlags;              // 加载器标志
    DWORD   NumberOfRvaAndSizes;      // 数据目录数量
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32;

5、代码实现

5.1、解析DOS头

#include <windows.h>
#include <stdio.h>

// 解析DOS头
void ParseDosHeader(LPVOID fileData) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    
    printf("\n========== DOS Header ==========\n");
    printf("%-25s 0x%04X", "e_magic:", dosHeader->e_magic);
    printf(" (%c%c)\n", dosHeader->e_magic & 0xFF, (dosHeader->e_magic >> 8) & 0xFF);
    
    printf("%-25s %d\n", "e_cblp:", dosHeader->e_cblp);
    printf("%-25s %d\n", "e_cp:", dosHeader->e_cp);
    printf("%-25s %d\n", "e_crlc:", dosHeader->e_crlc);
    printf("%-25s %d\n", "e_cparhdr:", dosHeader->e_cparhdr);
    printf("%-25s %d\n", "e_minalloc:", dosHeader->e_minalloc);
    printf("%-25s %d\n", "e_maxalloc:", dosHeader->e_maxalloc);
    printf("%-25s 0x%04X\n", "e_ss:", dosHeader->e_ss);
    printf("%-25s 0x%04X\n", "e_sp:", dosHeader->e_sp);
    printf("%-25s 0x%04X\n", "e_csum:", dosHeader->e_csum);
    printf("%-25s 0x%04X\n", "e_ip:", dosHeader->e_ip);
    printf("%-25s 0x%04X\n", "e_cs:", dosHeader->e_cs);
    printf("%-25s 0x%04X\n", "e_lfarlc:", dosHeader->e_lfarlc);
    printf("%-25s %d\n", "e_ovno:", dosHeader->e_ovno);
    printf("%-25s 0x%04X\n", "e_oemid:", dosHeader->e_oemid);
    printf("%-25s 0x%04X\n", "e_oeminfo:", dosHeader->e_oeminfo);
    printf("%-25s 0x%08X\n", "e_lfanew:", dosHeader->e_lfanew);
    
    // DOS Stub
    if (dosHeader->e_lfanew > sizeof(IMAGE_DOS_HEADER)) {
        printf("\n[DOS Stub Message]\n");
        char* stub = (char*)fileData + sizeof(IMAGE_DOS_HEADER);
        int stubSize = dosHeader->e_lfanew - sizeof(IMAGE_DOS_HEADER);
        
        // 查找可打印字符串
        for (int i = 0; i < stubSize; i++) {
            if (stub[i] >= 0x20 && stub[i] < 0x7F) {
                printf("%c", stub[i]);
            } else if (stub[i] == '\r' || stub[i] == '\n') {
                printf("%c", stub[i]);
            }
        }
        printf("\n");
    }
}

5.2、解析文件头

// 获取Machine类型名称
const char* GetMachineName(WORD machine) {
    switch (machine) {
        case IMAGE_FILE_MACHINE_I386:  return "Intel 386 (x86)";
        case IMAGE_FILE_MACHINE_AMD64: return "AMD64 (x64)";
        case IMAGE_FILE_MACHINE_ARM:   return "ARM";
        case IMAGE_FILE_MACHINE_ARMNT: return "ARM Thumb-2";
        case IMAGE_FILE_MACHINE_ARM64: return "ARM64";
        case IMAGE_FILE_MACHINE_IA64:  return "Intel Itanium";
        default: return "Unknown";
    }
}

// 解析Characteristics
void PrintCharacteristics(WORD chars) {
    printf("%-25s 0x%04X\n", "Characteristics:", chars);
    
    struct { WORD flag; const char* name; } flags[] = {
        { IMAGE_FILE_RELOCS_STRIPPED,         "RELOCS_STRIPPED" },
        { IMAGE_FILE_EXECUTABLE_IMAGE,        "EXECUTABLE_IMAGE" },
        { IMAGE_FILE_LINE_NUMS_STRIPPED,      "LINE_NUMS_STRIPPED" },
        { IMAGE_FILE_LOCAL_SYMS_STRIPPED,     "LOCAL_SYMS_STRIPPED" },
        { IMAGE_FILE_AGGRESIVE_WS_TRIM,       "AGGRESIVE_WS_TRIM" },
        { IMAGE_FILE_LARGE_ADDRESS_AWARE,     "LARGE_ADDRESS_AWARE" },
        { IMAGE_FILE_BYTES_REVERSED_LO,       "BYTES_REVERSED_LO" },
        { IMAGE_FILE_32BIT_MACHINE,           "32BIT_MACHINE" },
        { IMAGE_FILE_DEBUG_STRIPPED,          "DEBUG_STRIPPED" },
        { IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP, "REMOVABLE_RUN_FROM_SWAP" },
        { IMAGE_FILE_NET_RUN_FROM_SWAP,       "NET_RUN_FROM_SWAP" },
        { IMAGE_FILE_SYSTEM,                  "SYSTEM" },
        { IMAGE_FILE_DLL,                     "DLL" },
        { IMAGE_FILE_UP_SYSTEM_ONLY,          "UP_SYSTEM_ONLY" },
        { IMAGE_FILE_BYTES_REVERSED_HI,       "BYTES_REVERSED_HI" },
    };
    
    for (int i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) {
        if (chars & flags[i].flag) {
            printf("                          -> %s\n", flags[i].name);
        }
    }
}

// 解析文件头
void ParseFileHeader(LPVOID fileData) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        (PBYTE)fileData + dosHeader->e_lfanew
    );
    
    PIMAGE_FILE_HEADER fileHeader = &ntHeaders->FileHeader;
    
    printf("\n========== File Header ==========\n");
    printf("%-25s 0x%04X (%s)\n", "Machine:", 
           fileHeader->Machine, GetMachineName(fileHeader->Machine));
    printf("%-25s %d\n", "NumberOfSections:", fileHeader->NumberOfSections);
    
    // 解析时间戳
    time_t timestamp = fileHeader->TimeDateStamp;
    struct tm* tm = gmtime(&timestamp);
    char timeStr[64];
    strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S UTC", tm);
    printf("%-25s 0x%08X (%s)\n", "TimeDateStamp:", 
           fileHeader->TimeDateStamp, timeStr);
    
    printf("%-25s 0x%08X\n", "PointerToSymbolTable:", 
           fileHeader->PointerToSymbolTable);
    printf("%-25s %d\n", "NumberOfSymbols:", fileHeader->NumberOfSymbols);
    printf("%-25s %d bytes\n", "SizeOfOptionalHeader:", 
           fileHeader->SizeOfOptionalHeader);
    
    PrintCharacteristics(fileHeader->Characteristics);
}

5.3、解析可选头

// 获取子系统名称
const char* GetSubsystemName(WORD subsystem) {
    switch (subsystem) {
        case IMAGE_SUBSYSTEM_UNKNOWN:          return "Unknown";
        case IMAGE_SUBSYSTEM_NATIVE:           return "Native";
        case IMAGE_SUBSYSTEM_WINDOWS_GUI:      return "Windows GUI";
        case IMAGE_SUBSYSTEM_WINDOWS_CUI:      return "Windows Console";
        case IMAGE_SUBSYSTEM_OS2_CUI:          return "OS/2 Console";
        case IMAGE_SUBSYSTEM_POSIX_CUI:        return "POSIX Console";
        case IMAGE_SUBSYSTEM_WINDOWS_CE_GUI:   return "Windows CE GUI";
        case IMAGE_SUBSYSTEM_EFI_APPLICATION:  return "EFI Application";
        case IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER: return "EFI Boot Service Driver";
        case IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER:      return "EFI Runtime Driver";
        case IMAGE_SUBSYSTEM_EFI_ROM:          return "EFI ROM";
        case IMAGE_SUBSYSTEM_XBOX:             return "Xbox";
        case IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION: return "Windows Boot Application";
        default: return "Unknown";
    }
}

// 解析DLL特征
void PrintDllCharacteristics(WORD chars) {
    printf("%-25s 0x%04X\n", "DllCharacteristics:", chars);
    
    struct { WORD flag; const char* name; } flags[] = {
        { IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA,     "HIGH_ENTROPY_VA" },
        { IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE,        "DYNAMIC_BASE (ASLR)" },
        { IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY,     "FORCE_INTEGRITY" },
        { IMAGE_DLLCHARACTERISTICS_NX_COMPAT,           "NX_COMPAT (DEP)" },
        { IMAGE_DLLCHARACTERISTICS_NO_ISOLATION,        "NO_ISOLATION" },
        { IMAGE_DLLCHARACTERISTICS_NO_SEH,              "NO_SEH" },
        { IMAGE_DLLCHARACTERISTICS_NO_BIND,             "NO_BIND" },
        { IMAGE_DLLCHARACTERISTICS_APPCONTAINER,        "APPCONTAINER" },
        { IMAGE_DLLCHARACTERISTICS_WDM_DRIVER,          "WDM_DRIVER" },
        { IMAGE_DLLCHARACTERISTICS_GUARD_CF,            "GUARD_CF (CFG)" },
        { IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, "TERMINAL_SERVER_AWARE" },
    };
    
    for (int i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) {
        if (chars & flags[i].flag) {
            printf("                          -> %s\n", flags[i].name);
        }
    }
}

// 解析可选头(PE32和PE32+)
void ParseOptionalHeader(LPVOID fileData) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        (PBYTE)fileData + dosHeader->e_lfanew
    );
    
    BOOL is64 = (ntHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
    
    printf("\n========== Optional Header ==========\n");
    
    if (is64) {
        PIMAGE_OPTIONAL_HEADER64 opt = (PIMAGE_OPTIONAL_HEADER64)&ntHeaders->OptionalHeader;
        
        printf("%-25s 0x%04X (PE32+/64-bit)\n", "Magic:", opt->Magic);
        printf("%-25s %d.%d\n", "LinkerVersion:", 
               opt->MajorLinkerVersion, opt->MinorLinkerVersion);
        printf("%-25s 0x%08X\n", "SizeOfCode:", opt->SizeOfCode);
        printf("%-25s 0x%08X\n", "SizeOfInitializedData:", opt->SizeOfInitializedData);
        printf("%-25s 0x%08X\n", "SizeOfUninitializedData:", opt->SizeOfUninitializedData);
        printf("%-25s 0x%08X (RVA)\n", "AddressOfEntryPoint:", opt->AddressOfEntryPoint);
        printf("%-25s 0x%08X\n", "BaseOfCode:", opt->BaseOfCode);
        printf("%-25s 0x%016llX\n", "ImageBase:", opt->ImageBase);
        printf("%-25s 0x%08X (%d bytes)\n", "SectionAlignment:", 
               opt->SectionAlignment, opt->SectionAlignment);
        printf("%-25s 0x%08X (%d bytes)\n", "FileAlignment:", 
               opt->FileAlignment, opt->FileAlignment);
        printf("%-25s %d.%d\n", "OSVersion:", 
               opt->MajorOperatingSystemVersion, opt->MinorOperatingSystemVersion);
        printf("%-25s %d.%d\n", "ImageVersion:", 
               opt->MajorImageVersion, opt->MinorImageVersion);
        printf("%-25s %d.%d\n", "SubsystemVersion:", 
               opt->MajorSubsystemVersion, opt->MinorSubsystemVersion);
        printf("%-25s 0x%08X\n", "SizeOfImage:", opt->SizeOfImage);
        printf("%-25s 0x%08X\n", "SizeOfHeaders:", opt->SizeOfHeaders);
        printf("%-25s 0x%08X\n", "CheckSum:", opt->CheckSum);
        printf("%-25s %d (%s)\n", "Subsystem:", 
               opt->Subsystem, GetSubsystemName(opt->Subsystem));
        
        PrintDllCharacteristics(opt->DllCharacteristics);
        
        printf("%-25s 0x%016llX\n", "SizeOfStackReserve:", opt->SizeOfStackReserve);
        printf("%-25s 0x%016llX\n", "SizeOfStackCommit:", opt->SizeOfStackCommit);
        printf("%-25s 0x%016llX\n", "SizeOfHeapReserve:", opt->SizeOfHeapReserve);
        printf("%-25s 0x%016llX\n", "SizeOfHeapCommit:", opt->SizeOfHeapCommit);
        printf("%-25s %d\n", "NumberOfRvaAndSizes:", opt->NumberOfRvaAndSizes);
    } else {
        PIMAGE_OPTIONAL_HEADER32 opt = (PIMAGE_OPTIONAL_HEADER32)&ntHeaders->OptionalHeader;
        
        printf("%-25s 0x%04X (PE32/32-bit)\n", "Magic:", opt->Magic);
        printf("%-25s %d.%d\n", "LinkerVersion:", 
               opt->MajorLinkerVersion, opt->MinorLinkerVersion);
        printf("%-25s 0x%08X\n", "SizeOfCode:", opt->SizeOfCode);
        printf("%-25s 0x%08X\n", "AddressOfEntryPoint:", opt->AddressOfEntryPoint);
        printf("%-25s 0x%08X\n", "BaseOfCode:", opt->BaseOfCode);
        printf("%-25s 0x%08X\n", "BaseOfData:", opt->BaseOfData);
        printf("%-25s 0x%08X\n", "ImageBase:", opt->ImageBase);
        printf("%-25s 0x%08X\n", "SectionAlignment:", opt->SectionAlignment);
        printf("%-25s 0x%08X\n", "FileAlignment:", opt->FileAlignment);
        printf("%-25s 0x%08X\n", "SizeOfImage:", opt->SizeOfImage);
        printf("%-25s 0x%08X\n", "SizeOfHeaders:", opt->SizeOfHeaders);
        printf("%-25s %d (%s)\n", "Subsystem:", 
               opt->Subsystem, GetSubsystemName(opt->Subsystem));
        
        PrintDllCharacteristics(opt->DllCharacteristics);
    }
}

5.4、修改PE头信息

// 修改入口点
BOOL ModifyEntryPoint(LPVOID fileData, DWORD newEntryPoint) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        (PBYTE)fileData + dosHeader->e_lfanew
    );
    
    printf("[*] 原入口点: 0x%08X\n", 
           ntHeaders->OptionalHeader.AddressOfEntryPoint);
    
    ntHeaders->OptionalHeader.AddressOfEntryPoint = newEntryPoint;
    
    printf("[+] 新入口点: 0x%08X\n", newEntryPoint);
    
    return TRUE;
}

// 修改ImageBase
BOOL ModifyImageBase(LPVOID fileData, ULONGLONG newImageBase) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        (PBYTE)fileData + dosHeader->e_lfanew
    );
    
    BOOL is64 = (ntHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
    
    if (is64) {
        PIMAGE_OPTIONAL_HEADER64 opt = (PIMAGE_OPTIONAL_HEADER64)&ntHeaders->OptionalHeader;
        opt->ImageBase = newImageBase;
    } else {
        PIMAGE_OPTIONAL_HEADER32 opt = (PIMAGE_OPTIONAL_HEADER32)&ntHeaders->OptionalHeader;
        opt->ImageBase = (DWORD)newImageBase;
    }
    
    return TRUE;
}

// 禁用ASLR
BOOL DisableASLR(LPVOID fileData) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        (PBYTE)fileData + dosHeader->e_lfanew
    );
    
    ntHeaders->OptionalHeader.DllCharacteristics &= 
        ~IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE;
    
    printf("[+] ASLR已禁用\n");
    return TRUE;
}

// 禁用DEP
BOOL DisableDEP(LPVOID fileData) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        (PBYTE)fileData + dosHeader->e_lfanew
    );
    
    ntHeaders->OptionalHeader.DllCharacteristics &= 
        ~IMAGE_DLLCHARACTERISTICS_NX_COMPAT;
    
    printf("[+] DEP已禁用\n");
    return TRUE;
}

6、课后作业

  1. 编写完整的PE头解析器

    • 解析所有DOS头字段
    • 解析所有文件头字段
    • 解析所有可选头字段
    • 支持PE32和PE32+
  2. 实现PE头修改工具

    • 修改入口点
    • 修改时间戳
    • 禁用/启用安全特性
  3. 分析PE头与程序行为的关系

    • 修改Subsystem观察程序运行变化
    • 修改Characteristics观察加载行为
    • 研究DllCharacteristics的安全意义