深入学习PE文件结构系列一DOS-Header-DOS-Stub-Rich-Header
0x1 前言
Windows终端攻防越深入学习越发现基础的重要性,之前只是零散的学习Windows PE知识,趁着现在工作之余还有时间精力,准备系统的学习下Windows PE,本篇文章开始,会进行PE文件结构深度学习系列,最后会用这些知识实现一个PE Parser,如果你也能跟着这个系列学完,我相信你回头再看PE Loader、PE Packer、PE Injection、Process Hollowing、RDI等等,会有不一样的视角
0x2 PE结构概览
PE全称Portable Executable,是Windows中使用的可执行文件格式,它基于COFF文件格式(Common Object File Format)修改而来,准确的说PE文件格式是对COFF文件格式的扩展,其中PE File Header部分就是标准的COFF文件格式(后面会提到)
PE文件不只是EXE文件,还有DLL(动态链接库)、SRV(内核模块)、CPL(控制面板项)、等等
一个PE文件需要包含特定的内容,Windows PE Loader才能正确解析它,并将它载入内存,通常的PE文件结构如下图
我们用PE-bear打开一个实际的PE文件,可以看到结构如下
上图所示,一个PE文件的主要部分包括:DOS Header、DOS Stub、NT Headers、Section Table(我更习惯称之为Section Headers,后面多使用Section Headers)、Section
DOS Header
每个PE文件都以一个64字节大小的结构体开始,这个64字节大小的结构体称之为DOS Header(DOS头),因为它的存在,PE文件也叫MS-DOS可执行文件
DOS Stub
DOS Header之后就是DOS Stub(DOS存根),它从第65个字节开始,是一个兼容MS-DOS 2.0的可执行文件,它的作用就是,当被运行在DOS模式下,输出一个消息”This program cannot be run in DOS mode”,然后退出
NT Headers
NT Headers(NT头)包含3部分:
- Image File Signature(镜像文件签名,我更习惯称之为PE File Signature,PE文件签名,后面多使用PE File Signature):一个4字节的内容,标识这是一个PE文件
- Image File Header(镜像文件头,我更习惯称之为PE File Header,PE文件头,后面多使用PE File Header):是一个标准的COFF文件头,本质是一个结构体,里面包含PE文件相关信息
- Image Optional Header(镜像可选头,我更习惯称之为PE Optional Header,PE可选头,后面多使用PE Optional Header):这部分叫PE可选头是因为像Object File这样的对象文件没有这部分,但像EXE这样的可执行文件必须得有这部分,它可以说是PE文件结构中最重要的部分
Section Table
紧随PE Optional Header之后就是Section Table(节表,我更习惯称之为Section Headers,节头,后面多使用Section Headers),它是一个结构体数组,每个结构体装有特定数据的相关信息,比如导出表、导入表、重定位表等等
Sections
节是PE文件实际存储数据的地方,包括代码、数据、资源等等
DOS Header
DOS Header是从PE文件起始位置开始的一个64字节大小的结构体,,在Windows中,它的存在只是为了向后兼容,由于它和DOS Stub的存在,使得PE文件也是一个MS-DOS下可执行文件,当它运行在MS-DOS下时,DOS Stub被执行,输出一条消息并退出(PE文件中的其他部分不会被执行),没有它们的话,PE文件在MS-DOS下执行将会报错
DOS Header的结构体定义位于winnt.h中,定义如下
1 | typedef struct _IMAGE_DOS_HEADER { |
多数字段我们不用关注,只需要关注其中2个字段就可以了
- e_magic:DOS头的第一个成员,类型为WORD表示大小是2字节,它有一个固定值0x5A4D,对应的ASCII是”MZ”,它的作用可以理解为MS-DOS下可执行文件标识
- e_lfanew:DOS头的最后一个成员,位于DOS Header偏移0x3C处,它中的值是NT Headers的起始地址,PE Loader也是通过它获取真正PE部分的起始地址
下图展示了一个实际PE文件中的DOS Header部分
可以看到第一个成员Magic Number的值为0x5A4D,最后一个成员位于DOS Header偏移0x3C处,它中的值0x78正是真正PE部分的起始地址
DOS Stub
DOS Stub本质是一段MS-DOS可执行程序,用来输出一个错误消息并退出,当PE文件运行在DOS模式下,会输出默认消息”This program cannot be run in DOS mode”,提示这个PE文件不能运行在DOS模式下,需要注意,这个消息的内容是可以修改的
DOS Stub不需要了解太多,我们来做点好玩的,试着逆向它
将它从PE-bear中拷贝出来,然后粘贴到010 editor中,并创建一个新的文件,使用IDA打开后可以看到程序的汇编代码
如果你学习过Windows16位汇编,你会发现代码并不复杂
1 | push cs |
- 代码段寄存器cs中的值入栈,再将栈顶的值(代码段寄存器cs的值)赋给数据段寄存器ds,也就是说让数据段寄存器的起始地址等于代码段寄存器的起始地址
1
2
3mov dx, 0eh
mov ah, 9
int 21h - int 21h是DOS下的中断处理(就类似于Windows下的API调用),具体执行哪种类型的中断处理(API调用)取决于ah中的值,这里给ah赋值的9表示执行输出到屏幕功能,而输出的内容就是dx中的值,将0eh赋值给dx就是将那个字符串的地址赋值给dx
1
2mov ax, 4c01h
int 21h - 这两个语句也同理,0x4c表示的含义是退出函数,放在ax的高16位ah中,0x01表示函数退出代码,放在ax的低16位al中,所以ax的值是0x4c01,结合到一起,执行退出函数的中断处理,并返回值1
完整的DOS API调用参考可以查看这里:https://en.wikipedia.org/wiki/DOS_API
Rich Header
在DOS Stub和NT Headers之间,其实还有一段数据,叫Rich Header(富有头),通常使用微软Visual Studio构建的exe才会有这个头,里面的数据用于表示VS的名字、类型、版本等等
很多讲述PE文件结构的文章都没有提到这个头,其实Rich Header并不是PE文件的标准内容,就是说,将这部分完全填充为0也不会影响PE文件,说到这里有没有想到什么?我们可以将PE文件内嵌的内容除了放到资源节,还可以放到这里,其实已经有恶意软件这么做了
在2018年冬奥会期间有一款名为Olympic Destroyer的恶意软件,这款恶意软件以伪造其他恶意软件而闻名,比如在Rich Header中伪造Lazarus threat group组织的特征,具体分析可参考:https://securelist.com/the-devils-in-the-rich-header/84348/
Rich Header中首先是一系列异或加密的数据,然后是Rich Header签名,最后是一个校验值,这个校验值也是异或加密的密钥
下图中可以看到Rich Header中主要是微软VS编译工具的相关信息
参考链接
https://0xrick.github.io/win-internals/pe1/
https://0xrick.github.io/win-internals/pe2/
https://0xrick.github.io/win-internals/pe3/
