4.PE文件之扩展PE头(IMAGE_OPTIONAL_HEADER)

可选/扩展PE头IMAGE_OPTIONAL_HEADER,它有着比标准PE头(IMAGE_FILE_HEADER)更多的内容.

IMAGE_OPTIONAL_HEADER

结构及成员含义如下:

//大小: 32bit(0xE0) 64bit(0xF0)

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

typedef struct _IMAGE_OPTIONAL_HEADER {

    WORD    Magic;                          //文件类型: 10bh为32位PE文件 / 20bh为64位PE文件
    BYTE    MajorLinkerVersion;             //链接器(主)版本号 对执行没有任何影响
    BYTE    MinorLinkerVersion;             //链接器(次)版本号 对执行没有任何影响
    DWORD   SizeOfCode;                     //包含代码的节的总大小.文件对齐后的大小.编译器填的没用
    DWORD   SizeOfInitializedData;          //包含已初始化数据的节的总大小.文件对齐后的大小.编译器填的没用.
    DWORD   SizeOfUninitializedData;        //包含未初始化数据的节的总大小.文件对齐后的大小.编译器填的没用.(未初始化数据,在文件中不占用空间;但在被加载到内存后,PE加载程序会为这些数据分配适当大小的虚拟地址空间).
    DWORD   AddressOfEntryPoint;            //程序入口(RVA)
    DWORD   BaseOfCode;                     //代码的节的基址(RVA).编译器填的没用(代码节起始的RVA,表示映像被加载进内存时代码节的开头相对于ImageBase的偏移地址,节的名称通常为".text")
    DWORD   BaseOfData;                     //数据的节的基址(RVA).编译器填的没用(数据节起始的RVA,表示映像被加载进内存时数据节的开头相对于ImageBase的偏移地址,节的名称通常为".data")
    DWORD   ImageBase;                      //内存镜像基址
    DWORD   SectionAlignment;               //内存对齐大小
    DWORD   FileAlignment;                  //文件对齐大小
    WORD    MajorOperatingSystemVersion;    //标识操作系统主版本号 
    WORD    MinorOperatingSystemVersion;    //标识操作系统次版本号 
    WORD    MajorImageVersion;              //PE文件自身的主版本号
    WORD    MinorImageVersion;              //PE文件自身的次版本号
    WORD    MajorSubsystemVersion;          //运行所需子系统主版本号
    WORD    MinorSubsystemVersion;          //运行所需子系统次版本号
    DWORD   Win32VersionValue;              //子系统版本的值.必须为0,否则程序运行失败.
    DWORD   SizeOfImage;                    //内存中整个PE文件的映射尺寸.可比实际的值大.必须是SectionAlignment的整数倍
    DWORD   SizeOfHeaders;                  //所有头+节表按照文件对齐后的大小.
    DWORD   CheckSum;                       //校验和.大多数PE文件该值为0.在内核模式的驱动程序和系统DLL中,该值则是必须存在且是正确的.在IMAGEHLP.DLL中函数CheckSumMappedFile就是用来计算文件头校验和的,对于整个PE文件也有一个校验函数MapFileAndCheckSum.
    WORD    Subsystem;                      //文件子系统  驱动程序(1) 图形界面(2) 控制台/DLL(3)
    WORD    DllCharacteristics;             //文件特性.不是针对DLL文件的
    DWORD   SizeOfStackReserve;             //初始化时保留的栈大小.该字段默认值为0x100000(1MB),如果调用API函数CreateThread时,堆栈参数大小传入NULL,则创建出来的栈大小将是1MB.
    DWORD   SizeOfStackCommit;              //初始化时实际提交的栈大小.保证初始线程的栈实际占用内存空间的大小,它是被系统提交的.这些提交的栈不存在与交换文件里,而是在内存中.
    DWORD   SizeOfHeapReserve;              //初始化时保留的堆大小.用来保留给初始进程堆使用的虚拟内存,这个堆的句柄可以通过调用函数GetProcessHeap获得.每一个进程至少会有一个默认的进程堆,该堆在进程启动时被创建,而且在进程的生命期中不会被删除.默认值为1MB.
    DWORD   SizeOfHeapCommit;               //初始化时实践提交的堆大小.在进程初始化时设定的堆所占用的内存空间,默认值为PAGE_SIZE. 
    DWORD   LoaderFlags;                    //调试相关
    DWORD   NumberOfRvaAndSizes;            //目录项数目,默认为10h.
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//结构数组 数组元素个数由IMAGE_NUMBEROF_DIRECTORY_ENTRIES定义
} IMAGE_OPTIONAL_HEADER32, * PIMAGE_OPTIONAL_HEADER32;

通过WinHex定位IMAGE_OPTIONAL_HEADER:

4.PE文件之扩展PE头(IMAGE_OPTIONAL_HEADER)

Magic: 魔术字,表示了PE文件类型,如下表所示:

常量符号 常量值
IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b PE32
IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b PE64
IMAGE_ROM_OPTIONAL_HDR_MAGIC 0x107 ROM

SizeOfCode: 表示了所有代码节的总和(以字节计算),该大小是基于文件对齐后的大小.(判断某个节是否包含代码的方法是根据节的属性中是否含有IMAGE_SCN_CNT_CODE标志).

AddressOfEntryPoint: 表示了程序入口地址,该值是一个RVA,加上Imagebase为程序在内存中的入口地址(OEP).如果在一个可执行文件中附加了一段自己的代码,并且想让这段代码首先被执行,需要修改这里的值指向自己的代码位置.对于普通程序来说它就是启动地址;对于设备驱动程序来说它是初始化函数的地址.入口点对于DLL来说是可选的,如果不存在入口点,这个字段必须设置为0.

ImageBase: 指出PE文件的优先载入内存中的起始地址,如果这个地址已经被占用,操作系统会重新分配(DLL会出现这种情况),这时就需要重定位表提供的数据来修复,EXE首先加载不会出现这种情况,大部分EXE imagebase默认为0x400000. 此值可随便修改,不能超出虚拟地址空间以及必须是64KB的整数倍,并修复重定位数据即可正常运行.

4.PE文件之扩展PE头(IMAGE_OPTIONAL_HEADER)

使用OD / DBG打开进程观察入口点

4.PE文件之扩展PE头(IMAGE_OPTIONAL_HEADER)

入口点为ImageBase(0x00400000) + AddressOfEntryPoint(0x00015757)

SectionAlignment: 内存中节的对齐粒度,该字段指定了节被装入内存后的对齐单位.Win32的页面大小是4KB,所以Win32PE文件中节的内存对齐粒度一般都为4KB大小,十六进制表示为1000h.SectionAlignment必须大于或等于FileAlignment.当它小于系统页面大小时,必须保证SectionAlignmentFileAlignment相等.

FileAlignment: 文件中节的对齐粒度,Win32PE文件中节的文件对齐粒度一般都为200h,Windows会选择使用512字节的大小(一个物理扇区的大小).

SizeOfImage: 表示内存中整个PE文件的映射尺寸,必须是SectionAlignment的整数倍.(该值可以比实际的值大,但不能比它小).

SizeOfHeaders: 表示了PE文件中所有头+节表按照文件对齐后的大小:

4.PE文件之扩展PE头(IMAGE_OPTIONAL_HEADER)

代码计算如下:

int main()
{
  //读取文件二进制数据
  DWORD dwFileSize = 0;
  PCHAR pFileBuffer = FileToMem(FILE_PATH_IN, &dwFileSize);

  PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
  PIMAGE_NT_HEADERS pNth = (PIMAGE_NT_HEADERS)(pFileBuffer + pDos->e_lfanew);
  PIMAGE_FILE_HEADER pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);


  DWORD SizeOfHeader =  pDos->e_lfanew/*包括了IMAGE_DOS_HEADERS + IMAGE_DOS_STUB*/ + \
              4/*IMAGE_NT_HEADER -> Signature*/ + \
              IMAGE_SIZEOF_FILE_HEADER/*IMAGE_FILE_HEADER结构大小*/ + \
              pFil->SizeOfOptionalHeader/*IMAGE_OPTIONAL_HEADER结构大小*/ + \
              pFil->NumberOfSections * sizeof(IMAGE_SECTION_HEADER)/*所有IMAGE_SECTION_HEADER结构大小*/;
  //之后按照文件对齐即可算出SizeOfHeader的大小               
  return 0;
}

Subsystem:子系统标识,定义如下:

常量符号 常量值 常量含义
IMAGE_SUBSYSTEM_UNKNOWN 0 未知子系统
IMAGE_SUBSYSTEM_NATIVE 1 设备驱动程序和Native Windows进程
IMAGE_SUBSYSTEM_WINDOWS_GUI 2 Windows图形用户界面
IMAGE_SUBSYSTEM_WINDOWS_CUI 3 Windows控制台
IMAGE_SUBSYSTEM_OS2_CUI 5 OS/2 控制台
IMAGE_SUBSYSTEM_POSIX_CUI 7 Posix 控制台
IMAGE_SUBSYSTEM_NATIVE_WINDOWS 8 Windows9x 驱动
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 9 Windows CE图形界面
IMAGE_SUBSYSTEM_EFI_APPLICATION 10 可扩展固件接口EFI 应用程序
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER 11 带引导服务的EFI驱动程序
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER 12 带运行时服务的EFI驱动程序
IMAGE_SUBSYSTEM_EFI_ROM 13 EFI ROM映像
IMAGE_SUBSYSTEM_XBOX 14 X- Box
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION 16
IMAGE_SUBSYSTEM_XBOX_CODE_CATALOG 17

4.PE文件之扩展PE头(IMAGE_OPTIONAL_HEADER)

Subsystem = 0x0002(Windows图形用户界面).

DllCharacteristics: 文件属性,每位含义如下:

数据位 常量符号 常量值 为1时含义
0 IMAGE_LIBRARY_PROCESS_INIT 0x0001 保留必须为0
1 IMAGE_LIBRARY_PROCESS_TERM 0x0002 保留必须为0
2 IMAGE_LIBRARY_THREAD_INIT 0x0004 保留必须为0
3 IMAGE_LIBRARY_THREAD_TERM 0x0008 保留必须为0
5 IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA 0x0020 image can handle a high entropy 64-bit virtual address space.
6 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040 DLL可以在加载时被重定位.
7 IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 0x0080 强制代码实施完整性验证
8 IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100 该映像兼容DEP
9 IMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200 可以隔离,但并不隔离次映像
10 IMAGE_DLLCHARACTERISTICS_NO_SEH 0x0400 映像不使用SEH
11 IMAGE_DLLCHARACTERISTICS_NO_BIND 0x0800 不绑定映像
12 IMAGE_DLLCHARACTERISTICS_APPCONTAINER 0x1000 保留必须为0
13 IMAGE_DLLCHARACTERISTICS_WDM_DRIVER 0x2000 该映像为一个WDM Driver
14 IMAGE_DLLCHARACTERISTICS_GUARD_CF 0x4000 保留必须为0
15 IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0x8000 可用于终端服务器

DataDirectory: 结构表示了PE文件中各个目录项信息,是一个数组,结构定义如下:

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress; //目录项对应RVA(VirtualAddress + Imagebase 为目录项在内存中的起始地址) PS(属性证书数据中该字段的值表示的是FOA)
    DWORD   Size;           //目录项对于大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

4.PE文件之扩展PE头(IMAGE_OPTIONAL_HEADER)

常量定义 常量值 含义
IMAGE_DIRECTORY_ENTRY_EXPORT 0 导出表
IMAGE_DIRECTORY_ENTRY_IMPORT 1 导入表
IMAGE_DIRECTORY_ENTRY_RESOURCE 2 资源
IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 异常
IMAGE_DIRECTORY_ENTRY_SECURITY 4 安全证书
IMAGE_DIRECTORY_ENTRY_BASERELOC 5 重定位表
IMAGE_DIRECTORY_ENTRY_DEBUG 6 调试信息
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 版权所有
IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 全局指针
IMAGE_DIRECTORY_ENTRY_TLS 9 TLS 表
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 加载配置
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 绑定导入
IMAGE_DIRECTORY_ENTRY_IAT 12 IAT 表
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 延迟导入
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 COM

代码定位IMAGE_OPTIONAL_HEADER如下:

读取文件代码:

PVOID FileToMem(IN PCHAR szFilePath, OUT LPDWORD dwFileSize)
{
	//打开文件
	FILE* pFile = fopen(szFilePath, "rb");
	if (!pFile)
	{
		printf("FileToMem fopen Fail \r\n");
		return NULL;
	}
 
	//获取文件长度
	fseek(pFile, 0, SEEK_END);			//SEEK_END文件结尾
	DWORD Size = ftell(pFile);
	fseek(pFile, 0, SEEK_SET);			//SEEK_SET文件开头
 
	//申请存储文件数据缓冲区
	PCHAR pFileBuffer = (PCHAR)malloc(Size);
	if (!pFileBuffer)
	{
		printf("FileToMem malloc Fail \r\n");
		fclose(pFile);
		return NULL;
	}
 
	//读取文件数据
	fread(pFileBuffer, Size, 1, pFile);
 
	//判断是否为可执行文件
	if (*(PSHORT)pFileBuffer != IMAGE_DOS_SIGNATURE)
	{
		printf("Error: MZ \r\n");
		fclose(pFile);
		free(pFileBuffer);
		return NULL;
	}
 
	if (*(PDWORD)(pFileBuffer + *(PDWORD)(pFileBuffer + 0x3C)) != IMAGE_NT_SIGNATURE)
	{
		printf("Error: PE \r\n");
		fclose(pFile);
		free(pFileBuffer);
		return NULL;
	}
 
	if (dwFileSize)
	{
		*dwFileSize = Size;
	}
 
	fclose(pFile);
 
	return pFileBuffer;
}

读取扩展PE头数据代码

VOID PrintOpoHeader()
{
	//读取文件二进制数据
	DWORD dwFileSize = 0;
	PCHAR pFileBuffer = FileToMem(FILE_PATH_IN, &dwFileSize);
	if (!pFileBuffer)
	{
		return;
	}

	PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_NT_HEADERS pNth = (PIMAGE_NT_HEADERS)(pFileBuffer + pDos->e_lfanew);
	PIMAGE_FILE_HEADER pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
	PIMAGE_OPTIONAL_HEADER pOpo = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);

	printf("IMAGE_OPTIONAL_HEADER->Magic  [0x%04x] \r\n", pOpo->Magic);
	printf("IMAGE_OPTIONAL_HEADER->MajorLinkerVersion  [0x%02x] \r\n", pOpo->MajorLinkerVersion);
	printf("IMAGE_OPTIONAL_HEADER->MinorLinkerVersion  [0x%02x] \r\n", pOpo->MinorLinkerVersion);
	printf("IMAGE_OPTIONAL_HEADER->SizeOfCode  [0x%08x] \r\n", pOpo->SizeOfCode);
	printf("IMAGE_OPTIONAL_HEADER->SizeOfInitializedData  [0x%08x] \r\n", pOpo->SizeOfInitializedData);
	printf("IMAGE_OPTIONAL_HEADER->SizeOfUninitializedData  [0x%08x] \r\n", pOpo->SizeOfUninitializedData);
	printf("IMAGE_OPTIONAL_HEADER->AddressOfEntryPoint  [0x%08x] \r\n", pOpo->AddressOfEntryPoint);
	printf("IMAGE_OPTIONAL_HEADER->BaseOfCode  [0x%08x] \r\n", pOpo->BaseOfCode);
	printf("IMAGE_OPTIONAL_HEADER->BaseOfData  [0x%08x] \r\n", pOpo->BaseOfData);
	printf("IMAGE_OPTIONAL_HEADER->ImageBase  [0x%08x] \r\n", pOpo->ImageBase);
	printf("IMAGE_OPTIONAL_HEADER->SectionAlignment  [0x%08x] \r\n", pOpo->SectionAlignment);
	printf("IMAGE_OPTIONAL_HEADER->FileAlignment  [0x%08x] \r\n", pOpo->FileAlignment);
	printf("IMAGE_OPTIONAL_HEADER->MajorOperatingSystemVersion  [0x%04x] \r\n", pOpo->MajorOperatingSystemVersion);
	printf("IMAGE_OPTIONAL_HEADER->MinorOperatingSystemVersion  [0x%04x] \r\n", pOpo->MinorOperatingSystemVersion);
	printf("IMAGE_OPTIONAL_HEADER->MajorImageVersion  [0x%04x] \r\n", pOpo->MajorImageVersion);
	printf("IMAGE_OPTIONAL_HEADER->MinorImageVersion  [0x%04x] \r\n", pOpo->MinorImageVersion);
	printf("IMAGE_OPTIONAL_HEADER->MajorSubsystemVersion  [0x%04x] \r\n", pOpo->MajorSubsystemVersion);
	printf("IMAGE_OPTIONAL_HEADER->MinorSubsystemVersion  [0x%04x] \r\n", pOpo->MinorSubsystemVersion);
	printf("IMAGE_OPTIONAL_HEADER->Win32VersionValue  [0x%08x] \r\n", pOpo->Win32VersionValue);
	printf("IMAGE_OPTIONAL_HEADER->SizeOfImage  [0x%08x] \r\n", pOpo->SizeOfImage);
	printf("IMAGE_OPTIONAL_HEADER->SizeOfHeaders  [0x%08x] \r\n", pOpo->SizeOfHeaders);
	printf("IMAGE_OPTIONAL_HEADER->CheckSum  [0x%08x] \r\n", pOpo->CheckSum);
	printf("IMAGE_OPTIONAL_HEADER->Subsystem  [0x%04x] \r\n", pOpo->Subsystem);
	printf("IMAGE_OPTIONAL_HEADER->DllCharacteristics  [0x%04x] \r\n", pOpo->DllCharacteristics);
	printf("IMAGE_OPTIONAL_HEADER->SizeOfStackReserve  [0x%08x] \r\n", pOpo->SizeOfStackReserve);
	printf("IMAGE_OPTIONAL_HEADER->SizeOfStackCommit  [0x%08x] \r\n", pOpo->SizeOfStackCommit);
	printf("IMAGE_OPTIONAL_HEADER->SizeOfHeapReserve  [0x%08x] \r\n", pOpo->SizeOfHeapReserve);
	printf("IMAGE_OPTIONAL_HEADER->SizeOfHeapCommit  [0x%08x] \r\n", pOpo->SizeOfHeapCommit);
	printf("IMAGE_OPTIONAL_HEADER->LoaderFlags  [0x%08x] \r\n", pOpo->LoaderFlags);
	printf("IMAGE_OPTIONAL_HEADER->NumberOfRvaAndSizes  [0x%08x] \r\n", pOpo->NumberOfRvaAndSizes);

	PIMAGE_DATA_DIRECTORY pDir = pOpo->DataDirectory;
	for (size_t i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++)
	{
		printf("IMAGE_DATA_DIRECTORY[%02d]->VirtualAddress  [0x%08x] ->Size [0x%08x] \r\n", i, pDir[i].VirtualAddress, pDir[i].Size);
	}
}

上一篇:艾美捷Exbio丨流式细胞术配套试剂


下一篇:IMAGE_DOS_HEADER解析