目录
PointerToSymbolTable & NumberOfSymbols
MajorLinkerVersion & MinorLinkerVersion
SizeOfInitializedData & SizeOfUninitializedData
MajorOperatingSystemVersion & MinorOperatingSystemVersion
MajorImageVersion & MinorImageVersion
MajorSubsystemVersion & MinorSubsystemVersion
SizeOfStackReserve & SizeOfStackCommit
SizeOfHeapReserve & SizeOfHeapCommit
PointerToRelocations & NumberOfRelocations
PointerToLinenumbers & NumberOfLinenumbers
工具:Notepad++ 插件 Hex-Edit
IMAGE_DOS_HEADER_STRUCT
0x0h - 0x3fh: 这部分被称为IMAGE_DOS_HEADER_STRUCT,这部分是DOS块,DOS能根据此识别出这是一个有效的执行体。
IMAGE_DOS_HEADER_STRUCT{
word e_magic; //文件标志,“MZ”
word e_cblp;
word e_cp;
word e_crlc;
word e_cparhdr;
word e_minalloc;
word e_maxalloc;
word e_ss;
word e_sp;
word e_csum;
word e_ip; //DOS代码入口IP
word e_cs; //DOS代码入口CS
word e_lfarlc;
word e_ovno;
word 4 dup(?) e_res;
word e_oemid;
word e_oeminfo;
word 10 dup(?) e_res2;
word e_lfanew; //指向PE文件头
}
e_lfanew
这个结构体描述了在DOS环境运行的一些信息,但在DOS环境运行,会提示“This program cannot be run in DOS mode”。结构体字段最后一个双字指向PE文件头,这个才是真正PE文件信息的起始点。因为Intel CPU属于小端序,所以读出来是 0x108h。
IMAGE_NT_HEADER
PE头的结构体叫IMAGE_NT_HEADER,在接触的windows知识当中会经常遇到“NT”,这个是New Technology的简称。这个结构体分两个版本,32位和64位,但大同小异。下面以32位的举例
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
Signature
可以看到PE头含有“PE”两个字母,也就是 0x45 0x50 0x0 0x0,对应这个Signature签名
IMAGE_FILE_HEADER
接下来看IMAGE_FILE_HEADER 这个结构体
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //运行平台
WORD NumberOfSections; //文件的区块数
DWORD TimeDateStamp; //文件创建日期和时间
DWORD PointerToSymbolTable; //指向符号表
DWORD NumberOfSymbols; //符号表中符号的个数
WORD SizeOfOptionalHeader; //IMAGE_OPTIONAL_HEADER32结构的大小
WORD Characteristics; //文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Machine
从字段可以读出运行平台:0x8664h,这个字段在winnt.h中都有定义,如下所示,这个是运行在AMD64平台
#define IMAGE_FILE_MACHINE_UNKNOWN 0
#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_CEE 0xC0EE
NumberOfSections
文件区块数有6个,对应的区块信息就紧跟在该头的后面,此处的区块数名称常用有.text .data等
TimeDateStamp
文件创建时间是0x624eac73 这个值是以自1970年1月1日以来的格林威治时间计算的秒数,用时间戳转换工具查看和资源管理器对比,相差无几,对比如下
PointerToSymbolTable & NumberOfSymbols
由于COFF符号表在PE文件中比较少见了,此处显示没有符号表存在,同理,下面的字段符号表符号数目也是为0。
SizeOfOptionalHeader
表示IMAGE_OPTIONAL_HEADER结构体的大小,取决于当前文件是32位还是64位,32位一般是0x00E0h,64位一般0x00F0h,我这里显示0x00F0h,编译的也正是x64 release版本
Characteristics
表示该PE文件的属性,字段是通过winnt.dll里一些有效值运算得到的,片选如下
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved external references).
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Aggressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // Debugging info stripped from file in .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // If Image is on removable media, copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // If Image is on Net, copy and run from the swap file.
#define IMAGE_FILE_SYSTEM 0x1000 // System File.
#define IMAGE_FILE_DLL 0x2000 // File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // Bytes of machine word are reversed.
根据我这里获取的0x22,从中表示含义是,该软件是一个exe可执行文件,可以处理大于2GB的地址。
IMAGE_OPTIONAL_HEADER
该结构定义了PE文件更多的信息。前面已经有字段SizeOfOptionnalHeader 表示该结构体大小,且32位和64位大同小异,故下面通过IMAGE_OPTIONAL_HEADER64结构了解PE信息。
typedef struct _IMAGE_OPTIONAL_HEADER64 {
WORD Magic; //标志字
BYTE MajorLinkerVersion; //链接器主版本号
BYTE MinorLinkerVersion; //链接器次版本号
DWORD SizeOfCode; //所有含有代码的区块的大小
DWORD SizeOfInitializedData; //所有初始化数据区块的大小
DWORD SizeOfUninitializedData; //所有未初始化数据区块的大小
DWORD AddressOfEntryPoint; //程序执行入口RVA
DWORD BaseOfCode; //代码区块起始RVA
ULONGLONG ImageBase; //程序默认载入基地址 32位此处表示数据区块起始RVA
DWORD SectionAlignment; //内存中区块的对齐值
DWORD FileAlignment; //文件中区块的对齐值
WORD MajorOperatingSystemVersion; //操作系统的主版本号
WORD MinorOperatingSystemVersion; //操作系统的次版本号
WORD MajorImageVersion; //用户自定义主版本号
WORD MinorImageVersion; //用户自定义次版本号
WORD MajorSubsystemVersion; //所需子系统主版本号
WORD MinorSubsystemVersion; //所需子系统次版本号
DWORD Win32VersionValue; //保留,设置为0
DWORD SizeOfImage; //映像载入内存后的总尺寸
DWORD SizeOfHeaders; //MS-DOS头部,PE头部,区块表总的大小
DWORD CheckSum; //映像校验和
WORD Subsystem; //文件子系统
WORD DllCharacteristics; //显示DLL特性的旗帜
ULONGLONG SizeOfStackReserve; //初始化时栈的大小 32位此处用的DWORD
ULONGLONG SizeOfStackCommit; //初始化时实际提交栈的大小 32位此处用的DWORD
ULONGLONG SizeOfHeapReserve; //初始化时保留堆的大小 32位此处用的DWORD
ULONGLONG SizeOfHeapCommit; //初始化时实际保留堆的大小 32位此处用的DWORD
DWORD LoaderFlags; //与调试相关,默认为0
DWORD NumberOfRvaAndSizes; //数据目录表的项数
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录表
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
Magic
从该例中,读出为0x020Bh。这个字段说明文件是ROM映像(0107h)还是普通的可执行的映像(010Bh),如果是64位则是020Bh。这个字段也说明了该exe是64位的。
MajorLinkerVersion & MinorLinkerVersion
标志链接程序的版本号为 0E-10,一般没啥大用。
SizeOfCode
有IMAGE_SCN_CNT_CODE属性的区块总大小,向上对齐某个值的整数倍,本例是0x1FE00h。大多时候文件只有一个Code块,那么这个字段和.text块的大小匹配(.text块一般存放代码)。
SizeOfInitializedData & SizeOfUninitializedData
已经初始化数据库的大小,本例中为 0x1AA00h。未初始化数据区块的大小为0。
AddressOfEntryPoint
程序执行入口RVA。本例中是0x0076B,这个地址并不是指向Main、WinMain或DllMain函数的地址,而是运行时的库代码,由它来调用Main函数。
BaseOfCode
代码区块起始RVA,本例中是0x1000h。在内存中,代码段通常是PE文件头之后,数据块之前,这个值通常是0x1000h。
ImageBase
程序默认载入基地址,此处是0x0140000000h。如果没有其他文件占用这块地址,那么加载器就不需要基址重定位了。
SectionAlignment
内存中区块的对齐值,本例为0x1000h,每个区块被载入的地址必定是该对齐值得整数倍。
FileAlignment
磁盘上PE文件中区块的对齐值,本例是0x200h。这个是为了保证块总是从磁盘的扇区开始。
MajorOperatingSystemVersion & MinorOperatingSystemVersion
要求操作系统的主版本号,次版本号。这个字段已经不那么有用了。
MajorImageVersion & MinorImageVersion
用户自定义主版本号,次版本号。本例中为0,什么都没有设置。
MajorSubsystemVersion & MinorSubsystemVersion
所需子系统主版本号,次版本号。
Win32VersionValue
保留,设置为0。一直有个疑问,微软很多时候都会定义结构体存在保留字段,这是为了后续新增功能,还是为了字节对齐?
SizeOfImage
映像载入内存后的总尺寸,最后一个块根据其大小向上取整,本例为0x03E000h,换算成十进制是253952字节,用Explorer查看文件大小为233984字节。
SizeOfHeaders
MS-DOS头部,PE头部,区块表总的大小,该值会四舍五入到文件对齐值得倍数,本例是0x400h,文件对齐值前面说过是0x200h。
CheckSum
映像校验和。一般EXE文件该值为0,但内核模式得驱动程序和系统DLL必须有一个校验和。本例中是为0。
Subsystem
可执行文件所期望的子系统枚举值。该字段只对EXE有用。本例中是3,字符子系统。该exe采用输出控制台程序生成。
#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.
DllCharacteristics
显示DLL特性的,表示DLLMain函数何时被调用,默认为0。本例中是0x8160。
#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
SizeOfStackReserve & SizeOfStackCommit
初始化时栈的大小 以及 初始化时实际提交栈的大小,本例中分别是 0x100000h和0x1000h。一般来说在exe文件里为线程保留栈的大小不会全部提交,一开始只提交其中一部分,有必要时候菜提交剩下的,默认一开始提交4KB。
SizeOfHeapReserve & SizeOfHeapCommit
初始化时保留堆的大小 和 初始化时实际保留堆的大小,默认值分别为1MB 和 4kb。本例就是默认值。
LoaderFlags
与调试相关,默认为0。
NumberOfRvaAndSizes
数据目录项数,从Windows NT发布以来,这都是16。本例也是16。
DataDirectory
数据目录表。该表成员结构指向了输出表,输入表,资源块等数据。其结构体如下所示。
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //数据块起始RVA
DWORD Size; //数据块长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
IMAGE_SECTION_HEADER
紧跟IMAGE_NT_HEADERS后的就是 IMAGE_SECTION_HEADER结构数组,也就是区块表。它包含了所管理区块的位置,长度,属性等信息。
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //8字节的区块名
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;
Name[IMAGE_SIZEOF_SHORT_NAME]
从本例中可以看出,该区块名字叫.text
VirtualSize
表示实际被使用区块的大小,本例中是0x1FDECh。这个值是在进行对齐前区块的实际大小。
VirtualAddress
区块被装载到内存中的RVA,该值要求是SecitonAlignment的整数倍,在本例中是0x1000h。
SizeOfRawData
在文件中对齐后的尺寸,也就是该块在磁盘文件中所占空间,该字段是已经经过FileAlignment对齐过后,调整的长度。本例中是0x1FE0h。
PointerToRawData
在磁盘文件中的偏移。如果程序自装载PE或者COFF文件,那么必须用线性映像方法载入文件,需要找到该块数据,故此字段被使用。本例中是0x4000h。
PointerToRelocations & NumberOfRelocations
这两个字段在obj文件中使用,分别表示重定位信息的偏移,重定位表的重定位数目,在本例中都为0。
PointerToLinenumbers & NumberOfLinenumbers
这两个关于行号表的信息,分别表示行号表在文件中的偏移,行号表中行号数目。在本例中都为0。
Characteristics
区块的属性。比如说,可读,可写,可执行。本例中是0x60000020h,翻译过来就是可读,可执行,包含代码。
#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_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.
参考文献:
段钢.加密与解密第四版[M].北京:电子工业出版社,2021.