PE头解析(仅限于PE头)
学习感悟,如果错误,还望指出
目录
前言
过程
总结
前言
PE格式是在Windows上的可执行程序需要遵守的格式规范。
接下来介绍一下PE头中的一部分成员的含义。
DOS头
大小是固定的64字节
struct _IMAGE_DOS_HEADER ,
{ 0x00 WORD e_magic;//表明此文件是不是PE文件,如果是PE文件的话,值为4D5A,ASCII值对应MZ。
0x02 WORD e_cblp;
0x04 WORD e_cp;
0x06 WORD e_crlc;
0x08 WORD e_cparhdr;
0x0a WORD e_minalloc;
0x0c WORD e_maxalloc;
0x0e WORD e_ss;
0x10 WORD e_sp;
0x12 WORD e_csum;
0x14 WORD e_ip;
0x16 WORD e_cs;
0x18 WORD e_lfarlc;
0x1a WORD e_ovno;
0x1c WORD e_res[4];
0x24 WORD e_oemid;
0x26 WORD e_oeminfo;
0x28 WORD e_res2[10];
0x3c DWORD e_lfanew; //指明PE标识,也就是NT头的开始位置的偏移是多少,大小不固定,因为在DOS头结束,和NT头开始之间的区域是操作系统留给编译器使用的空闲区域,大小不确定,所以需要这个成员来存储NT头部的偏移。
};
struct _IMAGE_NT_HEADERS
{ 0x00 DWORD Signature; //PE标识,也就是上图中DOS头最后一个成员所指向的地方。
0x04 _IMAGE_FILE_HEADER FileHeader; //标准PE头
0x18 _IMAGE_OPTIONAL_HEADER OptionalHeader;//可选PE头
};
标准PE头
大小为20字节
struct _IMAGE_FILE_HEADER
{
0x00 WORD Machine;//标识此程序可以在什么CPU上允许,如果是0x00的话说明在任何CPU上都 可以允许此程序,如果是014c,标识在386及其之后的CPU上可以执行。
0x02 WORD NumberOfSections;//去掉PE头部的节区数量
0x04 DWORD TimeDateStamp;//时间戳,因为编译器在编译的时候会生成一个MAP文件,MAP文 件,MAP文件里面记录了此程序的函数的名字,和地址,里面有一个时间戳,记录了MAP文件的生成时间,这个时间是和exe程序相匹配的,有的加壳软件在进行加壳的时候,会需要提供MAP文件,识别MAP文件的时间戳和exe是否匹配。
0x08 DWORD PointerToSymbolTable;
0x0c DWORD NumberOfSymbols;
0x10 WORD SizeOfOptionalHeader;//记录了可选PE头的大小,32位默认是E0,64位默认是F0,大小可以调整。
0x12 WORD Characteristics; //每一位都有特定的含义,可执行程序是10F,级0,1,2,3,8位置1
};
可选PE头
大小不确定,32位默认为E0,64系统默认为F0,可以自己定义大小。
struct _IMAGE_OPTIONAL_HEADER
{
0x00 WORD Magic; //说明文件类型,10B说明是32位下的PE文件,20B说明64位下的PE文件
0x02 BYTE MajorLinkerVersion;
0x03 BYTE MinorLinkerVersion;
0x04 DWORD SizeOfCode; //所有代码节的和,必须是FileAlignment的整数倍
0x08 DWORD SizeOfInitializedData;//所有已初始化数据的和,必须是FileAlignment的整数倍
0x0c DWORD SizeOfUninitializedData;//所有未初始化数据的和,必须是FileAlignment的整数倍
0x10 DWORD AddressOfEntryPoint; //简称OEP,程序的入口地址,需要配合ImageBase来定位程序的入口地址。这里相信大家可能会有疑问,为什么不直接定位到入口地址,还需要使用ImageBase+OEP偏移的方式来进行定位,因为一个EXE程序,很可能不止由一个PE文件构成,如果直接写死,而这个位置又已经被别的PE文件占据了,那么就出事了,如果采用这种偏移的方式,ImageBase改变之后,通过OEP进行偏移还是可以进行定位,程序还是可以跑起来。
0x14 DWORD BaseOfCode;//代码节的基址。
0x18 DWORD BaseOfData; //数据节的基址。
0x1c DWORD ImageBase; //程序在加载入内存的一个基址,也就是起始地址。
0x20 DWORD SectionAlignment; //内存对齐,1000字节
0x24 DWORD FileAlignment;//硬盘对齐,200字节
这里说一下,一个exe程序存储到硬盘上,如果直接通过一个16进制的编辑器打开,不会做任何改动,和在硬盘中是一样的,但是这个程序虽然加载如内存,但是不能跑起来,如果硬盘对齐和内存对齐尺寸不一样,那么就存在一个拉伸的过程,举个例子,代码节在硬盘上占389个字节,然后硬盘给他分配400字节(因为要进行对齐),那么允许这个程序时,拉到硬盘中,就会被拉伸到1000字节,如果硬盘对齐和内存对齐是一致的,就没有这个拉伸的过程了。
0x28 WORD MajorOperatingSystemVersion;
0x2a WORD MinorOperatingSystemVersion;
0x2c WORD MajorImageVersion;
0x2e WORD MinorImageVersion;
0x30 WORD MajorSubsystemVersion;
0x32 WORD MinorSubsystemVersion;
0x34 DWORD Win32VersionValue;
0x38 DWORD SizeOfImage; //程序在内存中的映射尺寸,可以设置的比原来的尺寸更长,但是必须是SectionAlignment的整数倍
0x3c DWORD SizeOfHeaders;//所有的头加上节表的大小。必须是文件对齐的整数倍。(DOS头+PE标识+标准PE头+可选PE头+节表)
0x40 DWORD CheckSum; //校验和,其实很简单,就是把数据从开始到结束加起来,存到此成员中,自然溢出。
0x44 WORD Subsystem;
0x46 WORD DllCharacteristics;
0x48 DWORD SizeOfStackReserve; //初始化的时候保留栈的大小
0x4c DWORD SizeOfStackCommit;//初始化时实际提交栈的大小
0x50 DWORD SizeOfHeapReserve; //初始化时保留堆区的大小
0x54 DWORD SizeOfHeapCommit;//初始化时实际提交堆的大小
0x58 DWORD LoaderFlags;
0x5c DWORD NumberOfRvaAndSizes;
0x60 _IMAGE_DATA_DIRECTORY DataDirectory[16];
};
过程
下面是我用C写的解析PE头的数据(只限于PE头),方法比较笨,如果大家有更好的办法请告知我,感谢。
我的思路是,把PE头的每个成员的信息用数组记录下来,然后依次匹配读取。
#Analyze_Of_PE_Header.h
"""Analyze_Of_PE_Header.h"""
#pragma once
typedef char BYTE;
typedef short WORD;
typedef int DWORD;
//DOS头信息
extern const BYTE* _IMAGE_DOS_HEADER[19];
//DOS头每个成员的大小
extern DWORD DOS_LENGTH[21];//标准PE头信息
extern const BYTE* _IMAGE_FILE_HEADER[7];
//标准PE头每个成员的大小
extern DWORD FILE_PE_LENGTH[7];//可选PE头信息
extern const BYTE* _IMAGE_OPTIONAL_HEADER[30];
//可选PE头每个成员的大小
extern DWORD _OPTIONAL_HEADER_LENGTH[30];char* ReadFile(char*);
bool Analyse_PE_Head(char*);
#Analyze_Of_PE_Header.cpp
#define _CRT_SECURE_NO_WARNINGS
#include "Analyze_Of_PE_Header.h"
#include <stdio.h>
#include <malloc.h>
#include <string.h>#DOS头信息
const BYTE* _IMAGE_DOS_HEADER[19]
{"e_magic","e_cblp","e_cp","e_crlc","e_cparhdr","e_minalloc","e_maxalloc","e_ss","e_sp","e_csum","e_ip","e_cs","e_lfarlc","e_ovno","e_res[4]","e_oemid","e_oeminfo","e_res2[10]","e_lfanew"
};DWORD DOS_LENGTH[21]
{2,2,2,2,2,2,2,2,2,2,2,2,2,2,8,2,2,20,4
};#标准PE头信息
const BYTE* _IMAGE_FILE_HEADER[7]
{"Machine","NumberOfSections","TimeDateStamp","PointerToSymbolTable","NumberOfSymbols","SizeOfOptionalHeader","Characteristics"
};DWORD FILE_PE_LENGTH[7]
{2,2,4,4,4,2,2
};//可选PE头信息
const BYTE* _IMAGE_OPTIONAL_HEADER[30]
{"Magic","MajorLinkerVersion","MinorLinkerVersion","SizeOfCode","SizeOfInitializedData","SizeOfUninitializedData","AddressOfEntryPoint","BaseOfCode","BaseOfData","ImageBase","SectionAlignment","FileAlignment","MajorOperatingSystemVersion","MinorOperatingSystemVersion","MajorImageVersion","MinorImageVersion","MajorSubsystemVersion","MinorSubsystemVersion","Win32VersionValue","SizeOfImage","SizeOfHeaders","CheckSum","Subsystem","DllCharacteristics","SizeOfStackReserve","SizeOfStackCommit","SizeOfHeapReserve","SizeOfHeapCommit","LoaderFlags","NumberOfRvaAndSizes"
};
DWORD _OPTIONAL_HEADER_LENGTH[30]
{2,1,1,4,4,4,4,4,4,4,4,4,2,2,2,2,2,2,4,4,4,4,2,2,4,4,4,4,4,4
};
char* ReadFile(char* p)
{FILE* fp = fopen(p, "rb");if (fp == NULL){printf("文件打开失败\n");return NULL;}fseek(fp, 0, SEEK_END);int len = ftell(fp);char* buf = (char*)malloc(len);if (buf == NULL){printf("内存分配失败\n");return NULL;}fseek(fp, 0, SEEK_SET);fread(buf, 1, len, fp);fclose(fp);return buf;
}bool Analyse_PE_Head(char* p)
{char* Buf = ReadFile(p);char* BufBackUp = Buf;int Turn = 0;if (Buf == NULL){return false;}//DOS头WORD* Test = (WORD*)Buf;if ((*Test) != 0x5A4D){return false;}printf("DOS头部:\n");int i = 0;while (i < 19){printf("%s: ", _IMAGE_DOS_HEADER[i]);if (DOS_LENGTH[i] == 2){WORD* Ptemp = (WORD*)Buf;Buf += 2;printf("%x\n", (*Ptemp));}else if (DOS_LENGTH[i] == 4){DWORD* Ptemp = (DWORD*)Buf;Buf += 4;//记录PE头偏移Turn = (*Ptemp);printf("%x\n", (*Ptemp));}else{WORD* Ptemp = (WORD*)Buf;int j = 0;while (j < (DOS_LENGTH[i] / 2)){printf("%x", *Ptemp);Buf += 2;Ptemp++;j++;}printf("\n");}i++;}printf("\n-----------------------------------------------\n");//PE头跳转Buf = BufBackUp + Turn;//NT头开始int* temp = (int*)Buf;if ((*temp) != 0x4550){return false;}printf("PE标识:%x\n", (*temp));Buf += 4;//标准PE头printf("标准PE头\n");i = 0;while (i < 7){printf("%s: ", _IMAGE_FILE_HEADER[i]);if (FILE_PE_LENGTH[i] == 2){WORD* Ptemp = (WORD*)Buf;Buf += 2;printf("%x\n", (*Ptemp));}else{DWORD* Ptemp = (DWORD*)Buf;Buf += 4;printf("%x\n", (*Ptemp));}i++;}printf("\n-----------------------------------------------\n");//可选PE头printf("可选PE头\n");i = 0;while (i < 30){printf("%s: ", _IMAGE_OPTIONAL_HEADER[i]);if (_OPTIONAL_HEADER_LENGTH[i] == 2){WORD* Ptemp = (WORD*)Buf;Buf += 2;printf("%x\n", (*Ptemp));}else if (_OPTIONAL_HEADER_LENGTH[i] == 1){BYTE* Ptemp = (BYTE*)Buf;Buf++;printf("%x\n", (*Ptemp));}else{DWORD* Ptemp = (DWORD*)Buf;Buf += 4;printf("%x\n", (*Ptemp));}i++;}printf("\n-----------------------------------------------\n");return true;
}
#main.cpp
#include <stdio.h>
#include "Analyze_Of_PE_Header.h"int main(void)
{char path[] = "C:\\Program Files (x86)\\NetSarang\\Xshell 7\\Xshell.exe";bool result = Analyse_PE_Head(path);if (result == NULL){printf("解析失败\n");}return 0;
}
这是运行结果
总结
但是我还有一个思路就是根据不同的头部,建立不同的结构体,但是在对应结构体成员的查找上遇到了问题,没有想到实现的办法,所以采用了这种办法。
PE头解析(仅限于PE头)相关推荐
- PE文件解析(1):Dos头与NT头
文章目录 DOS头 NT头 标准NT头 可选NT头 什么是PE文件? PE文件是在windows平台可执行的文件. 包括:.exe(可执行程序),dll(动态链接库).sys(驱动程序) 这是PE文件 ...
- PE头解析-字段说明
一.什么是可执行文件 1.可执行文件(executable file)指的是可以由操作系统进行加载执行的文件. 2.可执行文件的格式: Windows平台: PE(Portable Executabl ...
- 滴水逆向三期实践1:PE头字段解析,附PE结构下载
视频资源详见网盘搜索 或 在线的B站滴水逆向三期 其课件也能在CSDN或百度搜索到,以下部分为课件内容摘要,部分为自己的理解 最后附上详细注释的自写代码 PE(Portable Executable) ...
- PE文件(一)PE头
PE文件结构(一)PE头 12/100 发布文章 qq_63329753 未选择任何文件 new PE文件结构 PE(Portble Executable File Format,可移植的执行体文件格 ...
- PE文件学习系列二 DOS头分析
合肥程序员群:49313181. 合肥实名程序员群 :128131462 (不愿透露姓名和信息者勿加入) Q Q:408365330 E-Mail:egojit@qq.com PE文件 ...
- 图解VC++版PE文件解析器源码分析
该源码下载自 http://download.csdn.net/download/witch_soya/4979587 1 Understand 分析的图表 2 PE结构解析的主要代码简要分析 首先看 ...
- windows PE结构解析
1 基本概念 下表描述了贯穿于本文中的一些概念: 名称 描述 地址 是"虚拟地址"而不是"物理地址".为什么不是"物理地址"呢?因为数据在内 ...
- Win32汇编:PE结构解析器
PE格式是Windows系统下最常用的可执行文件格式,有些应用必须建立在了解PE文件格式的基础之上,如可执行文件的加密与解密,文件型病毒的查杀等,熟练掌握PE文件结构,有助于软件的分析. 在PE文件中 ...
- 15.windbg-dds、dps、dqs、PE文件解析
以下默认windbg加载calc程序 d*s dds.dps和dqs命令显示给定范围内存的内容,它们是把内存区域转储出来,并把内存中每个元素都视为一个符号对其进行解析,dds是四字节视为一个符号,dq ...
最新文章
- MySQL面试题 | 附答案解析(三)
- 给大家介绍一位中科院师兄,读研时通过实习和比赛收入五十万
- oel6mysql_Linux7(CentOS,RHEL,OEL)和 Oracle RAC环境系列4:target(图形
- Python 微信机器人:调用电脑摄像头时时监控功能实现演示,调用电脑摄像头进行拍照并保存
- 神经网络到底是如何做出决策的?
- Ubuntu 16.04 把Dock放到桌面底部
- 上个ensp实验只发了配置,这次是命令条目
- 知乎上砍手豪关于kaggle的观点(转载)
- netbeans java中文_Ubuntu 下jdk安装中文字体 java 解决netbeans 方块字 中文乱码
- 关于Netbeans调试PHP
- 美国能源局投2100万美元加速光伏软成本下降
- CSU 1115: 最短的名字(字典树)
- android开发目录结构说明
- VirtualBox 6.1.4的共享剪贴板确实有问题,6.1.0正常
- wait()和sleep()区别(常见面试题)
- jdk8下载与安装教程
- 网络编程 upd 发送接收数据
- C# PDF转图片(JPG,Png)
- 在Mac上使用中国银行和工商银行网银
- 模仿百度首页的图片轮播