先转载一下怕以后找不到ヽ(✿゚▽゚)ノ

前面学习了PE结构的总体结构,接下来将具体学习PE的各个结构细节

这次学习的结构为DOS 部首

DOS部首

DOS部首结构

image-20210326211401967


image-20210326235719068


DOS部首结构 对应C中的结构体 说明
DOS ‘MZ’ HEADER _IMAGE_DOS_HEADER DOS MZ头 结构体
DOS stub DOS 存根

DOS MZ头

结构体截图

在winnt.h中找到_IMAGE_DOS_HEADER,得到以下截图(具体查找对应C结构体方法在PE文件笔记一 PE介绍中已经说明了,这里不再赘述)

image-20210326212749211


结构体代码

复制代码 隐藏代码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进行操作,并选中其余成员部分

image-20210326223305500


2.将选中的部分,也就是其余成员部分全部修改为0

右键→编辑→填充选块 (快捷键Ctrl+L)

image-20210326224239328


image-20210326224313607


image-20210326224403422


确定修改后:

image-20210326224440179


3.保存修改的文件

文件→保存(快捷键Ctrl+S)

image-20210326224522965


image-20210326224557452


4.执行修改后的文件

image-20210326220814513


可以看到程序仍然可以正常运行,验证了:其余成员在32位及以上的Windows系统中无效

Dos Stub

Dos Stub在32位及以上的Windows系统中其实也无效,但不妨研究一下他的作用

1.截取出Dos Stub部分的数据

image-20210326224705523

选中部分为Dos Stub,其数据范围由_IMAGE_DOS_HEADER结构体中的最后一个成员e_lfanew决定


2.复制选中部分也就是Dos Stub部分的数据

image-20210326224751035


3.将数据粘贴到记事本中

image-20210326225328310


对应数据

 复制代码 隐藏代码
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

image-20210326231526001


数据部分为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;
}

运行结果

image-20210326234752508

可以看到能够正确地得到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文件:点我下载

转载自吾爱破解上的一位大佬