PE文件解析
Arcueid 发表于 浙江 二进制安全 164浏览 · 2024-11-23 06:08

PE文件解析

什么是PE文件

Portable Executable文件 是一种可移植可执行文件格式 是windows中 exe(应用程序) dll(动态链接库) sys(驱动)等二进制文件的标准文件格式

PE Struct

通过010editor打开一个exe文件 使用exe模板

可以看见PE文件主要由 Dos头 Dos存根 Nt头 数据目录 节表数据 组成

Dos头

MZ头部

这是为了兼容DOS系统留下来的东西 其中比较重要的有e_magic e_lfanew

标准结构体如下

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;

e_magic 文件开始的前两个字节 系统通过这个字段识别文件格式 固定为5A4D 这是我们判断文件是否是PE文件的一个参考

e_lfanew 指向nt头

00 00 01 00 对应到nt头

Dos Stub

不重要

紧跟在MZ头后面

在Dos环境下运行的程序

运行后输出This program cannot be run in DOS mode.

NT头

由签名 文件头 可选头组成

签名 固定的00004550

文件头

文件头结构如下

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;
数据类型 变量名 解释
WORD Machine 目标平台 只能为几个固定的值 表明运行在什么架构
WORD NumberOfSections 节的数量
DWORD TimeDateStamp 时间戳
DWORD PointerToSymbolTable 符号表指针 通常为0
DWORD NumberOfSymbols 符号数量 通常为0
WORD SizeOfOptionalHeader 可选头大小
WORD Characteristics 文件特性 只能为几个固定的值 表明具体是什么PE文件

可选头

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

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;

比较重要的有

magic 指定是32位还是64位 还是ROM

AddressOfEntryPoint

到程序入口点的RVA

ImageBase

加载到内存中的首选地址 也就是PE文件会被加载到进程的那个地址

SizeOfImage

在内存中加载的总大小 会进行内存对齐

SectionAlignment

内存对齐的大小 默认是0x1000

FileAlignment

文件对齐 默认是0x200

DataDirectory

IMAGE_DATA_DIRECTORY结构体组成的数组

包含如导入表 导出表等的 RVA 和 大小

数据目录

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

保存了 RVA 和 大小

比较重要的有

导入表 列出依赖的dll及其中的函数

导出表 列出向外部提供的函数 一般是dll有

资源表 包含程序的资源数据 如 icon version

节表

实际存放数据的地方

比如常见的 .text 代码段 .data 数据段 .rsrc 资源段 .reloc 重定位表段

节表的数量由文件头中NumberOfSections决定

结构如下

#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;

Name 节的名称

VirtualAddress 节的RVA 节在加载到内存后的位置

Characteristics 描述节的属性 包括是否可读写执行 如何对齐

PointerToRawData 指向节的文件位置

SizeOfRawData 对齐后大小

代码实现

这里不关心FOA 直接读到内存中用VA

1、 加载文件到内存

DWORD FileReadSize;
    char path[] = "C:\\Windows\\System32\\calc.exe";
    HANDLE hFile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    DWORD dwFileSize = GetFileSize(hFile, NULL);
    LPVOID FileImage = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    ReadFile(hFile, FileImage, dwFileSize, &FileReadSize, NULL);
    CloseHandle(hFile);

2、 获取nt头

PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)FileImage;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD64)FileImage+pDosHeader->e_lfanew);

3、 获取数据目录 要用什么就用对应的结构体 这里以导入表为例

void PrintImportTable(PIMAGE_IMPORT_DESCRIPTOR importTable, LPVOID fileImage) {
    while (importTable->Name) {
        const char* dllName = (const char*)((DWORD64)fileImage + importTable->Name);
        std::cout << "DLL Name: " << dllName << std::endl;

        // 遍历导入的函数
        PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)((DWORD64)fileImage + importTable->OriginalFirstThunk);
        if (!thunk) {
            thunk = (PIMAGE_THUNK_DATA)((DWORD64)fileImage + importTable->FirstThunk);
        }

        while (thunk->u1.AddressOfData) {
            if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal)) {
                std::cout << "  Ordinal: " << IMAGE_ORDINAL(thunk->u1.Ordinal) << std::endl;
            }
            else {
                PIMAGE_IMPORT_BY_NAME importByName = (PIMAGE_IMPORT_BY_NAME)((DWORD64)fileImage + thunk->u1.AddressOfData);
                std::cout << "  Function: " << importByName->Name << std::endl;
            }
            thunk++;
        }
        importTable++;
    }
}
    IMAGE_DATA_DIRECTORY dataDir = (IMAGE_DATA_DIRECTORY)pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    PIMAGE_IMPORT_DESCRIPTOR importTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD64)FileImage + dataDir.VirtualAddress);

    PrintImportTable(importTable, FileImage);

4、 获取节表 遍历输出

WORD sectionNum = pNtHeader->FileHeader.NumberOfSections;
    std::cout << "-------------------------------------------------------"<< std::endl;
    PIMAGE_SECTION_HEADER pSecHeader = (PIMAGE_SECTION_HEADER)((DWORD64)pNtHeader+ sizeof(IMAGE_NT_HEADERS));
    for (size_t i = 0; i < sectionNum; i++) {
        PIMAGE_SECTION_HEADER currentPSecHeader = pSecHeader + i;
        std::cout << "Section Name: " << currentPSecHeader->Name << std::endl;


    }
0 条评论
某人
表情
可输入 255