PE文件和COFF文件格式分析——签名COFF文件头和可选文件头2

Posted breaksoftware

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PE文件和COFF文件格式分析——签名COFF文件头和可选文件头2相关的知识,希望对你有一定的参考价值。

        之前的博文中介绍了IMAGE_FILE_HEADER结构,现在来讨论比较复杂的“可选文件头”结构体。(转载请指明来自breaksoftware的csdn博客)先看下其声明

typedef struct _IMAGE_OPTIONAL_HEADER 
    //
    // Standard fields.
    //
    WORD    Magic;
    .
    .
    .
    DWORD   BaseOfData;    // not exist in PE32+
    //
    // NT additional fields.
    //
    DWORD   ImageBase;
    .
    .
    .
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
 IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

        看一下64位版本该结构体

typedef struct _IMAGE_OPTIONAL_HEADER64 
    WORD        Magic;
    .
    .
    .
    DWORD       BaseOfCode;
    ULONGLONG   ImageBase;
    .
    .
    .
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
 IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

         我们观察这个32位版本结构体,可以看到该结构体包含两块数据:Standard fields和NT additional fields。我们可以猜想到,该结构体应该在第一个NT操作系统之前就存在了,只是当时其内容只有Standard fields(以后称为标准域)下的内容,后来NT系统增加了NT additional fields(以后称为扩展域)下元素。

        此处需要特别注意一点,我们看两个在WinNT.h中定义的结构体

typedef struct _IMAGE_NT_HEADERS64 
    DWORD Signature;
    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;

        该结构给出了PE文件头的结构体布局,但是切记,这仅仅是布局。我们千万不要想当然的认为直接从PE头部开始将IMAGE_NT_HEADERS32(64)结构体大小的数据拷贝到该结构体对象中。

memcpy( &ImageNTHeader32,lpPEStart,sizeof(IMAGE_NT_HEADERS32 );// 这是错误的!!

        为什么?因为一个文件中不一定有完整的IMAGE_OPTIONAL_HEADER32(64)结构体对象信息。原因在《可选文件头1》做了介绍,IMAGE_FILE_HEADER中字段SizeOfOptionalHeader指定了该文件中保存的“可选文件头”真实长度,我们应该根据该元素来给IMAGE_OPTIONAL_HEADER32(64)对象赋值。

        我们的文件是使用IMAGE_OPTIONAL_HEADER32还是IMAGE_OPTIONAL_HEADER64结构体呢?可能有人会记起,我们在《可选文件头1》中介绍了判断文件是32位还是64位的方法,我们是否可以通过该判断的结果来判断是哪种结构体呢?最开始我也是这么想的,后来我发现我电脑上Microsoft Visual Studio 10.0\\VC\\lib\\amd64\\Microsoft.VisualC.STLCLR.dll文件是个64位文件但是使用了IMAGE_OPTIONAL_HEADER32结构体!!!是不是很惊讶!我不知道微软这么设计的原因,但是我知道了通过之前判断是否为64位文件来决定可选文件头结构体类型是错误的。那如何判断呢?

        其实是有标记的。紧跟着IMAGE_FILE_HEADER结构体的肯定是IMAGE_OPTIONAL_HEADER32(64)的Magic字段。如果该字段是0x010B,则是使用了IMAGE_OPTIONAL_HEADER32(称为PE32);如果是0x020B,则使用了IMAGE_OPTIONAL_HEADER64(称为PE32+)。切记PE32和PE32+和这个文件是32位文件还是64位文件是没有关系的!它们是两种不同的概念!切记要分清。

BOOL CGetPEInfo::GetOptionalHeader()
    
    CHECKOPHEADER();
    GETFILETYPE();

    size_t unDwordSize = sizeof(DWORD);
    size_t unImgFileHeaderSize = sizeof(IMAGE_FILE_HEADER);
    size_t unImgOpHeaderSize = 0;
    
    LPBYTE lpImgOpHeaderAddr = m_lpPEStart + unDwordSize + unImgFileHeaderSize;
    
    LPVOID lpOpHeaderStart= NULL;

    WORD dwOptionHeader = 0;
    
    if ( FALSE == SafeCopy( &dwOptionHeader, lpImgOpHeaderAddr, sizeof(WORD) ) ) 
        return FALSE;
    

    if ( E64Bit == m_eFileType && PE32MAGICNUM == dwOptionHeader ) 
        // D:\\Microsoft Visual Studio 10.0\\VC\\lib\\amd64\\Microsoft.VisualC.STLCLR.dll
        //_ASSERT(FALSE);
    

    if ( IMAGE_NT_OPTIONAL_HDR32_MAGIC == dwOptionHeader ) 
        // 64位系统文件也存在该格式可选头
        m_eFileOpType  = EOp32;
        unImgOpHeaderSize = sizeof(IMAGE_OPTIONAL_HEADER32);
        lpOpHeaderStart = &m_OptionalHeader32;
    
    else if ( IMAGE_NT_OPTIONAL_HDR64_MAGIC== dwOptionHeader ) 
        m_eFileOpType = EOp32Plus;
        unImgOpHeaderSize = sizeof(IMAGE_OPTIONAL_HEADER64);
        lpOpHeaderStart = &m_OptionalHeader64;
    
    else 
        _ASSERT(FALSE);
        return FALSE;
    

    memset( lpOpHeaderStart, 0 , unImgOpHeaderSize);

    // 根据镜像文件头中可选文件头大小拷贝数据
    BOOL bSuc = SafeCopy( lpOpHeaderStart, lpImgOpHeaderAddr, m_FileHeader.SizeOfOptionalHeader );

    if ( bSuc ) 
        m_dwInfoMask |= OPHEADER;
    
    else 
        _ASSERT(FALSE);
    

	if ( EOp32 == m_eFileOpType ) 
		m_dwFileAlignment = m_OptionalHeader32.FileAlignment;
	
	else 
		m_dwFileAlignment = m_OptionalHeader64.FileAlignment;
	
    return bSuc;

        现在我们将重心放到IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];这个数组元素,我在《可选文件头1》中对此有了点描述,而且我还说可选文件头大小要看这个数组元素的“位置”(而不是个数)来决定的。现在我来细说下。先看下微软的声明

typedef struct _IMAGE_DATA_DIRECTORY 
    DWORD   VirtualAddress;
    DWORD   Size;
 IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

#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

        DataDirectory保存了指向“块信息”的目录信息,其中包括偏移(除了IMAGE_DIRECTORY_ENTRY_SECURITY元素是相对文件偏移RA,其他都是相对虚拟首地址偏移RVA)和大小。如果某文件只包含IMAGE_DIRECTORY_ENTRY_EXPORT(0) 、IMAGE_DIRECTORY_ENTRY_IMPORT(1) 和IMAGE_DIRECTORY_ENTRY_BASERELOC(5)等三个目录,则IMAGE_DIRECTORY_ENTRY_EXCEPTION(2)、IMAGE_DIRECTORY_ENTRY_SECURITY(3)和IMAGE_DIRECTORY_ENTRY_SECURITY(4)的信息都要被填充0。于是IMAGE_FILE_HEADER::SizeOfOptionalHeader所指定的可选文件头大小为DataDirectory之前的元素总大小加上6(最后一个目录IMAGE_DIRECTORY_ENTRY_BASERELOC所在的位置5+1)*sizeof(IMAGE_DATA_DIRECTORY)。这就说明了为什么可选文件头大小是根据目录的位置而不是数量来决定的。

        下篇博文我们将详细说一下IMAGE_OPTIONAL_HEADER32和IMAGE_OPTIONAL_HEADER64中其他元素的意义。

以上是关于PE文件和COFF文件格式分析——签名COFF文件头和可选文件头2的主要内容,如果未能解决你的问题,请参考以下文章

程序员自我修养阅读笔记——Windows PE/COFF

C++学习(四五九)pe elf coff

有没有人能够创建 PE COFF 和 ELF 的混合体?

PE文件基础

PE 学习之路 —— DOS 头NT 头

COFF文件格式