当前位置: 首页 > news >正文

《Windows PE》3.2 PE头结构-DOS头和DOS块

正如我们在初识PE文件一节中看到的,PE文件头中包含几个重要的结构,DOS头、DOS块(DOS Stub)和NT头。NT头就是PE特征码+文件头(COFF 文件标头)+扩展头(可选标头),合称为NT头。这一节我们将详细讲解这几个重要的结构。我们将DOS头和DOS 块合称为MS-DOS 存根。COFF 对象文件(obj)标头由 COFF 文件标头和可选标头组成。

本节必须掌握的知识点:

        DOS

        DOS

        NT

3.2.1 DOS头

DOS头(DOS Header)是可执行文件中的一个数据结构,它是用于支持早期的DOS操作系统的标准格式。DOS头位于可执行文件的开头,包含了一些关于文件的基本信息和可执行程序的入口点。

MS-DOS 存根是在 MS-DOS 下运行的有效应用程序。 它放置在 EXE 映像的前面。 链接器在此处放置默认存根,当映像在 MS-DOS 中运行时,此存根会输出消息“此程序不能在 DOS 模式下运行”。 用户可以使用 /STUB 链接器选项指定不同的存根。

在位置 0x3c,存根具有 PE 签名(PE特征码“PE\0\0”)文件偏移量。 此信息使 Windows 能够正确执行映像文件,即使此文件具有 MS-DOS 存根也不例外。 链接期间,此文件偏移量放在位置 0x3c。

实验九:在winnt.h头文件中查看DOS头、文件头和扩展头的结构定义

在VS中输入#include "winnt.h" ,点击右键,打开文档。然后搜索 IMAGE_DOS_HEADER 或者在程序里面输入IMAGE_DOS_HEADER 按F12转到定义。

IMAGE_DOS_HEADER结构

typedef struct _IMAGE_DOS_HEADER {

    WORD   e_magic;              // DOS 魔数

    WORD   e_cblp;                // 文件的最后一页的字节数

    WORD   e_cp;                  // 文件中的页数

    WORD   e_crlc;                  // 重定位项的数量

    WORD   e_cparhdr;             // 标头的段数

    WORD   e_minalloc;             // 程序所需的最小附加段数

    WORD   e_maxalloc;             // 程序所需的最大附加段数

    WORD   e_ss;                   // 初始堆栈段的相对偏移量

    WORD   e_sp;                  // 初始堆栈指针

    WORD   e_csum;               // 文件校验和

    WORD   e_ip;                   // 初始指令指针

    WORD   e_cs;                  // 初始代码段的相对偏移量

    WORD   e_lfarlc;                 // 重定位表的文件偏移量

    WORD   e_ovno;               // 覆盖号

    WORD   e_res[4];                // 保留字段

    WORD   e_oemid;              // OEM 标识符(用于 e_oeminfo)

    WORD   e_oeminfo;             // OEM 信息;由 e_oemid 指定

    WORD   e_res2[10];              // 保留字段

    LONG   e_lfanew;               // 新的 PE 头的文件偏移量

} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

下面是DOS头中一些重要字段的说明:

●e_magic:这是一个16位的字段,用于表示可执行文件的标识符。对于标准的可执行文件,该字段应为5A4DH,即"MZ"的ASCII码,取自微软开发人员名字。

●e_lfanew:这是一个32位的字段,它表示PE头的偏移量。PE头(Portable Executable Header)是在DOS头之后的一个数据结构,包含了更详细的可执行文件信息。

●e_cblp和e_cp:这两个字段分别表示文件的最后一个页(512字节)的字节数和文件中的页数。

●e_crlc和e_cparhdr:这两个字段分别表示重定位表项数量和标准头的字节数。

●e_minalloc和e_maxalloc:这两个字段分别表示程序所需的最小和最大内存量。

●e_ss和e_sp:这两个字段分别表示程序的初始堆栈段和堆栈指针。

●e_csum:这是一个16位的字段,用于存储文件的校验和。在早期的DOS操作系统中,可以使用该字段进行简单的文件完整性校验。现在已经没什么用了,可以随便改。

●e_ip和e_cs:这两个字段分别表示初始指令指针和代码段。

●e_lfarlc:这是一个16位的字段,表示重定位表的偏移量。

●e_ovno:这是一个16位的字段,用于存储一些附加信息。

       为了方便读者阅读,我们将winnt.h头文件中DOS头结构定义的英文注释改为中文注释。

DOS头是可执行文件格式中的一个重要组成部分,它允许DOS操作系统识别和加载可执行文件。然而,现代的Windows操作系统已经不再依赖DOS头来执行可执行文件,而是使用PE头和其他相关结构来解析和加载可执行文件。虽然如此,Windows PE文件中还是保留了DOS头结构。

●DOS头结构IMAGE_DOS_HEADER中最重要的成员有两个:

1.第一个是e_magic当我们判断一个文件是否为PE文件时,我们需要判断DOS头结构的第一个字段e_magic 为5A4DH (“MZ”),同时NT头的第一个字段Signature为0x00004550(“PE/0/0”)。

2.第二个字段是e_lfanew,这个字段是一个文件内的偏移地址,指向PE头。我们可以根据DOS头中的这个字段查找PE头。

我们以notepad32.exe为例,使用WinHex打开notepad32.exe,如下所示:

00000000   4D 5A 90 00 03 00 00 00  04 00 00 00 FF FF 00 00   MZ............

00000010   B8 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00   ?......@.......

00000020   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................

00000030   00 00 00 00 00 00 00 00  00 00 00 00 E0 00 00 00   ............?..

00000040   0E 1F BA 0E 00 B4 09 CD  21 B8 01 4C CD 21 54 68   ..?.???L?Th

00000050   69 73 20 70 72 6F 67 72  61 6D 20 63 61 6E 6E 6F   is program canno

00000060   74 20 62 65 20 72 75 6E  20 69 6E 20 44 4F 53 20   t be run in DOS

00000070   6D 6F 64 65 2E 0D 0D 0A  24 00 00 00 00 00 00 00   mode....$.......

00000080   EC 85 5B A1 A8 E4 35 F2  A8 E4 35 F2 A8 E4 35 F2   靺[〃?颞?颞??

00000090   6B EB 3A F2 A9 E4 35 F2  6B EB 55 F2 A9 E4 35 F2   k?颟?騥險颟??

000000A0   6B EB 68 F2 BB E4 35 F2  A8 E4 34 F2 63 E4 35 F2   k雋蚧?颞?騝??

000000B0   6B EB 6B F2 A9 E4 35 F2  6B EB 6A F2 BF E4 35 F2   k雓颟?騥雑蚩??

000000C0   6B EB 6F F2 A9 E4 35 F2  52 69 63 68 A8 E4 35 F2   k雘颟?騌ichㄤ5?

000000D0   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................

000000E0   50 45 00 00 4C 01 03 00  87 52 02 48 00 00 00 00   PE..L...嘡.H....

000000F0   00 00 00 00 E0 00 0F 01  0B 01 07 0A 00 78 00 00   ....?.......x..

DOS头结构IMAGE_DOS_HEADER最后一个字段e_lfanew的值为0xE0,再看一下文件偏移地址000000E0处的值刚好是PE特征码0x00004550(“PE/0/0”)。

●下面是给出一段判断是否为PE文件的汇编代码

;检测PE文件是否有效

mov esi,@lpMemory

assume esi:ptr IMAGE_DOS_HEADER               ;esi指向DOS头

;判断是否有MZ字样

.if [esi].e_magic!=IMAGE_DOS_SIGNATURE       ;判断DOS头特征

jmp _ErrFormat

.endif

;调整ESI指针指向PE文件头

add esi,[esi].e_lfanew   

assume esi:ptr IMAGE_NT_HEADERS

;判断是否有PE字样

.if [esi].Signature!=IMAGE_NT_SIGNATURE

jmp _ErrFormat

.endif

练习

请读者使用WinHex打开任意一个PE文件,然后对照IMAGE_DOS_HEADER结构,写出每个结构成员的数值。

3.2.2 DOS Stub

       我们将PE头和DOS头之间的部分称为DOS块(DOS Stub)。DOS Stub(DOS占位程序)是可执行文件中的一段代码,位于PE文件的DOS头和PE头之间。它是为了保持对早期DOS操作系统的兼容性而存在的。

DOS Stub是一个小型的程序或一段指令集,通常是用汇编语言编写的。它的主要作用是在运行可执行文件时,如果操作系统无法识别PE文件格式,或者在非Windows环境下执行,会将执行权转移到DOS Stub上,以提供一些友好的提示信息或执行相关的操作。

DOS Stub通常包含一些文本、图形或其他形式的信息,例如欢迎信息、版本号、作者信息,甚至可以是一段小的动画效果。它的大小通常是固定的,为64字节(16位DOS时代)或128字节(32位DOS时代)。而事实是DOS块的大小并不是固定的,我们可以修改DOS头最后一个字段e_lfanew的值,扩展DOS块。DOS块本身及扩展部分的空间是可以被我们用来存储其他信息的(虽然我们很少这样使用)。

当在DOS环境下执行可执行文件时,DOS操作系统会首先加载DOS Stub并执行它。如果可执行文件是一个有效的PE文件,DOS Stub会在执行完自己的任务后,通过跳转或调用指令将控制权转移到PE头,进而由操作系统继续解析和执行PE文件。

在现代Windows操作系统中,DOS Stub的作用相对较小,因为操作系统已经能够正确识别和解析PE文件格式。然而,为了保持对早期DOS应用程序的兼容性,PE文件仍然保留了DOS Stub。

 注意

1.DOS Stub并非所有的PE文件都必须包含。在一些特殊的情况下,开发人员可以选择不包含DOS Stub,从而使得PE文件更加紧凑。

2.当PE加载器加载PE文件后,PE文件头部所在的页面被设置为只读属性。因此扩展部分只能存放只读数据。通常我们建议DOS块扩展后的大小不能使整个PE文件头部的大小超过400H,否则需要修改下面各个节区内的所有文件偏移地址,造成不必要的麻烦。

实验十:在DOS系统中运行32位PE文件

●第一步:将第一章编写的HelloWorld.exe 32位PE文件拖入WinHex中,观察DOS块的数据如下所示:

00000040   0E 1F BA 0E 00 B4 09 CD  21 B8 01 4C CD 21 54 68   ..?.???L?Th

00000050   69 73 20 70 72 6F 67 72  61 6D 20 63 61 6E 6E 6F   is program canno

00000060   74 20 62 65 20 72 75 6E  20 69 6E 20 44 4F 53 20   t be run in DOS

00000070   6D 6F 64 65 2E 0D 0D 0A  24 00 00 00 00 00 00 00   mode....$.......

00000080   5D 5C 6D C1 19 3D 03 92  19 3D 03 92 19 3D 03 92   ]\m?=.?=.?=.?

00000090   97 22 10 92 1E 3D 03 92  E5 1D 11 92 18 3D 03 92   ?.?=.掑..?=.?

000000A0   52 69 63 68 19 3D 03 92  00 00 00 00 00 00 00 00   Rich.=.?.......

       ●第二步:修改HelloWorld.exe程序名为1234.exe。

32位PE文件需要修改文件名才能在DOSBox虚拟机上运行的原因是因为DOSBox是一个模拟DOS环境的虚拟机,它主要用于运行早期的DOS应用程序和游戏。DOSBox模拟了DOS操作系统的行为和环境,但它实际上是在现代操作系统上运行的。

DOSBox是为了兼容旧的DOS应用程序而设计的,它对32位PE文件的支持相对有限。32位PE文件通常是为Windows操作系统设计的,并使用了Windows特定的API和功能。而DOSBox主要模拟的是早期的DOS环境,因此无法直接运行32位的Windows PE文件。

为了在DOSBox中运行32位PE文件,可以尝试将文件名修改为具有DOS兼容性的8.3格式(最多8个字符的文件名和3个字符的扩展名)。

DOSBox在加载可执行文件时,会根据文件名的扩展名来判断文件类型,并使用相应的处理方式。通过将32位PE文件的文件名修改为DOS兼容的格式,DOSBox会将其识别为DOS应用程序,尽管它实际上是一个32位的Windows PE文件。

 注意

即使将文件名修改为DOS兼容格式,仍然无法保证所有的32位PE文件都能在DOSBox中正常运行。这是因为DOSBox并不是为运行32位Windows应用程序而设计的,它的功能和兼容性有限。在某些情况下,可能需要使用其他工具或虚拟机来运行32位PE文件。

●第三步:验证DOS块内的数据是什么?

       安装DOSBox虚拟机,打开虚拟机后,输入命令:

       mount c d:\code\winpe\ch03

       c:

       将虚拟机C盘根目录对应真实机HelloWorld.exe程序所在的目录。

       第四步:命令行输入1234.exe后,回车运行。如图3-3所示,窗口显示一行提示信息:“This program cannot be run in DOS mode.”。这正是WinHex看到的字符串。

       第五步:使用debug.exe调试器加载1234.exe,然后输入U命名,查看反汇编代码,如图3-4所示。学习过 16位汇编的读者一定非常熟悉。这是一段汇编代码中看到的内容正是我们在WinHex中看到的DOS块中的内容。前面14个字节为一段汇编代码,调用int 21h的9号功能(DX为入口参数,AH为功能号),输出偏移地址0EH处的’$’结尾的字符串,正是我们运行1234.exe程序在窗口显示的提示信息。最后调用int 21h的4CH号功能,入口参数为0,结束程序。

图3-3 在DOS系统上运行32位PE文件

图3-4 使用debug调试器查看反汇编代码

实验十一:使用IDA分析32位PE文件的DOS

       我们还可以使用IDA分析HelloWorld.exe程序的DOS块。

       第一步:将HelloWorld.exe拖入WinHex,将文件偏移地址0x3C处(DOS头指向PE头的e_lfanew字段修改为0),如下所示:

00000000   4D 5A 90 00 03 00 00 00  04 00 00 00 FF FF 00 00   MZ............

00000010   B8 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00   ?......@.......

00000020   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................

00000030   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................

       第二步:将修改后的HelloWorld.exe程序拖入IDA,主窗口显示的反汇编代码如下所示:

seg000:0000 ; File Name   : D:\code\winpe\HelloWorld.exe

seg000:0000 ; Format      : MS-DOS executable (EXE)

seg000:0000 ; Base Address: 1000h Range: 10000h-10450h Loaded length: 450h

seg000:0000 ; Entry Point : 1000:0

seg000:0000

seg000:0000                 .686p

seg000:0000                 .mmx

seg000:0000                 .model large

seg000:0000

seg000:0000 ; ===========================================================

seg000:0000

seg000:0000 ; Segment type: Pure code

seg000:0000 seg000    segment byte public 'CODE' use16

seg000:0000           assume cs:seg000

seg000:0000         assume es:nothing, ss:seg000, ds:nothing, fs:nothing, gs:nothing

seg000:0000

seg000:0000 ; =============== S U B R O U T I N E =======================================

seg000:0000

seg000:0000 ; Attributes: noreturn

seg000:0000

seg000:0000                 public start

seg000:0000 start              proc near

seg000:0000                 push    cs

seg000:0001                 pop     ds

seg000:0002                 assume ds:seg000

seg000:0002                 mov     dx, 0Eh

seg000:0005                 mov     ah, 9

seg000:0007                 int     21h       ; DOS - PRINT STRING

seg000:0007                                  ; DS:DX -> string terminated by "$"

seg000:0009                 mov     ax, 4C01h

seg000:000C                 int     21h       ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)

seg000:000C start           endp              ; AL = exit code

seg000:000C

seg000:000C ; -------------------------------------------------------------------

seg000:000E aThisProgramCan db 'This program cannot be run in DOS mode.',0Dh,0Dh,0Ah

seg000:000E                 db '$',0

seg000:003A                 align 8

       IDA看到的反汇编代码与我们在debug调试器中看到的反汇编代码几乎一致。

实验十二:扩展32位PE文件的DOS

       接下来我们做一个扩展DOS块的实验。仍然以HelloWorld.exe为例,将其重命名为HelloWorld2.exe,拖入WinHex内, 将DOS头的最后一个字段e_lfanew(指向PE头的文件偏移地址)0x00000C8修改为0x00000268,如下所示。

       00000000   4D 5A 90 00 03 00 00 00  04 00 00 00 FF FF 00 00   MZ............

00000010   B8 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00   ?......@.......

00000020   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................

00000030   00 00 00 00 00 00 00 00  00 00 00 00 68 02 00 00   ............h...

       读者可能会有疑问,为何要改为0x00000268大小呢?在前文中有提到,我们可以扩展DOS块的大小,但是扩展后PE文件头部最好不要超过1024个字节,否则我们将改变PE文件头部后面的几个节区在PE文件内的偏移,如此一来就需要修改所有与此相关的偏移,带来很多麻烦。

       注意观察,节表和第一个.text节区之间的空白区域全部为0。这部分空间在文件偏移00000260H~00000400H之间,.text节区的起始地址为00000400H。我们只要保证.text节区的起始地址不变,因此所有节区的地址都不会发生变化。那么DOS块可以扩展的字节数就是400H-260H=1A0H个字节(十进制数416个字节)。

       可以扩展的最大字节数计算出来之后,下一步就是在DOS块内粘贴零字节了。为了部破坏原DOS块的内容,我们选择在PE头特征码的前一个字节的地址处粘贴416个零字节(因为DOS块没有什么用途,我们也可以选在DOS块的任意位置)。

       具体操作方法:

       鼠标选中0xC7地址处,点击鼠标右键,点击“编辑”>“粘贴零字节”,弹出一个对话框窗口,如图3-5所示。填写字节数416,点击“OK”按钮。                    

图3-5 粘贴零字节

      

此时,我们观察一下PE头位于文件0x268偏移地址处。我们将DOS头的最后一个字段e_lfanew(指向PE头的文件偏移地址)0x00000C8修改为0x00000268,删除节表与.text节区间多余的零字节,保持.text节区地址不变。然后点击WinHex

工具栏“保存”按钮。

       最后测试一下HelloWorld.exe是否可以正常运行。

练习

       请读者按照上述实验十、十一、十二的方法分别测试notepad32.exe和notepad64.exe或者其他任意PE文件。

 

结论

       1.不论32位还是64位PE文件都可以扩展DOS块的大小,方法是修改DOS头结构的最后一个字段e_lfanew的值,指向一个新的PE头的文件偏移地址。

       2.为了不改变PE文件头部后面节区的文件偏移地址,扩展后的PE文件头部的大小不超过400H(1024)个字节。


http://www.mrgr.cn/news/39558.html

相关文章:

  • 可视化是工业互联网的核心技术之一,都有哪些应用场景?
  • 基于方差有界的强化学习算法,挖掘稳定 Alpha 因子公式
  • 个人文章合集 - 基础网络相关
  • 浅谈Java之内存缓存
  • 婚恋交友系统该如何做才能做到日进斗金?
  • 火语言RPA流程组件介绍--网页/元素截图
  • TCP的三次握手四次挥手
  • 基于无人机图像的洪水灾害受损评估分割数据集,共4494张高清无人机图像,10个类别,共22GB数据量,主要关注道路,建筑的受损情况。洪水应急救援
  • 全球55%的开发者都在用的分布式云到底怎么样?
  • RTX 5090/5080详细规格曝光 显存喜人 600W功耗没跑
  • 启动服务并登录MySQL9数据库
  • Spring自动装配的5种方式
  • C语言中的日志机制:打造全面强大的日志系统
  • 【数据结构】图的最小生成树
  • 封装了一个iOS水平方向动态宽度layout
  • Python | Leetcode Python题解之第446题等差数列划分II-子序列
  • 与大勇谈修复bug的感想
  • 根父类 Object 的使用与常用方法(equals\toString\clone\finalize\getClass\hashCode....)
  • 基于SSM的会员管理系统【附源码】
  • LeetCode题练习与总结:有效的字母异位词--242