专栏首页方亮PE文件和COFF文件格式分析--MS-DOS 2.0兼容Exe文件段

PE文件和COFF文件格式分析--MS-DOS 2.0兼容Exe文件段

        MS 2.0节是PE文件格式中第一个“节”。其大致结构如下:(转载请指明来源于breaksoftware的csdn博客

        在VC\PlatformSDK\Include\WinNT.h文件中有对MS-DOS 2.0兼容EXE文件头的完整定义

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

        这个结构占用0x40个字节,其中我们将主要关注两个成员变量:e_magic和e_lfanew。

       以我xp电脑上notepad为例,我们使用UE打开C:\windows\notepad.exe

        可以发现IMAGE_DOS_HEADER结构中e_magic对应的数据位0x5A4D(MZ),e_lfanew对应的是0x000000E0。这个两个数据是这个结构体中最需要关心的两个成员变量。幻数(Magic Num)这个概念是用于区分一个格式文件的类型,就像一个人的姓,知道你姓啥之后,就可以明确你是不是我们族人。同样,解析这些文件的程序也会去尝试读取这样的幻数,以确认这个文件符合它要求的。在我所知道的一些格式中,他们的幻数往往是这个格式发明者的名称缩写(或者是格式后缀)。我们这个MS-Dos 2.0兼容EXE文件头中的幻数MZ也是纪念他的发明者,可以想到,这个名字应该不是盖茨,因为MZ和Bill Gates(BG)一点也没关系,也不是Paul Allen(PA),更不可能是销售出生的Steve Ballmer。它是Mark Zbikowski,中文翻译是马克·茨柏克沃斯基

        那么为什么PE格式文件会有个Dos文件头呢?Dos系统时代,有两种(我所知道的,我压根没经历过那个年代)可执行文件格式,一种是.exe为后缀的文件,其结构是MZ格式。另一种是以.com为后缀的文件,其结构是COM格式。从Wiki上对MZ格式的介绍可以看出来,MZ格式要比COM格式要新,MZ格式头中包含了重定向信息(本文第一个图中),且其支持可执行体大于64KiB。如今我们电脑上PE可执行文件的后缀也是.exe,为了让该后缀程序在Dos和Nt间有个过渡,我们需要让Dos系统能知道它不能“正确”执行该Exe文件。于是我们PE可执行文件一开始处便插入了一个MS-Dos 2.0兼容Exe文件头,Dos系统加载我们PE文件时,从一开始读取我们文件,发现是“DOS下可执行程序”,于是成功且顺利的执行我们的程序中DOS系统可执行部分,这部分DOS程序输出“该程序不能在DOS上”执行的提示。

        现在我们来看下MS-2.0节结构图和我们结构体的对应关系:

        MS-Dos 2.0兼容Exe文件头   对应于IMAGE_DOS_HEADER中e_magic到e_ovno

        未使用 对应于 e_res[4],虽说这段没使用,但是我还是觉得这段很有意思的。我在做注册表沙箱时,研究了下某公司的沙箱,可是它的沙箱不让regedit.exe进入沙箱运行,于是我就改了e_res[4]这段数据中部分,从而让修改后的regedit.exe在它的沙箱中运行。为什么呢?很容易想象,“MD5+签名”是安全公司一大“安全准绳”。我改了这个没啥用的数据段,不会影响程序运行,但是会使MD5不同,且签名被破坏。这段地址是(文件起始偏移0x1C)

        OEM标志 对应于 e_oemid

        OEM信息 对应于 e_oeminfo

        OEM信息和PE文件头偏移 之间存在一段空白,这段空白对应于 e_res2[10],这段数据和之前e_res[4]一样,改改也无妨。这段地址是(偏移0x28)

        PE文件头偏移 对应于 e_lfanew,其位于0x3C偏移处。

        MS-Dos 2.0占位程序和重定向表和未使用数据段如下图,因为我也没仔细研究过这个结构,所以也不能准确区分出哪块是占位程序,哪块是重定向表,哪块是未使用段。

       从上面的数据我们可以看到,如果我们程序运行在Dos下,会输出“This program connot be run in Dos mode"。

       那么NT系统加载我们的PE可执行程序呢?它不会去执行DOS占位程序,而会跳到PE头位置继续读取和执行。PE头位置就是e_lfanew字段的值,该值是PE头和文件头的之间的偏移量。如本例中就是0x000000E0。我们去该偏移去查看数据

       看到PE了么?这个PE是PE头的Magic Num。我会在之后介绍PE文件头及其相关知识。

       以上是非常常见的MS-DOS 2.0兼容Exe文件段,似乎有点枯燥。那我们现在思考一个问题,应该很有意思的。MS-DOS 2.0兼容Exe文件段是为了程序在DOS环境下运行时提示“不兼容”。但是目前DOS环境真的很少了,似乎我们真的没必要去纠结于我们的程序是否会在DOS下提示“不兼容”,即使在DOS不能运行,也没什么大不了的——反正功能也用不了。那么这么一大块空间,我们是不是可以放点别的?是的,我们可以。举个例子,我电脑上PPTV有个.ax文件叫(.ax文件就是DirectShow Filters的DLL文件)CoreAVC.ax。它就将它的导入表放在这段空间里!

       看到了?导入表是使用了Kernerl32.dll中的LoadLibraryA和GetProcessAddress两个函数。再仔细看,而除了e_magic和e_lfanew两个字段要保证OK外,其他字段和DOS代码空间都可以被利用!那么不禁有人要问,这样做有什么好处呢?首先,减少了PE文件大小(虽然只是那么一点点)。其次,它可以让一些非常强大的分析工具分析出错,比如我电脑上的PE Explorer,因为它足够“较真”,所以它识别不出来该文件的信息。至于原因,我会在之后介绍导入表的时候给出来。这儿再废话几句,研究完PE文件格式,我发现一个道理:标准是标准,即使标准很严谨,但是如果标准实现不完善,那么也会产生各种有趣的漏洞和利用。

       贴一下代码

#define DOSMAGIC     0x5A4D

BOOL CGetPEInfo::IsMzFile() {

    size_t unWordSize = sizeof(WORD);
    ULONG ulFileSize =(ULONG)( m_lpFileEnd - m_lpFileStart );
    if ( ulFileSize < unWordSize ) {
        return FALSE;
    }
    WORD wMagic = 0;

    SafeCopy( &wMagic, m_lpFileStart, unWordSize );
    
    return (DOSMAGIC == wMagic) ? TRUE : FALSE;
}

BOOL CGetPEInfo::GetDOSHeaderInfo() {

    if ( FALSE == IsMzFile() ) {
        return FALSE;
    }

    size_t unDosHeader = sizeof(IMAGE_DOS_HEADER);

    memset( &m_DosHeader, 0, unDosHeader );

    BOOL bSuc = SafeCopy( &m_DosHeader,m_lpFileStart, unDosHeader );

    if ( FALSE == bSuc ) {
        _ASSERT(FALSE);
    }
    else {
        m_dwInfoMask |= DOSHEADER;
    }

    return bSuc;
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • C++拾取——Linux下实测布隆过滤器(Bloom filter)和unordered_multiset查询效率

            布隆过滤器是一种判定元素是否存在于集合中的方法。其基本原理是使用哈希方法将数据映射到一个很长的向量上。在维基百科上,它被称为“空间效率和查询时间...

    方亮
  • 堆问题分析的利器——valgraind的massif

            堆问题也是内存问题的一部分。如果我们发现程序内存一直在增加,怀疑是内存泄漏,则可以使用《内存问题分析的利器——valgraind的memchec...

    方亮
  • 并行计算——OpenMP加速矩阵相乘

            OpenMP是一套基于共享内存方式的多线程并发编程库。第一次接触它大概在半年前,也就是研究cuda编程的那段时间。OpenMP产生的线程运行于C...

    方亮
  • 11.2.0.4 RAC测试环境修改时区

    依据Linux (RHEL)修改时区更改了系统的时区后,集群数据库的各个日志还是显示之前的时区时间。

    Alfred Zhao
  • Git基本命令 -- 基本工作流程 + 文件相关操作

    可以先找一个已经被git管理的项目, 我就使用这个项目吧: https://github.com/solenovex/ID3-Editor 基本工作流程 克隆以...

    solenovex
  • Git push大文件失败解决

    在github上推送时候,貌似单个文件不能超过100M。再加上天朝的墙,给个眼神自己体会。既然是提交大文件导致http postbuffer溢出,将postbu...

    小闫同学啊
  • [AI新知] Azure机器学习正式推出时间序列预测功能

    微软为时间序列预测加入了多项新功能,包括考量时间序列资料的交叉验证,以及将资料加入时间处理,成为额外的资料特征

    阿泽
  • Git比较分支差异的3个命令

    dev和master的所有差异,可以加--left-right参数,指明属于哪个分支的commit。

    dongfanger
  • git分支、标签管理与别名

    分支管理是git比较重要的一个概念,平时用的也比较多。我们先来在本地的仓库里操作一下分支:

    端碗吹水
  • Java每日一题1_关于Spring

    A Spring Core:Core封装包是框架的最基础部分,提供IOC和依赖注入特性

    Java学习

扫码关注云+社区

领取腾讯云代金券