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文件看一下吧。
请仔细观察我所标记的。首先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 4550IMAGE_FILE_HEADER FileHeader;IMAGE_OPTIONAL_HEADER32 OptionalHeader; // 标准PE头 大小固定位0x14个字节
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; // 扩展PE头 32位大小默认位0xE0typedef 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的开发上来,所以代码可能不规范别介意。