继续PE系列笔记的更新
PE其它笔记索引可前往:
PE文件笔记一 PE介绍
前面学习了PE结构的总体结构,接下来将具体学习PE的各个结构细节
这次学习的结构为DOS 部首
DOS部首
DOS部首结构
DOS部首结构 |
对应C中的结构体 |
说明 |
DOS 'MZ' HEADER |
_IMAGE_DOS_HEADER |
DOS MZ头 结构体 |
DOS stub |
无 |
DOS 存根 |
DOS MZ头
结构体截图
在winnt.h中找到_IMAGE_DOS_HEADER,得到以下截图(具体查找对应C结构体方法在PE文件笔记一 PE介绍中已经说明了,这里不再赘述)
结构体代码
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;
结构体成员分析
_IMAGE_DOS_HEADER结构体的成员并不少,但在现在需要学习的只有两个
因为:
回顾
DOS部首,可以说是Windows的历史遗留问题了,因为Windows程序最早是在DOS系统(16位系统)上运行的
所以该部分主要是给DOS用的(向下兼容)
更新
目前在32位或64位 WINDOWS系统上还有效的只有两个成员了:
- 第一个成员:e_magic
- 最后一个成员:e_lfanew
成员详情
成员 |
数据宽度 |
注释 |
说明 |
值 |
e_magic |
WORD(2字节) |
Magic number |
PE文件判断标识 |
固定为4d 5a (ASCII='MZ') |
e_lfanew |
LONG(4字节) |
File address of new exe header |
存储PE头首地址 |
不定 |
验证其余成员无效性
前面说到在目前的系统中,只有两个成员是有效的,为验证这一点,将其余成员全部置为0试试
1.用WinHex或UltraEdit等十六进制编辑器打开一个程序
这里采用WinHex进行操作,并选中其余成员部分
2.将选中的部分,也就是其余成员部分全部修改为0
右键→编辑→填充选块 (快捷键Ctrl+L)
确定修改后:
3.保存修改的文件
文件→保存(快捷键Ctrl+S)
4.执行修改后的文件
可以看到程序仍然可以正常运行,验证了:其余成员在32位及以上的Windows系统中无效
Dos Stub
Dos Stub在32位及以上的Windows系统中其实也无效,但不妨研究一下他的作用
1.截取出Dos Stub部分的数据
选中部分为Dos Stub,其数据范围由_IMAGE_DOS_HEADER结构体中的最后一个成员e_lfanew决定
2.复制选中部分也就是Dos Stub部分的数据
3.将数据粘贴到记事本中
对应数据
0E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000FD661975B9077726B9077726B90777260448E126BB077726B07FE226A2077726A755F326BE077726B07FE426A6077726B9077626E8057726B07FF4267D077726B07FF32651077726A755E326B8077726B907E026BB077726B07FE626B807772652696368B90777260000000000000000
对应数据反汇编
PUSH CS
POP DS
MOV DX,000E
MOV AH,09
INT 21
MOV AX,4C01
INT 21
DB 54
DB 68
DB 69
DB 00
DB 33
DB 70
……
通过16位的反汇编引擎即可得到对应的反汇编代码
这里我们主要关注DB段,也就是汇编中数据段部分有DB 54;DB 68;DB 69 ……
在WINHEX中找到其对应的数据部分,查看其对应的ASCII
数据部分为This program cannot be run in DOS
结合前面的两个INT 21 中断 不难猜测出该段数据对应的16位反汇编为输出数据部分的内容:This program cannot be run in DOS
自写代码解析DOS MZ头
了解了DOS部首的结构以后就可以自己写代码来读取DOS MZ头了
代码
// PE.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <malloc.h>
#include <windows.h>
int main(int argc, char* argv[])
{
//创建DOS对应的结构体指针
_IMAGE_DOS_HEADER* dos;
//读取文件,返回文件句柄
HANDLE hFile = CreateFileA("C:\\Documents and Settings\\Administrator\\桌面\\dbghelp.dll",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,0);
//根据文件句柄创建映射
HANDLE hMap = CreateFileMappingA(hFile,NULL,PAGE_READONLY,0,0,0);
//映射内容
LPVOID pFile = MapViewOfFile(hMap,FILE_MAP_READ,0,0,0);
//类型转换,用结构体的方式来读取
dos=(_IMAGE_DOS_HEADER*)pFile;
//输出结构体的第一个成员,以十六进制输出
printf("%X\n",dos->e_magic);
return 0;
}
运行结果
可以看到能够正确地得到DOS MZ头对应的第一个成员的值:5A4D(对应ASCII为MZ)
总结
- DOS部首分为两部分:DOS 'MZ' HEADER 和 DOS stub
- DOS 'MZ' HEADER对应的结构体_IMAGE_DOS_HEADER中仅第一个成员和最后一个成员在32位及以上的WINDOWS系统上有效
- DOS Stub对应为一串反汇编代码,其功能和输出This program cannot be run in DOS相关
- DOS 'MZ' HEADER中无效的成员部分可用来填充shellcode来达到其它目的
附件
附上本笔记中分析的EverEdit文件:点我下载