«

PE文件详解 - PE头

ljierui 发布于 阅读:94 技术杂谈


1、PE头

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

// PE头
typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature; // PE标识
    IMAGE_FILE_HEADER FileHeader; // 文件头
    IMAGE_OPTIONAL_HEADER32 OptionalHeader; // 扩展头
} IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;

1.1、IMAGE_FILE_HEADER

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine; // 运行平台
    WORD    NumberOfSections; // 区段的数量
    DWORD   TimeDateStamp; // 文件的创建时间
    DWORD   PointerToSymbolTable; // 符号表指针
    DWORD   NumberOfSymbols; // 符号的数量
    WORD    SizeOfOptionalHeader; // 扩展头大小
    WORD    Characteristics; // 文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

1.2、IMAGE_OPTIONAL_HEADER32

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //
    // 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;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    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.3、常用的区段

1.4、VA|RVA|FOA

它们之间的关系:虚拟地址(VA) = 基地址(Image Base)+相对虚拟地址(RVA)

1.5、通过代码读取PE头

#include<stdio.h>
#include<Windows.h>

int main()
{
    FILE* pFile = NULL;
    char* buffer;
    int nFileLength = 0;
    errno_t err = fopen_s(&pFile, "E:\\C_code\\MyDemo\\Debug\\MyDemo.exe", "rb");
    if (err != 0)
    {
        // 处理文件打开错误
        printf("Failed to open file.\n");
        return 1;
    }
    // fseek()设置文件指针的位置。这里将文件指针移动到文件末尾
    // SEEK_END 指定偏移量为0
    fseek(pFile, 0, SEEK_END);
    // ftell() 获取文件指针的当前位置,也就是文件的长度
    nFileLength = ftell(pFile);
    // rewind() 将文件指针重置到文件开头
    rewind(pFile);
    // 存储缓冲区大小
    // 计算方式:文件长度乘以sizeof(char)(sizeof(char)表示char类型的大小,通常是1字节),再加1字节用于存储字符串结束符\0
    int imageLength = nFileLength * sizeof(char) + 1;
    // 动态分配内存 : malloc函数动态分配了一块内存,大小为imageLength字节
    buffer = (char*)malloc(imageLength);
    // memset() :将分配的内存初始化为0。
    memset(buffer, 0, nFileLength * sizeof(char) + 1);
    // fread函数用于从文件中读取数据。这里将文件中的内容读取到之前分配的缓冲区buffer中,每次读取1字节,总共读取imageLength个字节。
    fread(buffer, 1, imageLength, pFile);

    // 读取DOS头
    // 定义指向 PIMAGE_DOS_HEADER的指针 ReadDosHeader
    PIMAGE_DOS_HEADER ReadDosHeader;
    // 因为buffer是char类型,所以要转成指针类型
    // 目的是将缓冲区中的数据解释为IMAGE_DOS_HEADER结构
    ReadDosHeader = (PIMAGE_DOS_HEADER)buffer;

    // 打印指针中的数据
    printf("MS-DOS Info:\n");
    printf("MZ标志位:%x\n", ReadDosHeader->e_magic);
    printf("PE头位置:%x\n", ReadDosHeader->e_lfanew);

    // PE头
    // 要注意你读取的EXE是32位还是64位的,64位要使用 PIMAGE_NT_HEADERS64
    printf("PE Header Info:\n");
    PIMAGE_NT_HEADERS ReadNTHeaders;
    // 因为要跳过DOS头,读取到PE开头,所以要加e_lfanew
    ReadNTHeaders = (PIMAGE_NT_HEADERS)(buffer + ReadDosHeader->e_lfanew);
    printf("PE标志位:%x\n", ReadNTHeaders->Signature);
    // 打印标准PE头里的东西
    printf("运行平台:%x\n", ReadNTHeaders->FileHeader.Machine);
    // 打印拓展PE头里的东西
    printf("ImageBase: %x\n",ReadNTHeaders->OptionalHeader.ImageBase);

    // 释放动态内存
    free(buffer);
    // 关闭文件
    fclose(pFile);
    return 0;

}

PE文件

推荐阅读: