fz12 发表于 2022-5-4 01:24

快速查询PE文件知识点和PE文件解析(上)

本帖最后由 fz12 于 2022-5-5 10:48 编辑

PE(Portable Executable)
PE文件的全称是Portable Executable ,意为可移植的可执行文件,常见的有EXE,DLL,SYS,COM,OCX,PE文件是微软Windows操作系统上的程序文件

PE节
节名说明
.text.text 节是供机器指令使用的默认节。一般情况下,在最终生成的可执行文件中,链接器将把每个.OBJ文件的.text节合并成一个巨大的、统一的.text节。
.data全局和静态变量存储(在编译时初始化)
.bss全局和静态变量存储(在编译时不初始化)
.textbss启用增量链接
.rsrc.rsrc节用于储存模块资源,这些资源是可以嵌入到可执行文件中的二进制对象。例如,定制的鼠标光标、字体、程序图标、字符串表及版本信息都是标准的资源。资源还可以是应用程序需要的任意数据块(例如另一个可执行文件)。
.idata存储有关导入库例程信息
.edata存储有关导出库例程信息
.reloc重定位
.rdata数据段(只读)
.crtc++ 运行时库 runtime
.tls线程局部存储

基础知识
[*]基地址(ImageBase):当PE文件通过Windows加载器载入内存后,内存中的版本称为模块,映射文件的起始地址称为模块句柄,可通过模块句柄访问内存中其他数据结构,这个内存起始地址就称为基地址。
[*]虚拟地址(VA,Virtual Address):在Windows系统中,PE文件被系统加载到内存后,每个程序都有自己的虚拟空间,这个虚拟空间的内存地址称为虚拟地址。
[*]相对虚拟地址(RVA,Relative Virtual Address):可执行文件中,有许多地方需要指定内存中的地址。例如,应用全局变量时需要指定它的地址。为了避免在PE文件中出现绝对内存地址引入了相对虚拟地址,它就是在内存中相对于PE文件载入地址的偏移量。
它们之间的关系:虚拟地址(VA)= 基地址(Image Base)+相对虚拟地址(RVA)
[*]文件偏移地址(Offset):当PE文件存储在磁盘中时,某个数据的位置相对于文件头的偏移量称为文件偏移地址(File Offset)。文件偏移地址从PE文件的第一个字节开始计数,起始值为0

PE 头解析
DOS头
DOS头和DOS存根,它们的存在主要是用来兼容DOS系统。当我们的程序运行在DOS系统的时候,就会运行DOS存根中的代码,代码内容就是输出一段字符串告诉用户,这个程序不能在16位系统运行。
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // DOS头签名,4D5A
    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;                  // 保留字
    WORD   e_oemid;                     // OEM 标识符(相对 e_oeminfo)
    WORD   e_oeminfo;                   // OEM 信息; e_oemid specific
    WORD   e_res2;                  // 保留字
    LONG   e_lfanew;                  // PE头的偏移位置
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
DOS存根
在DOS头下方,是个可选项,大小不固定,由代码与数据混合而成,一般从0x40开始
标识头
#define IMAGE_DOS_SIGNATURE               0x5A4D      // MZ
#define IMAGE_OS2_SIGNATURE               0x454E      // NE
#define IMAGE_OS2_SIGNATURE_LE            0x454C      // LE
#define IMAGE_VXD_SIGNATURE               0x454C      // LE
#define IMAGE_NT_SIGNATURE                  0x00004550// PE00
解析DOS头
#include<Windows.h>
#include<iostream>
using namespace std;

void ImageDosHeader(_In_z_ const char* path)
{
      // 获取文件对象
      HANDLE hfile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
      // 获取文件大小
      DWORD fSize = GetFileSize(hfile, NULL);
      char* pBuff = new char;
      DWORD dwReadSize = 0;
      
      // 读文件
      BOOL bSuccess = ReadFile(hfile, pBuff, fSize, &dwReadSize, NULL);
      if (bSuccess)
      {
                PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuff;
                // DOS头签名
                cout << hex << pDosHeader->e_magic << endl;
                // PE头的偏移位置
                cout << hex << pDosHeader->e_lfanew << endl;
      }
      else (cout.write("打开文件失败", 20));
      CloseHandle(hfile);
      delete[] pBuff;
}

void main()
{
      ImageDosHeader(R"(C:\Users\11981\Desktop\Project1\1.exe)");
}
NT头
typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature; // 标识,0x00004550
    IMAGE_FILE_HEADER FileHeader; // 文件头
    IMAGE_OPTIONAL_HEADER64 OptionalHeader; // 可选头
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
NT头:文件头
typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine; // 运行平台
    WORD    NumberOfSections; // 文件存在的区段数量
    DWORD   TimeDateStamp; // PE文件的创建时间,一般有连接器填写
    DWORD   PointerToSymbolTable; // COFF文件符号表在文件中的偏移
    DWORD   NumberOfSymbols; // 符号表的数量
    WORD    SizeOfOptionalHeader; // 可选PE头的大小
    WORD    Characteristics; // 文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Machine:运行平台
#define IMAGE_FILE_MACHINE_UNKNOWN         0
#define IMAGE_FILE_MACHINE_TARGET_HOST       0x0001// Useful for indicating we want to interact with the host and not a WoW guest.
#define IMAGE_FILE_MACHINE_I386            0x014c// Intel 386.
#define IMAGE_FILE_MACHINE_R3000             0x0162// MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000             0x0166// MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000            0x0168// MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2         0x0169// MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA             0x0184// Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3               0x01a2// SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP            0x01a3
#define IMAGE_FILE_MACHINE_SH3E            0x01a4// SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4               0x01a6// SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5               0x01a8// SH5
#define IMAGE_FILE_MACHINE_ARM               0x01c0// ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB             0x01c2// ARM Thumb/Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_ARMNT             0x01c4// ARM Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_AM33            0x01d3
#define IMAGE_FILE_MACHINE_POWERPC         0x01F0// IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP         0x01f1
#define IMAGE_FILE_MACHINE_IA64            0x0200// Intel 64
#define IMAGE_FILE_MACHINE_MIPS16            0x0266// MIPS
#define IMAGE_FILE_MACHINE_ALPHA64         0x0284// ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU         0x0366// MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16         0x0466// MIPS
#define IMAGE_FILE_MACHINE_AXP64             IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE         0x0520// Infineon
#define IMAGE_FILE_MACHINE_CEF               0x0CEF
#define IMAGE_FILE_MACHINE_EBC               0x0EBC// EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64             0x8664// AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R            0x9041// M32R little-endian
#define IMAGE_FILE_MACHINE_ARM64             0xAA64// ARM64 Little-Endian
#define IMAGE_FILE_MACHINE_CEE               0xC0EE
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_AGGRESIVE_WS_TRIM         0x0010// 调整工作集
#define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020// 应用程序可处理大于2GB的地址
#define IMAGE_FILE_BYTES_REVERSED_LO         0x0080// Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE             0x0100// 只在32为平台上运行
#define IMAGE_FILE_DEBUG_STRIPPED            0x0200// 不包含调试信息
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400// 不能从可移动盘运行
#define IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800// 不能从网络运行
#define IMAGE_FILE_SYSTEM                  0x1000// 系统文件,不能直接运行
#define IMAGE_FILE_DLL                     0x2000// DLL文件
#define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000// 文件不能在多处理器计算机上运行
#define IMAGE_FILE_BYTES_REVERSED_HI         0x8000// Bytes of machine word are reversed.
NE头:可选头
typedef struct _IMAGE_OPTIONAL_HEADER {
    WORD    Magic; // PE类型
    BYTE    MajorLinkerVersion; // 链接器的版本号
    BYTE    MinorLinkerVersion; // 链接器的版本号
    DWORD   SizeOfCode; // 代码段的长度,如果有多个代码段,则是代码段长度的总和
    DWORD   SizeOfInitializedData; // 初始化的数据长度
    DWORD   SizeOfUninitializedData; // 未初始化的数据长度
    DWORD   AddressOfEntryPoint; // 程序入口的RVA,对于exe这个地址可以理解为WinMain的RVA。对于DLL,这个地址可以理解为DllMain的RVA,如果是驱动程序,可以理解为DriverEntry的RVA
    DWORD   BaseOfCode; // 代码段起始地址的RVA
    DWORD   BaseOfData; // 数据段起始地址的RVA
    DWORD   ImageBase; // 映象(加载到内存中的PE文件)的基地址,这个基地址是建议,对于DLL来说,如果无法加载到这个地址,系统会自动为其选择地址。
    DWORD   SectionAlignment; // 节对齐,PE中的节被加载到内存时会按照这个域指定的值来对齐,比如这个值是0x1000,那么每个节的起始地址的低12位都为0
    DWORD   FileAlignment; // 节在文件中按此值对齐,SectionAlignment必须大于或等于FileAlignment
    WORD    MajorOperatingSystemVersion; // 所需操作系统的版本号
    WORD    MinorOperatingSystemVersion; // 所需操作系统的版本号
    WORD    MajorImageVersion; // 映象的版本号,这个是开发者自己指定的,由连接器填写
    WORD    MinorImageVersion; // 映象的版本号,这个是开发者自己指定的,由连接器填写
    WORD    MajorSubsystemVersion; // 所需子系统版本号
    WORD    MinorSubsystemVersion; // 所需子系统版本号
    DWORD   Win32VersionValue; // 保留,必须为0
    DWORD   SizeOfImage; // 映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小
    DWORD   SizeOfHeaders; // 所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的
    DWORD   CheckSum; // 映象文件的校验和
    WORD    Subsystem; // 运行该PE文件所需的子系统
    WORD    DllCharacteristics; // 映像文件的DLL特性
    DWORD   SizeOfStackReserve; // 运行时为每个线程栈保留内存的大小
    DWORD   SizeOfStackCommit; // 运行时每个线程栈初始占用内存大小
    DWORD   SizeOfHeapReserve; // 运行时为进程堆保留内存大小
    DWORD   SizeOfHeapCommit; // 运行时进程堆初始占用内存大小
    DWORD   LoaderFlags; // 保留,必须为0
    DWORD   NumberOfRvaAndSizes; // 数据目录的项数
    IMAGE_DATA_DIRECTORY DataDirectory; // 数据目录的结构体
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

typedef struct _IMAGE_OPTIONAL_HEADER64 {
    WORD      Magic;
    BYTE      MajorLinkerVersion;
    BYTE      MinorLinkerVersion;
    DWORD       SizeOfCode;
    DWORD       SizeOfInitializedData;
    DWORD       SizeOfUninitializedData;
    DWORD       AddressOfEntryPoint;
    DWORD       BaseOfCode;
    ULONGLONG   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;
    ULONGLONG   SizeOfStackReserve;
    ULONGLONG   SizeOfStackCommit;
    ULONGLONG   SizeOfHeapReserve;
    ULONGLONG   SizeOfHeapCommit;
    DWORD       LoaderFlags;
    DWORD       NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory;
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
Magic:PE类型
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC      0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC      0x20b
#define IMAGE_ROM_OPTIONAL_HDR_MAGIC       0x107
Subsystem:子系统
#define IMAGE_SUBSYSTEM_UNKNOWN            0   // Unknown subsystem.
#define IMAGE_SUBSYSTEM_NATIVE               1   // Image doesn't require a subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_GUI          2   // Image runs in the Windows GUI subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_CUI          3   // Image runs in the Windows character subsystem.
#define IMAGE_SUBSYSTEM_OS2_CUI            5   // image runs in the OS/2 character subsystem.
#define IMAGE_SUBSYSTEM_POSIX_CUI            7   // image runs in the Posix character subsystem.
#define IMAGE_SUBSYSTEM_NATIVE_WINDOWS       8   // image is a native Win9x driver.
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI       9   // Image runs in the Windows CE subsystem.
#define IMAGE_SUBSYSTEM_EFI_APPLICATION      10//
#define IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER11   //
#define IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER   12//
#define IMAGE_SUBSYSTEM_EFI_ROM            13
#define IMAGE_SUBSYSTEM_XBOX               14
#define IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION 16
#define IMAGE_SUBSYSTEM_XBOX_CODE_CATALOG    17
DllCharacteristics:DLL特性
//      IMAGE_LIBRARY_PROCESS_INIT            0x0001   // Reserved.
//      IMAGE_LIBRARY_PROCESS_TERM            0x0002   // Reserved.
//      IMAGE_LIBRARY_THREAD_INIT             0x0004   // Reserved.
//      IMAGE_LIBRARY_THREAD_TERM             0x0008   // Reserved.
#define IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA    0x0020// Image can handle a high entropy 64-bit virtual address space.
#define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040   // DLL can move.
#define IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY    0x0080   // Code Integrity Image
#define IMAGE_DLLCHARACTERISTICS_NX_COMPAT    0x0100   // Image is NX compatible
#define IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200   // Image understands isolation and doesn't want it
#define IMAGE_DLLCHARACTERISTICS_NO_SEH       0x0400   // Image does not use SEH.No SE handler may reside in this image
#define IMAGE_DLLCHARACTERISTICS_NO_BIND      0x0800   // Do not bind this image.
#define IMAGE_DLLCHARACTERISTICS_APPCONTAINER 0x1000   // Image should execute in an AppContainer
#define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER   0x2000   // Driver uses WDM model
#define IMAGE_DLLCHARACTERISTICS_GUARD_CF   0x4000   // Image supports Control Flow Guard.
#define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE   0x8000
数据目录项
该字段定义了PE 文件中出现的所有不同类型的数据的目录信息
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress; // 就是数据目录表的RVA
    DWORD   Size; // 数据块的长度,起始地址+尺寸 = 结束的位置
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
IMAGE_DATA_DIRECTORY
#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // 导出表地址和大小
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // 导入表地址和大小
#define IMAGE_DIRECTORY_ENTRY_RESOURCE      2   // 资源表地址和大小
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // 异常表地址和大小
#define IMAGE_DIRECTORY_ENTRY_SECURITY      4   // 属性证书数据地址和大小
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // 基地址重定位表地址和大小
#define IMAGE_DIRECTORY_ENTRY_DEBUG         6   // 调试信息地址和大小
//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // 预留为0
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS地址和大小
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // 加载配置表地址和大小
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // 绑定导入表地址和大小
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // 导入函数地址表地址和大小
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // 延迟导入表地址和大小
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor
区段头
定义了每个区段的属性
typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name; // 节表名称,如“.text”
    union {
            DWORD   PhysicalAddress; // 物理地址
            DWORD   VirtualSize; // 内存中区段所占大小
    } Misc;
    DWORD   VirtualAddress; // 内存中区段起始位置(RVA)
    DWORD   SizeOfRawData; // 硬盘文件中区段所占大小
    DWORD   PointerToRawData; // 硬盘文件中区段起始位置
    DWORD   PointerToRelocations; // 在OBJ文件中使用,重定位的偏移
    DWORD   PointerToLinenumbers; // 行号表的偏移(供调试使用地)
    WORD    NumberOfRelocations; // 在OBJ文件中使用,重定位项数目
    WORD    NumberOfLinenumbers; // 行号表中行号的数目
    DWORD   Characteristics; // 区段属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
Characteristics:区段属性
//      IMAGE_SCN_TYPE_REG                   0x00000000// Reserved.
//      IMAGE_SCN_TYPE_DSECT               0x00000001// Reserved.
//      IMAGE_SCN_TYPE_NOLOAD                0x00000002// Reserved.
//      IMAGE_SCN_TYPE_GROUP               0x00000004// Reserved.
#define IMAGE_SCN_TYPE_NO_PAD                0x00000008// Reserved.
//      IMAGE_SCN_TYPE_COPY                  0x00000010// Reserved.

#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_LNK_OTHER                  0x00000100// Reserved.
#define IMAGE_SCN_LNK_INFO                   0x00000200// Section contains comments or some other type of information.
//      IMAGE_SCN_TYPE_OVER                  0x00000400// Reserved.
#define IMAGE_SCN_LNK_REMOVE               0x00000800// Section contents will not become part of image.
#define IMAGE_SCN_LNK_COMDAT               0x00001000// Section contents comdat.
//                                           0x00002000// Reserved.
//      IMAGE_SCN_MEM_PROTECTED - Obsolete   0x00004000
#define IMAGE_SCN_NO_DEFER_SPEC_EXC          0x00004000// Reset speculative exceptions handling bits in the TLB entries for this section.
#define IMAGE_SCN_GPREL                      0x00008000// Section content can be accessed relative to GP
#define IMAGE_SCN_MEM_FARDATA                0x00008000
//      IMAGE_SCN_MEM_SYSHEAP- Obsolete    0x00010000
#define IMAGE_SCN_MEM_PURGEABLE            0x00020000
#define IMAGE_SCN_MEM_16BIT                  0x00020000
#define IMAGE_SCN_MEM_LOCKED               0x00040000
#define IMAGE_SCN_MEM_PRELOAD                0x00080000

#define IMAGE_SCN_ALIGN_1BYTES               0x00100000//
#define IMAGE_SCN_ALIGN_2BYTES               0x00200000//
#define IMAGE_SCN_ALIGN_4BYTES               0x00300000//
#define IMAGE_SCN_ALIGN_8BYTES               0x00400000//
#define IMAGE_SCN_ALIGN_16BYTES            0x00500000// Default alignment if no others are specified.
#define IMAGE_SCN_ALIGN_32BYTES            0x00600000//
#define IMAGE_SCN_ALIGN_64BYTES            0x00700000//
#define IMAGE_SCN_ALIGN_128BYTES             0x00800000//
#define IMAGE_SCN_ALIGN_256BYTES             0x00900000//
#define IMAGE_SCN_ALIGN_512BYTES             0x00A00000//
#define IMAGE_SCN_ALIGN_1024BYTES            0x00B00000//
#define IMAGE_SCN_ALIGN_2048BYTES            0x00C00000//
#define IMAGE_SCN_ALIGN_4096BYTES            0x00D00000//
#define IMAGE_SCN_ALIGN_8192BYTES            0x00E00000//
// Unused                                    0x00F00000
#define IMAGE_SCN_ALIGN_MASK               0x00F00000

#define IMAGE_SCN_LNK_NRELOC_OVFL            0x01000000// Section contains extended relocations.
#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.
NT头解析
#include<Windows.h>
#include<iostream>
using namespace std;

void ImageNtHeader(_In_z_ const char* path)
{
      // 获取文件对象
      HANDLE hfile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
      // 获取文件大小
      DWORD fSize = GetFileSize(hfile, NULL);
      char* pBuff = new char;
      DWORD dwReadSize = 0;

      // 读文件
      BOOL bSuccess = ReadFile(hfile, pBuff, fSize, &dwReadSize, NULL);
      if (bSuccess)
      {
                PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuff;
                PIMAGE_NT_HEADERS64 pNtHeader{ 0 };
                pNtHeader = (PIMAGE_NT_HEADERS64)(pDosHeader->e_lfanew + pBuff);
                // 标识
                cout << hex << pNtHeader->Signature << endl;

                PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)&pNtHeader->FileHeader;
                // 运行平台
                cout << pFileHeader->Machine << endl;

                PIMAGE_OPTIONAL_HEADER64 pOptionalHeader = (PIMAGE_OPTIONAL_HEADER64)&pNtHeader->OptionalHeader;
                PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
                for (int i = 0; i < pFileHeader->NumberOfSections; i++)
                {
                        // 节表名称
                        cout << pSectionHeader->Name << endl;
                        // 硬盘文件中区段所占大小
                        cout << pSectionHeader->SizeOfRawData << endl;
                        pSectionHeader++;
                }
      }
      else (cout.write("打开文件失败", 20));
      CloseHandle(hfile);
      delete[] pBuff;
}

void main()
{
      ImageNtHeader(R"(C:\Users\11981\Desktop\Project1\1.exe)");
}
导入表
当使用到一个dll中的函数时,会出现在导入表中(隐式调用)导入表位于数据目录表中第1项
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
      DWORD   Characteristics;            // 0 for terminating null import descriptor
      DWORD   OriginalFirstThunk;         // 指向导入名称表(INT)的RAV
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;                  // 0 if not bound,
                                          // -1 if bound, and real date\time stamp
                                          //   in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                          // O.W. date/time stamp of DLL bound to (Old BIND)

    DWORD   ForwarderChain; // 转发链,如果不转发则此值为0
    DWORD   Name; // 指向导入映像文件的名字
    DWORD   FirstThunk; // 指向导入地址表(IAT)的RAV
} IMAGE_IMPORT_DESCRIPTOR;
OriginalFirstThunk和FirstThunk分别指向两个不同的IMAGE_THUNK_DATA结构的数组。这两个数组都以一个空的IMAGE_THUNK_DATA结构结尾。一般情况下,导入表只需要关注OriginalFirstThunk和FirstThunk这两个字段。
typedef struct _IMAGE_THUNK_DATA64 {
    union {
      ULONGLONG ForwarderString;// PBYTE
      ULONGLONG Function;         // PDWORD
      ULONGLONG Ordinal;
      ULONGLONG AddressOfData;    // PIMAGE_IMPORT_BY_NAME
    } u1;
} IMAGE_THUNK_DATA64;
typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;

#include "poppack.h"                        // Back to 4 byte packing

//@
typedef struct _IMAGE_THUNK_DATA32 {
    union {
      DWORD ForwarderString;      // 指向一个转向者字符串的RVA
      DWORD Function;   // 被输入的函数的内存地址;
      DWORD Ordinal;// 被输入的API的序数值
      DWORD AddressOfData;       // 指向IMAGE_IMPORT_BY_NAME
    } u1;
} IMAGE_THUNK_DATA32;
导入表解析
#include<Windows.h>
#include<iostream>
using namespace std;

DWORD RvaToFoa(_In_ DWORD rva, _In_ PIMAGE_SECTION_HEADER p, _In_ PIMAGE_FILE_HEADER f)
{
      for (int i = 0; i < f->NumberOfSections; i++)
      {
                // FOA = 数据的RVA + 区段的RVA - 区段的FOA
                if (rva >= p->VirtualAddress && rva < (p->VirtualAddress + p->SizeOfRawData))
                {
                        return rva - p->VirtualAddress + p->PointerToRawData;
                }
                f++;
                p++;
      }
      return 0;
}

void ImageNtHeader(_In_z_ const char* path)
{
      // 获取文件对象
      HANDLE hfile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
      // 获取文件大小
      DWORD fSize = GetFileSize(hfile, NULL);
      char* pBuff = new char;
      DWORD dwReadSize = 0;
      // 读文件
      BOOL bSuccess = ReadFile(hfile, pBuff, fSize, &dwReadSize, NULL);
      if (bSuccess)
      {
                PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuff;
                PIMAGE_NT_HEADERS32 pNtHeader{ 0 };
                pNtHeader = (PIMAGE_NT_HEADERS32)(pDosHeader->e_lfanew + pBuff);
                PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)&pNtHeader->FileHeader;
                PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)&pNtHeader->OptionalHeader;
                PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
                PIMAGE_DATA_DIRECTORY dataDirectory = (PIMAGE_DATA_DIRECTORY)&pOptionalHeader->DataDirectory;
                // 导入表地址
                PIMAGE_IMPORT_DESCRIPTOR pImportDescrtiptor = (PIMAGE_IMPORT_DESCRIPTOR)(RvaToFoa(dataDirectory->VirtualAddress, pSectionHeader, pFileHeader) + pBuff);
                while (pImportDescrtiptor->OriginalFirstThunk)
                {
                        char* dllName = (RvaToFoa(pImportDescrtiptor->Name, pSectionHeader, pFileHeader) + pBuff);
                        cout << "DLL名称:" << dllName << endl;
                        cout << "日期时间标志:" << hex << pImportDescrtiptor->TimeDateStamp << endl;
                        cout << "转发链:" << hex << pImportDescrtiptor->ForwarderChain << endl;
                        cout << "名称OFFSET:" << hex << pImportDescrtiptor->Name << endl;
                        cout << "FirstThunk:" << hex << pImportDescrtiptor->FirstThunk << endl;
                        cout << "OriginalFirstThunk:" << hex << pImportDescrtiptor->OriginalFirstThunk << endl;
                        //指向地址表中的RVA
                        PIMAGE_THUNK_DATA pInt = (PIMAGE_THUNK_DATA)(RvaToFoa(pImportDescrtiptor->OriginalFirstThunk, pSectionHeader, pFileHeader) + pBuff);
                        DWORD Index = 0;
                        DWORD ImportOffset = 0;
                        //被导入函数的序号
                        while (pInt->u1.Ordinal)
                        {
                              // 说明序号导入,这个函数没有名字
                              if (pInt->u1.Ordinal & 0x80000000)
                              {
                                        int a = pInt->u1.Ordinal & 0x7FFFFFFF;
                                        cout << a << endl;
                              }
                              //最高位是0,表示这个是一个有名称的函数 RVA
                              else
                              {
                                        PIMAGE_IMPORT_BY_NAME importName = (PIMAGE_IMPORT_BY_NAME)(RvaToFoa(pInt->u1.AddressOfData, pSectionHeader, pFileHeader) + pBuff);
                                        cout << "API名称:" << importName->Name << endl;
                                        cout << "API序号:" << importName->Hint << endl;
                                        cout << "ThunkValue:" << pInt->u1.Function << endl;
                              }
                              pInt++;
                        }
                        pImportDescrtiptor++;
                }
      }
      else (cout.write("打开文件失败", 20));
      CloseHandle(hfile);
      delete[] pBuff;
}

void main()
{
      ImageNtHeader(R"(C:\Users\11981\Desktop\Project1\2.exe)");
}
导出表
导出的行为一般是dll给其他程序提供函数,变量,类导出表位于数据目录表的第0项
typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics; // 未使用,总是定义为0
    DWORD   TimeDateStamp; // 文件生成时间
    WORD    MajorVersion; // 未使用,总是定义为0
    WORD    MinorVersion; // 未使用,总是定义为0
    DWORD   Name; // 模块的真实名称
    DWORD   Base; // 基数,加上序数就是函数地址数组的索引值
    DWORD   NumberOfFunctions; // 导出函数的总数
    DWORD   NumberOfNames; // 以名称方式导出的函数的总数
    DWORD   AddressOfFunctions; // 导出地址表(EAT),指向输出函数地址的RVA
    DWORD   AddressOfNames;// 导出名称表(ENT),指向输出函数名字的RVA
    DWORD   AddressOfNameOrdinals;//指向导出序号数组(EOT),指向输出函数序号的RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
导出表解析
#include<Windows.h>
#include<iostream>
using namespace std;

DWORD RvaToFoa(_In_ DWORD rva, _In_ PIMAGE_SECTION_HEADER p, _In_ PIMAGE_FILE_HEADER f)
{
      for (int i = 0; i < f->NumberOfSections; i++)
      {
                // FOA = 数据的RVA + 区段的RVA - 区段的FOA
                if (rva >= p->VirtualAddress && rva < (p->VirtualAddress + p->SizeOfRawData))
                {
                        return rva - p->VirtualAddress + p->PointerToRawData;
                }
                f++;
                p++;
      }
      return 0;
}

void ImageNtHeader(_In_z_ const char* path)
{
      // 获取文件对象
      HANDLE hfile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
      // 获取文件大小
      DWORD fSize = GetFileSize(hfile, NULL);
      char* pBuff = new char;
      DWORD dwReadSize = 0;
      // 读文件
      BOOL bSuccess = ReadFile(hfile, pBuff, fSize, &dwReadSize, NULL);
      if (bSuccess)
      {
                PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuff;
                PIMAGE_NT_HEADERS32 pNtHeader{ 0 };
                pNtHeader = (PIMAGE_NT_HEADERS32)(pDosHeader->e_lfanew + pBuff);
                PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)&pNtHeader->FileHeader;
                PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)&pNtHeader->OptionalHeader;
                PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
                PIMAGE_DATA_DIRECTORY dataDirectory = (PIMAGE_DATA_DIRECTORY)&pOptionalHeader->DataDirectory;

                PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(RvaToFoa(dataDirectory->VirtualAddress, pSectionHeader, pFileHeader) + pBuff);
                char* szName = (char*)(RvaToFoa(pExport->Name, pSectionHeader, pFileHeader) + pBuff);

                if (pExport->AddressOfFunctions == 0)
                {
                        cout.write("没有导出表", 20);
                        return;
                }
                cout << "导出表OFFSET:" << RvaToFoa(dataDirectory->VirtualAddress, pSectionHeader, pFileHeader) << endl;
                cout << "特征值:" << pExport->Characteristics << endl;
                cout << "基:" << pExport->Base << endl;
                cout << "名称OFFSET:" << pExport->Name << endl;
                cout << "名称:" << szName << endl;
                cout << "函数数量:" << pExport->NumberOfFunctions << endl;
                cout << "函数名数量:" << pExport->NumberOfNames << endl;
                cout << "函数地址:" << pExport->AddressOfFunctions << endl;
                cout << "函数名称地址:" << pExport->AddressOfNames << endl;
                cout << "函数名称序号地址:" << pExport->AddressOfNameOrdinals << endl;
                //函数地址数量
                DWORD dwNumOfFun = pExport->NumberOfFunctions;
                //函数名数量
                DWORD dwNumOfNames = pExport->NumberOfNames;
                //基
                DWORD dwBase = pExport->Base;
                //导出地址表
                PDWORD pEAt32 = (PDWORD)(RvaToFoa(pExport->AddressOfFunctions, pSectionHeader, pFileHeader) + pBuff);
                //导出名称表
                PDWORD pENt32 = (PDWORD)(RvaToFoa(pExport->AddressOfNames, pSectionHeader, pFileHeader) + pBuff);
                //导出序号表
                PWORD pId = (PWORD)(RvaToFoa(pExport->AddressOfNameOrdinals, pSectionHeader, pFileHeader) + pBuff);
                for (DWORD i = 0; i < dwNumOfFun; i++)
                {
                        if (pEAt32 == 0)
                        {
                              continue;
                        }
                        DWORD Id = 0;
                        for (; Id < dwNumOfNames; Id++)
                        {
                              if (pId == i)
                              {
                                        break;
                              }
                        }
                        if (Id == dwNumOfNames)
                        {
                              cout << "Id:%x" << i + dwBase << endl;
                              cout << "Address:" << pEAt32;
                        }
                        else
                        {
                              char* szFunName = (char*)(RvaToFoa(pENt32, pSectionHeader, pFileHeader) + pBuff);
                              cout << "Id:" << i + dwBase << endl;
                              cout << "Address" << pEAt32;
                              cout << "Name" << szFunName;
                        }
                }

      }
      else (cout.write("打开文件失败", 20));
      CloseHandle(hfile);
      delete[] pBuff;
}

void main()
{
      ImageNtHeader(R"(C:\Users\11981\Desktop\Project1\2.exe)");
}
重定位表
Windows使用重定位机制保证代码无论模块加载到哪个基址都能正确被调用
typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress; // 重定位内存页的起始地址RVA
    DWORD   SizeOfBlock; // 重定位块的长度
//WORD    TypeOffset; 偏移12位,类型4位
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
TypeOffset高位字节
#define IMAGE_REL_BASED_ABSOLUTE            0 // 无意义,对齐用
#define IMAGE_REL_BASED_HIGH                  1 // 双字中,仅高16位被修正
#define IMAGE_REL_BASED_LOW                   2 // 双字中,仅低16位被修正
#define IMAGE_REL_BASED_HIGHLOW               3 // 双字32位都需要修正
#define IMAGE_REL_BASED_HIGHADJ               4// 进行基地址重定位时将差值的高16位加到指定偏移处的一个16位域上
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_5    5
#define IMAGE_REL_BASED_RESERVED            6
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_7    7
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_8    8
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_9    9
#define IMAGE_REL_BASED_DIR64               10   // 进行基地址重定位时将差值加到指定偏移处的一个64位域上

//
// Platform-specific based relocation types.
//

#define IMAGE_REL_BASED_IA64_IMM64            9
#define IMAGE_REL_BASED_MIPS_JMPADDR          5// 对MIPS平台的跳转指令进行基地址重定位
#define IMAGE_REL_BASED_MIPS_JMPADDR16      9 // 对MIPS16平台的跳转指令进行基地址重定位
#define IMAGE_REL_BASED_ARM_MOV32             5
#define IMAGE_REL_BASED_THUMB_MOV32         7

重定位表解析
#include<Windows.h>
#include<iostream>
using namespace std;

DWORD RvaToFoa(_In_ DWORD rva, _In_ PIMAGE_SECTION_HEADER p, _In_ PIMAGE_FILE_HEADER f)
{
      for (int i = 0; i < f->NumberOfSections; i++)
      {
                // FOA = 数据的RVA + 区段的RVA - 区段的FOA
                if (rva >= p->VirtualAddress && rva < (p->VirtualAddress + p->Misc.VirtualSize))
                {
                        return rva - p->VirtualAddress + p->PointerToRawData;
                }
                f++;
                p++;
      }
      return 0;
}

void ImageNtHeader(_In_z_ const char* path)
{
      // 获取文件对象
      HANDLE hfile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
      // 获取文件大小
      DWORD fSize = GetFileSize(hfile, NULL);
      char* pBuff = new char;
      DWORD dwReadSize = 0;
      // 读文件
      BOOL bSuccess = ReadFile(hfile, pBuff, fSize, &dwReadSize, NULL);
      if (bSuccess)
      {
                typedef struct _TYPE {
                        WORD Offset : 12;   //大小 2bit 重定位的偏移
                        WORD Tyoe : 4;
                }TYPE, * PTYPE;
                PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuff;
                PIMAGE_NT_HEADERS32 pNtHeader{ 0 };
                pNtHeader = (PIMAGE_NT_HEADERS32)(pDosHeader->e_lfanew + pBuff);
                PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)&pNtHeader->FileHeader;
                PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)&pNtHeader->OptionalHeader;
                PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
                // PIMAGE_DATA_DIRECTORY dataDirectory = (PIMAGE_DATA_DIRECTORY)&pOptionalHeader->DataDirectory;
                PIMAGE_BASE_RELOCATION pRel = (PIMAGE_BASE_RELOCATION)(pBuff + RvaToFoa(pOptionalHeader->DataDirectory.VirtualAddress, pSectionHeader, pFileHeader));
                DWORD dwCount = 0;
                while (*(PLONGLONG)pRel)
                {
                        printf("[%d] VirtualAddress-> SizeOfBlock-> \r\n", dwCount++, pRel->VirtualAddress, pRel->SizeOfBlock);

                        //需要修复重定位项个数
                        DWORD dwRelEntry = (pRel->SizeOfBlock - 8) / 2;

                        //指向重定位项
                        PWORD pRelData = (PWORD)pRel + 8;

                        for (size_t i = 0; i < dwRelEntry; i++)
                        {
                              //判断高4位
                              //32位高4位0011
                              //64位高4位1010
                              if ((pRelData & 0x3000) == 0x3000)
                              {
                                        //低12位 + VirtualAddress为真正需要修复数据的RVA
                                        DWORD dwData = pRelData & 0xFFF;
                                        DWORD dwDataRVA = dwData + pRel->VirtualAddress;
                                        printf("[%d] DATA RVA\r\n", i, dwData, dwDataRVA);
                              }
                        }
                        //指向下一个重定位结构
                        pRel = (PIMAGE_BASE_RELOCATION)((PUCHAR)pRel + pRel->SizeOfBlock);
                }

      }
      else (cout.write("打开文件失败", 20));
      CloseHandle(hfile);
      delete[] pBuff;
}

void main()
{
      ImageNtHeader(R"(C:\Users\11981\Desktop\Project1\2.exe)");
}
​参考文章
https://blog.csdn.net/Gamma_lab/article/details/123869956
https://blog.csdn.net/adam001521/article/details/84658708
查考书
加密与解密
Windows PE权威指南
最后说明
导出和导入表,重定位代码可能有点问题,可以参考别的链接,目前不知道什么原因,有大佬发现可以指出​

metoo2 发表于 2022-5-7 13:34

点赞支持,感谢楼主

metoo2 发表于 2022-5-7 13:42

代码源文件能够一起发一下么?

fz12 发表于 2022-5-7 17:57

metoo2 发表于 2022-5-7 13:42
代码源文件能够一起发一下么?

链接:https://pan.baidu.com/s/16iinLPqRWpcMAQKLUOd8GA
提取码:fkvz
--来自百度网盘超级会员V5的分享

ysysc 发表于 2022-5-13 00:35

谢谢大神,非常的有用。
页: [1]
查看完整版本: 快速查询PE文件知识点和PE文件解析(上)