可选/扩展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:
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的整数倍,并修复重定位数据即可正常运行.
使用OD / DBG打开进程观察入口点
入口点为ImageBase(0x00400000) + AddressOfEntryPoint(0x00015757)
SectionAlignment: 内存中节的对齐粒度,该字段指定了节被装入内存后的对齐单位.Win32的页面大小是4KB,所以Win32PE文件中节的内存对齐粒度一般都为4KB大小,十六进制表示为1000h.SectionAlignment必须大于或等于FileAlignment.当它小于系统页面大小时,必须保证SectionAlignment与FileAlignment相等.
FileAlignment: 文件中节的对齐粒度,Win32PE文件中节的文件对齐粒度一般都为200h,Windows会选择使用512字节的大小(一个物理扇区的大小).
SizeOfImage: 表示内存中整个PE文件的映射尺寸,必须是SectionAlignment的整数倍.(该值可以比实际的值大,但不能比它小).
SizeOfHeaders: 表示了PE文件中所有头+节表按照文件对齐后的大小:
代码计算如下:
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 |
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;
常量定义 | 常量值 | 含义 |
---|---|---|
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);
}
}