前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PE文件和COFF文件格式分析——签名、COFF文件头和可选文件头1

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

作者头像
方亮
发布2019-01-16 09:55:28
1.1K0
发布2019-01-16 09:55:28
举报
文章被收录于专栏:方亮

      本文将讨论PE文件中非常重要的一部分信息。(转载请指明来源于breakSoftware的CSDN博客

        首先说一下VC中对应的数据结构。“签名、COFF文件头和可选文件头”这三部分信息组合在一起是一个叫IMAGE_NT_HEADERS的结构体。

代码语言:javascript
复制
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;

        其中Signature对应于“签名”,FileHeader对应于“COFF文件头”,OptionalHeader对应于“可选文件头”。

        对于PE镜像文件,Signature对应的数据是0x00004550(‘PE\0\0’)。对于如何找到这个位置,在前一篇文章中已经有了解说:从文件头偏移0x3C读取一个DWORD大小的数据,从文件头偏移该数据长度,就到了Signature的起始位置。         看一下COFF文件头结构

代码语言:javascript
复制
typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

        以notepad为例

        Machine字段为0x014C,其对应的信息是“Intel 386或其后续处理器及兼容处理器”。

        NumberOfSections是0x0003,它是个非常重要的字段,表示节的数目。PE文件是由一系列“节”构成的,比较常见的是.text和.data等节,这样的独立的区块是用来存储“代码”、“数据”和“资源”等信息的。如xp上notepad,从数据中我们可以看到它有3个节,我们用其他工具分析得到它确实存在如下3个节。

        TimeDateStamp是0x41107CC3,该字段记录的是文件创建时间离1970年1月1日00:00的秒数。

        PointerToSymbolTable是0x00000000,该字段记录了该PE文件中调试信息符号表。由于符号表信息是在程序运行时不需要加载进入内存的,所以这个偏移使用的是相对文件头偏移RA。目前微软推荐是:将映像文件调试符号表信息独立的放在PDB文件中,所以不会在PE文件中再保存调试符号表信息,于是这个字段应该为0。当然这并不是硬性要求,我发现我电脑上就存在很多该字段不为0的文件。刚开始时我也不是很明白它们为什么要使用这个字段,特别是其指向的字符表个数(NumberOfSymbols)为0!!你说既然大小为0,那你指向有什么意思呢?其实这种设计是非常有深意的,我会在之后的章节中介绍这种深意。

        NumberOfSymbols是0x00000000,该字段记录了该PE文件中调试信息符号表元素个数。对于映像文件,该字段为0(非硬性要求),,理由在PointerToSymbolTable中已经说明。通过NumberOfSymbols和PointerToSymbolTable,我们可以找到字符串表起始位置,因为字符串表紧跟在符号表之后。

        SizeOfOptionalHeader是0x00E0,该字段用于描述“可选文件头”的大小。之后会看到“可选文件头”的中有个具有16个元素是数组,该数组保存了一系列“块信息”,但是并不是所有文件都有全部的“块信息”,于是链接器在链接生成PE文件时,也是根据实际存在的“块信息”位置(以后会说明为什么是位置而不是数量)去填充这个数组的。也就是说我们可能只是填充了1个元素,而剩下的15个元素直接被砍掉,而不是在内存中使用0来填充。

        这儿就引入一个问题,就是我们不能从“签名”位置开始,就直接memcpy一段IMAGE_NT_HEADERS大小的空间到一个IMAGE_NT_HEADERS对象中。因为“可选文件头”还要看“COFF文件头”中的SizeOfOptionalHeader数据。

        Characteristics字段用于标记该文件属性,notepad.exe该字段值为0x010F。下面我们来解释下该组合属性

标志

说明

IMAGE_FILE_RELOCS_STRIPPED

0x0001

仅适用于映像文件。它表明此文件不包含机制重定位信息,于是它只能被加载到其首选基地址。如果首选基地址不可用,则加载器会报错。链接器默认会移除可执行文件中的重定位信息。一般情况下,Exe文件会设置该值(如notepad.exe,但ntoskrnl.exe就没设置),而因为DLL文件为了其良好的兼容性是不会去设置这个值的(如Kernel32.dll、User32.dll等)。

IMAGE_FILE_EXECUTABLE_IMAGE

0x0002

仅适用于映像文件。它用于表明该文件是合法的,可以被运行。如果没有设置,则代表链接出现问题。这个一般都会设置。

IMAGE_FILE_LINE_NUMS_STRIPPED

0x0004

COFF行号信息已经被移除。不赞成使用该标志。但是我发现notepad.exe、Kernel32.dll、User32.dll等都设置了该标志。而一般我们编译的PE文件是不设置该项的。

IMAGE_FILE_LOCAL_SYMS_STRIPPED

0x0008

COFF符号表中有关局部符号的项已经被移除。不赞成使用该标志。但是我发现notepad.exe、Kernel32.dll、User32.dll等都设置了该标志。而一般我们编译的PE文件是不设置该项的。

IMAGE_FILE_AGGRESSIVE_WS_TRIM

0x0010

该标志已经被废弃。

IMAGE_FILE_LARGE_ADDRESS_ AWARE

0x0020

应用程序可以处理大于2GB的地址。

0x0040

为未来保留的字段。

IMAGE_FILE_BYTES_REVERSED_LO

0x0080

小尾,LSB在MSB前面。不赞成使用该标志。windows xp就是小尾。

IMAGE_FILE_32BIT_MACHINE

0x0100

适用于32位系统。我的xp系统上DLL和Exe文件基本都设置了该标志。

IMAGE_FILE_DEBUG_STRIPPED

0x0200

调试信息已经从该映像文件中移除。

IMAGE_FILE_REMOVABLE_RUN_ FROM_SWAP

0x0400

如果该文件是在移动介质上,需要将其完全加载到交换文件中。

IMAGE_FILE_NET_RUN_FROM_SWAP

0x0800

如果该文件是在网络介质上,需要将其完全加载到交换文件中。

IMAGE_FILE_SYSTEM

0x1000

该映像文件是一个系统文件,不是一个用户文件。

IMAGE_FILE_DLL

0x2000

此文件是DLL文件。

IMAGE_FILE_UP_SYSTEM_ONLY

0x4000

该文件仅能运行于单处理机器上。

IMAGE_FILE_BYTES_REVERSED_HI

0x8000

大尾,LSB在MSB后面。

        我观察了我系统上几个文件,发现以下规律:

       1 Sys和Exe的该属性为0x010E或者0x010F。

       2 DLL文件该属性一般为0x210E。DLL文件一般不会设IMAGE_FILE_RELOCS_STRIPPED(0x0001),因为它为了良好的兼容性,不能设置它必须要被加载的地址。一个Exe可能会加载多个DLL,如果系统“不小心”把某个DLL加载到0x70000000,那么如果有某个DLL设置了IMAGE_FILE_RELOCS_STRIPPED并将其首选加载地址正好也设置为0x70000000,那么系统为该Exe加载这个DLL将会失败。但是的确存在这样的文件,比如我电脑上ResourceCache.dll。DLL文件肯定要设置IMAGE_FILE_DLL。所以即使某个DLL文件的后缀名改了,你可以结合这个“特征码”来还原其真面目。

       这儿我还要说一个认知的误区。 IMAGE_FILE_32BIT_MACHINE标志可以用于标志这个文件是适用于32位系统,但是如果仅仅通过该标志就去鉴别这个文件是32位文件还是64位文件是不正确的。我也不知道微软为什么设计了该标志而没有严格限制这个标志。我通过扫描我电脑里所有文件,发现了一个可能具有指导性的鉴别策略:

       1 如果没有设置 IMAGE_FILE_32BIT_MACHINE但是设置了IMAGE_FILE_LARGE_ADDRESS_ AWARE的文件是64位文件。没有设置IMAGE_FILE_32BIT_MACHINE意味着该文件可能是64位程序,而设置了IMAGE_FILE_LARGE_ADDRESS_ AWARE,则说明该文件可以处理大于2G的空间的内存,则该文件是64位文件。如我本机上wwst64.exe。

       2 除了以上判断之外的其他可能标志该文件是32位文件。

          比如设置了IMAGE_FILE_32BIT_MACHINE而没有设置IMAGE_FILE_LARGE_ADDRESS_ AWARE,则说明这个文件可以处理2G以内内存空间,是32位文件;

          比如没有设置IMAGE_FILE_32BIT_MACHINE和IMAGE_FILE_LARGE_ADDRESS_ AWARE,怎么解释呢?反正它不是64位文件,因为不能处理大于2G内存空间,那它只能是32位文件了。如我本机上文件sqlite3.dll。

         比如设置了IMAGE_FILE_32BIT_MACHINE和IMAGE_FILE_LARGE_ADDRESS_ AWARE,那说明这是个可以处理大于2G内存空间的32位文件。如我本机上AcroBroker.exe。

代码语言:javascript
复制
BOOL CGetPEInfo::GetFileType() {
    CHECKFILETYPE();
    GETFILEHEADER();

    m_eFileType = E32Bit;

	m_bIsDllFile = ( m_FileHeader.Characteristics & IMAGE_FILE_DLL ) ? TRUE : FALSE;

    if ( !( IMAGE_FILE_32BIT_MACHINE & m_FileHeader.Characteristics ) 
        && !( IMAGE_FILE_LARGE_ADDRESS_AWARE & m_FileHeader.Characteristics ) ) {
            //_ASSERT(FALSE);
    }
    else if ( !( IMAGE_FILE_32BIT_MACHINE & m_FileHeader.Characteristics ) 
        && ( IMAGE_FILE_LARGE_ADDRESS_AWARE & m_FileHeader.Characteristics ) )
    {
        m_eFileType = E64Bit;
    }
    else if ( ( IMAGE_FILE_32BIT_MACHINE & m_FileHeader.Characteristics ) 
        && ( IMAGE_FILE_LARGE_ADDRESS_AWARE & m_FileHeader.Characteristics ) ) {
    }
    return TRUE;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2012年07月19日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档