1、PE 初识

1、PE 初识

概论

首先 PE头部分主要是学习PE结构的前半部分,每一个是做什么的,以及重点是什么,每一个是做什么用的。并使用Cpp代码来解析该PE头 注意这里用了一个Windows.h的头文件,后面再说。

PE是Windows系统

PE结构(Portable Executable),即可移植可执行文件格式,是Windows操作系统下的一种可执行文件格式。它定义了Windows操作系统中可执行文件(如EXE、DLL、SYS等)的结构和组织方式。PE结构的设计使得这些文件可以在不同的Windows平台上运行,因此得名“可移植”。

这个是比较官方的,其实说白了,就是Windows的可执行程序,

这里说一下可执行文件不仅仅只有exe,dll sys也是哈。这个没啥好说的,网上一堆给你说什么是可执行程序的,又是各种概念我觉得没啥必要学

这里直接开始吧,先说一下PE学了有啥用,说白了就是能更牛逼,能让你准确定位到全局变量到地址,因为全局变量一般都是编译到时候就确定了地址了,所以这里就非常有用了。

对于PE我学的是一个收费到课程,当然网上有很多免费到。可以学一下 比如滴水三期,海哥讲的,这个是都非常推荐到。

我们 先打开一个Pe文件看一下吧。

image

请仔细观察我所标记的。首先0x5A4d(小端存储),是MZ标记,MZ是一个牛逼大佬的名字,其他的没必要知道,这也是判断PE文件的一个标记,如果前两个字节没问题的话,我们就直接去0x3C的位置去找一个四字节数据, 这个是NT头的首地址,也就是PE标记,如果这个没问题的话,就说明这是一个PE文件了。

补充一些基础

首先就是如何打开文件并把文件写入到内存中去。这里直接贴代码吧 就不自己写了。

	FILE* pFile = fopen(FILE_PATH, "rb");
	if (!pFile)
	{
		log_error("open file faild!");
		return NULL;
	}
	log_info("open file success!");
	// 获取文件大小
	fseek(pFile,0, SEEK_END);
	size_t nFileSize = ftell(pFile);
	fseek(pFile, 0, SEEK_SET);
	PCHAR buff = calloc(1, nFileSize);
	if (NULL == buff)
	{
		log_error("calloc faild");
		fclose(pFile);
		return NULL;
	}
	size_t s = fread(buff, nFileSize, 1, pFile);
	if (s <= 0)
	{
		log_error("read file error!");
		fclose(pFile);
		return NULL;
	}

	fclose(pFile);
	return buff;

这个代码是我写的不保证一定对,但是可以看看

日志文件推荐:https://github.com/rxi/log.c

其次就是结构体指针那一块了,我很久之前写过,这里我就不写了

IMAGE_NT_HEAD

首先IMAGE_NT_HEAD 是一个结构体,定义如下

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature; 							// PE标记 0x0000 4550
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;		// 标准PE头 大小固定位0x14个字节
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;		// 扩展PE头 32位大小默认位0xE0

typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;				// 标准PE头 大小固定位0x14个字节
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;		// 扩展PE头 32位大小默认位0xF0
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

这里要注意。32位与64位大小不一致。后面深入解析就可以知道了。下面我们继续说。介绍每一项的功能

Signature: PE文件标记,如果该值为0x4050,如果不是该值程序将无法启动

FileHeader :标准PE头 指向 IMAGE_FILE_HEADER 结构体

OptionalHeader: 扩展PE头 指向 IMAGE_OPTIONAL_HEADER32/IMAGE_OPTIONAL_HEADER64结构体

其中标准PE头与扩展PE头在看到这里的时候无需深究。说到这里。其实我们就有了一个大概的印象

当我们把一个二进制文件,使用16位进行读取时。我们首先要查看前两个字节,必须是4D5A如果不是则该文件不是PE。如果是的话 就看3C位置,3C位置指向一个地址,我们直接去到该地址,改地址的值是一个0x4550则说明是一个PE文件代码如下。

BOOL CheckPeFile(PCHAR fileBuff)
{
	PIMAGE_DOS_HEADER pImgDosBuf = (PIMAGE_DOS_HEADER)fileBuff;
	if ((WORD)pImgDosBuf->e_magic != IMAGE_DOS_SIGNATURE)
	{
		return FALSE;
	}
	DWORD dwOffset = pImgDosBuf->e_lfanew;
	PIMAGE_NT_HEADERS pImgNtHead = (PIMAGE_NT_HEADERS)(fileBuff + dwOffset);
	if (pImgNtHead->Signature != IMAGE_NT_SIGNATURE)
	{
		return FALSE;
	}
	return TRUE;
}

代码中用到了两个宏分别位 IMAGE_DOS_SIGNATURE 和 IMAGE_NT_SIGNATURE

他们的意思我们可以看一下

#define IMAGE_DOS_SIGNATURE                 0x5A4D      // MZ
#define IMAGE_OS2_SIGNATURE                 0x454E      // NE
#define IMAGE_OS2_SIGNATURE_LE              0x454C      // LE
#define IMAGE_VXD_SIGNATURE                 0x454C      // LE
#define IMAGE_NT_SIGNATURE                  0x00004550  // PE00

其实就是几个十六进制的数。这里就不说了。

这里要注意一点,先解引用再转换和先转换再解引用的结果不一致,原因这里就不提了,如果不知道的话记得回去复习一下。这里再提一嘴,我再从Cpplinux服务器开发的代码习惯转变为Win32的开发上来,所以代码可能不规范别介意。

posted @ 2024-12-19 19:54  未然king  阅读(8)  评论(0编辑  收藏  举报