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;
}

这是运行结果

总结

但是我还有一个思路就是根据不同的头部,建立不同的结构体,但是在对应结构体成员的查找上遇到了问题,没有想到实现的办法,所以采用了这种办法。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值