[原创] PE 文件结构剖析之 MS-DOS 头
MS-DOS 头PE 文件中的 MS-DOS 头是微软出于兼容性考虑而保留下来的,MS-DOS 头也称为 DOS 头,紧接它后面的是 DOS Stub,当 PE 文件运行在 MS-DOS 系统时,会执行 DOS Stub 中的内容,不会出现不可预料的错误。
DOS 头结构在 winnt.h 中有定义,是一个 64 字节长的结构体:
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; // 1) Magic number
WORD e_cblp; // 2) Bytes on last page of file
WORD e_cp; // 3) Pages in file
WORD e_crlc; // 4) Relocations
WORD e_cparhdr; // 5) Size of header in paragraphs
WORD e_minalloc;// 6) Min extra paragraphs needed
WORD e_maxalloc;// 7) Max extra paragraphs needed
WORD e_ss; // 8) Initial (relative) SS value
WORD e_sp; // 9) Initial SP value
WORD e_csum; // 10) Checksum
WORD e_ip; // 11) Initial IP value
WORD e_cs; // 12) Initial (relative) CS value
WORD e_lfarlc;// 13) Offset of relocation table
WORD e_ovno; // 14) Overlay number
WORD e_res;// 15) Reserved words
WORD e_oemid; // 16) OEM identifier
WORD e_oeminfo; // 17) OEM information
WORD e_res2;// 18) Reserved words
LONG e_lfanew;// 19) Offset of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
大部分字段对 MS-DOS 系统中很重要,如果想在 MS-DOS 下执行一些操作,那就必须全面了解,但在 Windows 系统中就只需要了解 3 个字段就足够了,一个是 e_magic,一个是 e_lfanew,次重要字段为 e_lfarlc:
[*]e_magic:这是结构体中第一个字段,类型为 WORD,占用 2 个字节,称为魔数,为固定值 0x5A4D (用 IMAGE_DOS_HEADER 宏表示),该字段表明这是一个 MS-DOS 下的可执行文件;
[*]e_lfanew:PE 头的真正偏移,可进一步判断这个可执行文件是否为 PE 文件;
[*]e_lfarlc:重定位表偏移,意为 DOS Stub 的起始偏移地址,由此往前推 4 个字节便是指向 e_lfanew 的地址,但大多数人只记住 e_lfanew 的偏移地址 0x3C;
现在就来实际看一下吧,推荐使用 010Editor,这是一款功能强大的十六进制编辑软件,打开 PE 文件后会去加载 PE 解析模板然后自动解析结构:
相关字段偏移说明在图中已标出,右边红框框住的一大块为 DOS Stub。
需要注意的是,以 0x80 处分界后半部分严格来说是不属于 DOS Stub 的,也不属于 PE 的,它有一个名称,名为 Rich Header,前半部分里面包含了一个字符串 This is program cannot be run in DOS mode。将前半部分内容另存为一个 bin 文件,
0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68
69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F
74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20
6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00
用 IDA 打开
选择 No (16 位模式),反编译后为:
汇编代码:
push cs
pop ds
mov dx, 0Eh
mov ah, 9
int 21h
mov ax, 4C01h
int 21h
前两句表示借助栈结构将 cs 的值放入到 ds 中,这只是将代码段和数据段数值设置相同的一种方式。第 3 句 dx 的值为 0Eh,表示字符串 This program cannot be run in DOS mode. 的地址,第 4 句将 ah 设置为 9,第 5 句调用 DOS 中断 21h 服务,它需要一个参数来确定要执行的函数,该参数位于 ah 中,9 代表将字符串打印到屏幕,打印的字符串地址是存放在 dx 中,不了解 DOS 中断方面的可以参考中文维基百科关于 DOS API 说明:https://zh.wikipedia.org/wiki/DOS_API
最后一句再次调用中断 21h 服务,前一句设置 ax 的值为 0x4C01 (ah 为 0x4C,al 为 0x01),0x4C 表示用指定返回代码终止,返回代码为 0x01。DOS Stub 作用是为了将一条错误信息打印至屏幕上并退出,仅此而已。
现在来说说 Rich Header,网上有很多对 PE 结构解析的文章,但并未提到它,甚至上面的内容也很少提及,可能认为不太重要吧。Rich Header 存在于 DOS Stub 和 NT Header 之间,是一种未公开的结构,以 DanS 开头,并以 Rich 结尾,后跟一个检验和 (checksum,一个用于异或的 key),仅存在于使用 Visual Studio 工具集构建的可执行文件中,表示的是构建工具集的一些元数据,这些元数据包括类型、特定版本及构建号,把这部分数据全抹为 0 不会影响文件的执行。
这部分内容进行了异或加密,首先 rich id 为 0x68636952,表示的是 Rich,后跟一个检验和,会用此校验和当作异或 key,值为 0x48396E4C
开头 0x1B570F08 跟 xor_key 进行异或得到的是一个开始标志,表示 DanS,后面三条红杠标出的进行异或为 0,为 3 个清零的 DWORD 型数据。
后面跟着 11 条黄杠标出的数据,代表 11 项,每项包含 2 个 DWORD 型数据,将这两个数据分别与 xor_key 进行异或,比如第一条黄杠标出的数据
07 16 3A 49 47 6E 39 48
--------------------------
0x48396E47 0x493A1607
xor 0x48396E4C 0x48396E4C
--------------------------
0xB 0x103784B
--------------------------
B 103 784B
--------------------------
11.259.30795
11 表示的工具生成的项目数,比如一个应用程序由 3 个 C++ 源文件组成,那么项目计数为 3,259 表示的是工具标识符,比如 C++ 编译器、C 编译器、资源编译器、MASM 等,此处对应为 Masm1400 (Visual Studio 2015 14.00),30795 表示的是构建的 ID。其它的类似,有兴趣的可以去研究 PEBear 的源码,以下给出参考链接:https://github.com/hasherezade/bearparser/blob/master/parser/pe/RichHdrWrapper.cpp
可能大家觉得研究这个没有用,但事实在实际当中有攻击者使用过此结构,参考卡巴斯基这篇报告:https://securelist.com/the-devils-in-the-rich-header/84348/
这篇报告大概意思为 OlympicDestroyer 恶意软件作者修改了其中某个组件的 Rich Header,使其看起来和 Bluenoroff 相同,卡巴斯基安全研究人员通过反复对比不同构建工具集构建出来的可执行文件 Rich Header,并结合 OlympicDestroyer 和 Bluenoroff,发现 OlympicDestroyer 当中某个组件的 Rich Header 是假的,并表示不能够完全理解这一动机,但他们能肯定 OlympicDestroyer 的创建者有意修改了此结构,让它看起来像 Lazarus 组织开发的 Bluenoroff 一样。
关于 Rich Header 的更多应用,以后会出一些。
页:
[1]