逆向-PE头解析

与世无争的帅哥 提交于 2019-12-04 11:35:49

PE头解析

PE 格式是Windows系统下组织可执行文件的格式。PE文件由文件头和对应的数据组成。目标是在不同的架构下装载器和编程工具不用重写。

PE中一大特点是不连续的位置大部分记录的都是相对地址(RVA),相对的是PE文件中记录的基地址(image base)的偏移量。进程是程序的执行状态的实体,每个进程都有自己独立的内存空间(编址)PE和内核等一起编制,所以image base也不总是确定的。

结构(参考:加密与解密)

数据结构

IMAGE_DOS_HEADER

参考:参考:http://www.openrce.org/reference_library/files/reference/PE%20Format.pdf

IMAGE_DOS_HEADER STRUCT
{
+0h     WORD    e_magic //Magic DOS signature MZ(4Dh 5Ah) DOS可执行文件标记
+2h     WORD    e_cblp //Bytes on last page of file
+4h     WORD    e_cp //Pages in file
+6h     WORD    e_crlc //Relocations
+8h     WORD    e_cparhdr //Size of header in paragraphs
+0ah    WORD    e_minalloc //Minimun extra paragraphs needs
+0ch    WORD    e_maxalloc //Maximun extra paragraphs needs
+0eh    WORD    e_ss //intial(relative)SS value DOS代码的初始化堆栈SS
+10h    WORD    e_sp //intial SP value DOS代码的初始化堆栈指针SP
+12h    WORD    e_csum //Checksum
+14h    WORD    e_ip // intial IP value DOS代码的初始化指令入口[指针IP]
+16h    WORD    e_cs //intial(relative)CS value DOS代码的初始堆栈入口
+18h    WORD    e_lfarlc //File Address of relocation table
+1ah    WORD    e_ovno // Overlay number
+1ch    WORD    e_res[4] //Reserved words
+24h    WORD    e_oemid // OEM identifier(for e_oeminfo)
+26h    WORD    e_oeminfo // OEM information;e_oemid specific
+29h    WORD    e_res2[10] // Reserved words
+3ch    DWORD   e_lfanew //Offset to start of PE header PE头相对于文件的偏移地址
} IMAGE_DOS_HEADER ENDS

对于PE来说DOS头是为了兼容16位程序的,现在都是32、64位所以我们只关心这个结构体中的两个成员(16位系统中PE头和内容是冗余数据)

e_magic、e_lfanew(第一个和最后一个)

  • e_magic 是个标志MZ 0x4D5A 判断是否是PE文件用(不是唯一)
  • e_lfanew PE头相对于文件的偏移地址

上图3CH是e_lfanew内容指向PE头地址内容位0000000E(小端存储)。

我们可以看到DOS头和PE头是间隔的那块区域叫DOS stub这个是存储16位程序的,对于32、64位系统无用

PNTHeader = ImageBase + (dosHeader->e_lfanew)

IMAGE_NT_HEADERS

typedef struct _IMAGE_NT_HEADERS {
+0      hDWORD  Signature //
+4h     IMAGE_FILE_HEADER FileHeader //
+18h    IMAGE_OPTIONAL_HEADER32 OptionalHeader //
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

#define IMAGE_NT_SIGNATURE                  0x00004550
  • Signature固定为0x00004550

IMAGE_FILE_HEADER

typedef struct _IMAGE_FILE_HEADER {
+04h    WORD    Machine;//04h相对于_IMAGE_NT_HEADERS的,运行平台
+06h    WORD    NumberOfSections;//文件的区块数(*重要)
+08h    DWORD   TimeDateStamp;//文件创建时间 和unix时间戳一样int(secound(now-19700101))
+0cH    DWORD   PointerToSymbolTable;//指向符号表(主要用于调试)
+10H    DWORD   NumberOfSymbols;//符号表中符号个数(同上)
+14H    WORD    SizeOfOptionalHeader;//IMAGE_OPTIONAL_HEADER32 结构大小(*重要)IMAGE_OPTIONAL_HEADER是长度可变的。
+16H    WORD    Characteristics;//文件属性多,种属性通过 “或运算” 同时拥有
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

IMAGE_OPTIONAL_HEADER(*重要)

由IMAGE_FILE_HEADER的SizeOfOptionalHeader决定大小(可变长)
typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

+18h    WORD Magic; // 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
+1Ah    BYTE MajorLinkerVersion; // 链接程序的主版本号
+1Bh    BYTE MinorLinkerVersion; // 链接程序的次版本号
+1Ch    DWORD SizeOfCode; // 所有含代码的节的总大小
+20h    DWORD SizeOfInitializedData; // 所有含已初始化数据的节的总大小
+24h    DWORD SizeOfUninitializedData; // 所有含未初始化数据的节的大小
+28h    DWORD AddressOfEntryPoint; // 程序执行入口RVA
+2Ch    DWORD BaseOfCode; // 代码的区块的起始RVA
+30h    DWORD BaseOfData; // 数据的区块的起始RVA

    //
    // NT additional fields.
    //

+34h    DWORD ImageBase; // 文件在内存中的的首选装载地址。
+38h    DWORD SectionAlignment; // 内存中的区块的对齐大小
+3Ch    DWORD FileAlignment; // 文件中的区块的对齐大小
+40h    WORD MajorOperatingSystemVersion; // 要求操作系统最低版本号的主版本号
+42h    WORD MinorOperatingSystemVersion; // 要求操作系统最低版本号的副版本号
+44h    WORD MajorImageVersion; // 可运行于操作系统的主版本号
+46h    WORD MinorImageVersion; // 可运行于操作系统的次版本号
+48h    WORD MajorSubsystemVersion; // 要求最低子系统版本的主版本号
+4Ah    WORD MinorSubsystemVersion; // 要求最低子系统版本的次版本号
+4Ch    DWORD Win32VersionValue; // 莫须有字段,不被病毒利用的话一般为0
+50h    DWORD SizeOfImage; // 映像装入内存后的总尺寸
+54h    DWORD SizeOfHeaders; // 所有头 + 区块表的尺寸大小
+58h    DWORD CheckSum; // 映像的校检和
+5Ch    WORD Subsystem; // 可执行文件期望的子系统
+5Eh    WORD DllCharacteristics; // DllMain()函数何时被调用,默认为 0
+60h    DWORD SizeOfStackReserve; // 初始化时的栈大小
+64h    DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小
+68h    DWORD SizeOfHeapReserve; // 初始化时保留的堆大小
+6Ch    DWORD SizeOfHeapCommit; // 初始化时实际提交的堆大小
+70h    DWORD LoaderFlags; // 与调试有关,默认为 0
+74h    DWORD NumberOfRvaAndSizes; // 下边数据目录的项数,这个字段自Windows NT 发布以来 // 一直是16
+78h    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];// 数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

ImageBase文件在内存中载入地址,如果有文件占据这个位置装载器会进行应用基址重定位。 对于EXE文件来说,由于每个文件总是使用独立的虚拟地址空间一般不会被别的文件抢占。 对于DLL文件来说,由于多个DLL文件全部使用宿主EXE文件的地址空间,不能保证优先装入地址没有被别的DLL使用,所以DLL文件中必须包含重定位信息,对应

#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // Relocation info stripped from file.
 IMAGE_FILE_HEADER ->Characteristics(可以看到下面的数字位数不同,而且都是1、2、4、8他们总共加起来就是16看看二进制就知道了他们占的不是同一位与一下就能取到相对应位,linux的文件属性1、2、4也是一样的)
EXE文件的默认优先装入地址被定为00400000h,而DLL文件的默认优先装入地址被定为10000000h

  • AddressOfEntryPoint字段 : 程序执行入口RVA,imageBase+AddressOfEntryPoint就是程序运行的时候首先执行代码处。一般指向.text节。

  • SectionAlignment:程序载入内存后区块(节)对齐大小,每个节被载入内存后必须和CPU内存页对齐(方便设置内存页属性)最小1Kh(4KB)

  • FileAlignment:PE文件在磁盘上的对齐大小,最小为200h(512byte)一个扇区大小。

    关于对齐可以用winhex打开磁盘上的notpad.exe和内存中的notpad.exe,notpad装载的时候就被拉伸了。

  • IMAGE_DATA_DIRECTORY

    这个是个结构存储的对应表位置和大小,至于这些表有什么,做什么需要下面详细解释。

    typedef struct _IMAGE_DATA_DIRECTORY {
        DWORD   VirtualAddress; //表首地址的RVA
        DWORD   Size;  //表长度
    } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

    下面是微软的文档上截取的,其中我们比较关心的是导入表、重定位表(DLL等)

    Size Field Description
    8 Export Table The export table address and size. For more information see .edata Section (Image Only).
    8 Import Table The import table address and size. For more information, see The .idata Section.
    8 Resource Table The resource table address and size. For more information, see The .rsrc Section.
    8 Exception Table The exception table address and size. For more information, see The .pdata Section.
    8 Certificate Table The attribute certificate table address and size. For more information, see The Attribute Certificate Table (Image Only).
    8 Base Relocation Table The base relocation table address and size. For more information, see The .reloc Section (Image Only).
    8 Debug The debug data starting address and size. For more information, see The .debug Section.
    8 Architecture Reserved, must be 0
    8 Global Ptr The RVA of the value to be stored in the global pointer register. The size member of this structure must be set to zero.
    8 TLS Table The thread local storage (TLS) table address and size. For more information, The .tls Section.
    8 Load Config Table The load configuration table address and size. For more information, The Load Configuration Structure (Image Only).
    8 Bound Import The bound import table address and size.
    8 IAT The import address table address and size. For more information, see Import Address Table.
    8 Delay Import Descriptor The delay import descriptor address and size. For more information, see Delay-Load Import Tables (Image Only).
    8 CLR Runtime Header The CLR runtime header address and size. For more information, see The .cormeta Section (Object Only).
    8 Reserved, must be zero

区块

区块由区块表映射,区块表紧跟IMAGE_NT_HEADERS;多少个区块表由_IMAGE_NT_HEADERS.FileHeader.NumberOfSections指定。

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];   //8字节的name
    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;

#define IMAGE_SIZEOF_SHORT_NAME              8
  • Name八个字节的不一定以“\0”借位的字符串表示节的名字

  • Misc、PhysicalAddress、VirtualSize叫什么都行是未对齐前节的大小(可以不准确,改了也没事,编译器生成的)

  • 节在内存中的偏移地址,RVA。

  • SizeOfRawData 节在文件中对齐的尺寸

  • PointerToRawData 节在磁盘文件中的便宜,如果需要自己装载PE(不用操作系统装载)这个值有很多用处。

  • Characteristics 节属性(代码/数据,可读/可写)

    常用
    #define IMAGE_SCN_CNT_CODE                   0x00000020  // Section contains code.
    #define IMAGE_SCN_CNT_INITIALIZED_DATA       0x00000040  // Section contains initialized data.
    #define IMAGE_SCN_CNT_UNINITIALIZED_DATA     0x00000080  // Section contains uninitialized data.
    #define IMAGE_SCN_MEM_DISCARDABLE            0x02000000  // Section can be discarded.
    #define IMAGE_SCN_MEM_NOT_CACHED             0x04000000  // Section is not cachable.
    #define IMAGE_SCN_MEM_NOT_PAGED              0x08000000  // Section is not pageable.
    #define IMAGE_SCN_MEM_SHARED                 0x10000000  // Section is shareable.
    #define IMAGE_SCN_MEM_EXECUTE                0x20000000  // Section is executable.
    #define IMAGE_SCN_MEM_READ                   0x40000000  // Section is readable.
    #define IMAGE_SCN_MEM_WRITE                  0x80000000  // Section is writeable.
    关于区块的对齐

    区块对齐分两部分,FileAlignment、SectionAlignment分别代表文件中和内存中的对齐,为了有效读取和设置属性规定,FileAlignment现在好多程序都和SectionAlignment一样大小了。SectionAlignment内存对齐值在x86上一般是内存页大小4kB(1000H)在x64当中是8KB(2000H)。

    文件偏移与虚拟地址的转换

    FileAlignment、SectionAlignment不同需要将磁盘中的PE文件拉伸。

FileOffset=VA-RVA-k

参考:加密与解密
https://docs.microsoft.com/zh-cn/windows/win32/debug/pe-format

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!