PE解释器之前期准备工作

一:什么是PE解释器

        PE解释器通常指的是Portable Executable(PE)文件格式的解释器。PE是一种可执行文件和库文件的标准格式,主要用于32位和64位版本的Windows操作系统。PE文件包含程序的二进制代码、数据、资源以及与可执行文件相关的其他信息。PE解释器是用于解释和执行PE文件的工具或程序。
在Windows操作系统中,PE文件是常见的可执行文件格式,用于存储应用程序、驱动程序和动态链接库(DLL)。PE解释器负责读取和解析PE文件的结构,执行其中的二进制代码,并在系统上运行相应的程序。
PE文件的结构包括头部信息、节表、导入表、导出表等部分。PE解释器会按照这些结构解析文件,并执行其中包含的程序逻辑。对于DLL文件,PE解释器还负责处理动态链接,将程序运行所需的函数和资源链接到系统中。

二:必须知道的知识点

        地址空间:这个地址空间指的是PE文件被加载到内存的空间,是一个虚拟的地址空间,之所以不是物理空间是因为数据在内存中的位置经常在变,这样既可以节约内存开支又可以避开错误的内存位置。这个地址空间的大小为4G,但其中供程序装载的空间只有2G而且还是低2G空间,高2G空间则被用于装载内核DLL文件,所以也被称作内核空间。

        文件映射:PE文件在磁盘上的状态和在内存中的状态是不一样的,我们把PE文件在磁盘上的状态称作FileBuffer,在内存中的状态称为ImageBuffer。当PE文件通过装载器装入内存是会经过“拉伸”的过程,所以它在FileBuffer状态下和ImageBuffer状态下的大小是不一样的。这个拉伸的具体过程会在讲完PE头结构后进行介绍。大致的图解如下:

        VA:英文全称是Virual Address,简称VA,中文意思是虚拟地址。指的是文件被载入虚拟空间后的地址。

  ImageBase:中文意思是基址,指的是程序在虚拟空间中被装载的位置。

  RVA:英文全称是Relative Virual Address,简称RVA,中文意思是相对虚拟地址。可以理解为文件被装载到虚拟空间(拉伸)后先对于基址的偏移地址。计算方式:RVA = VA(虚拟地址) - ImageBase(基址)。它的对齐方式一般是以1000h为单位在虚拟空间中对齐的(传说中的4K对齐),具体对齐需要参照IMAGE_OPTIONAL_HEADER32中的SectionAlignment成员。

        FOA:英文全称是File Offset Address,简称FOA,中文意思是文件偏移地址。可以理解为文件在磁盘上存放时相对于文件开头的偏移地址。它的对齐方式一般是以200h为单位在硬盘中对齐的(512对齐),具体对齐需要参照IMAGE_OPTIONAL_HEADER32中的FileAlignment成员。

三:完整代码目录介绍及前期准备

前期准备我们先完成PE.h部分和部分函数实现

#ifndef _PE_H_ //如果标识符_PE_H_没有被定义,那么执行下面的代码,防止重复包含同一个头文件。//通常在头文件的开始部分使用这样的条件编译指令,以确保头文件只被包含一次,避免重 复定义和冲突。
#define _PE_H_
/*
这段代码是一个条件编译的语法,用于防止重复包含同一个头文件。
首先,#ifndef 是条件编译指令的一部分,意为 "if not defined",用于检查给定的标识符是否已经定义。
在这里,_PE_H_ 是一个标识符,通常用来表示头文件的宏定义。
接着,#define 是用于定义宏的指令。这里将`_PE_H_` 定义为一个标记,用来表示头文件的宏定义。
综合起来,这段代码的意思是:如果标识符`_PE_H_`没有被定义,那么执行下面的代码,
防止重复包含同一个头文件。通常在头文件的开始部分使用这样的条件编译指令,
以确保头文件只被包含一次,避免重复定义和冲突。
*/#include "stdio.h"
#include "stdlib.h"
#include "windows.h"#define FILE_PATH  "C:/Users/Qiu_JY/Desktop/PETool 1.0.0.5.exe"int GetFileLength(FILE* pf, DWORD* Length);int MyReadFile(void** pFileAddress);int MyReadFile_V2(void** pFileAddress, PCHAR FilePath);int MyWriteFile(PVOID pFileAdderss, DWORD FileSize, LPSTR FilePath);int FOA_TO_RVA(PVOID pFileAdderss, DWORD FOA, PDWORD pRVA);int RVA_TO_FOA(PVOID FileAdderss, DWORD RVA, PDWORD pFOA);#endif  
//表示条件编译块的结束,它与之前的条件编译指令一起,
//将一个代码块包括起来,只有在满足条件的情况下才会编译这部分代码。/*
\`#endif` 是条件编译指令的一部分,用于结束一个条件编译块。
在条件编译指令中,`#ifndef` 和 `#ifdef` 通常会与 `#endif` 配对使用。`#endif` 表示条件编译块的结束,
它与之前的条件编译指令一起,将一个代码块包括起来,只有在满足条件的情况下才会编译这部分代码。
在这个特定的例子中,`#ifndef` 和 `#define` 之间的代码被用于防止重复包含同一个头文件,
而 `#endif` 表示条件编译块的结束,意味着在这个代码块中定义的宏和代码只会在满足条件时编译和执行。
总之,`#endif` 的作用是结束一个条件编译块,将被包括在其中的代码从编译范围中排除。
*/
# define _CRT_SECURE_NO_WARNINGS
# include "PE.h"int GetFileLength(FILE *pf, DWORD *Length)
{int ret = 0;fseek(pf, 0, SEEK_END);//将文件指针移动到文件末尾。这一步相当于定位文件末尾,//以便后续通过 ftell 获取文件长度。*Length = ftell(pf);//使用 ftell 函数获取当前文件指针的位置,即文件长度。//将获取到的长度存储在 Length 指向的内存位置。fseek(pf, 0, SEEK_SET);//将文件指针移回文件开头。这一步是为了不影响文件的后续读取或操作。return ret;
}int MyReadFile(void **pFileAddress)
{int ret = 0;DWORD Length = 0; //双字节类型//打开文件FILE* pf = fopen(FILE_PATH, "rb");//打开模式为二进制读取模式if (pf == NULL){ret = -1;printf("func ReadFile() Error!\n");return ret;}//获取文件长度ret = GetFileLength(pf, &Length);if (ret != 0 && Length == -1){ret = -2;printf("func GetFileLength() Error!\n");return ret;}//分配空间*pFileAddress = (PVOID)malloc(Length);if (*pFileAddress == NULL){ret = -3;printf("func malloc() Error!\n");return ret;}memset(*pFileAddress, 0, Length);//使用 memset 函数将 *pFileAddress 指向的内存块清零。//读取文件进入内存fread(*pFileAddress, Length, 1, pf);//使用 fread 函数将文件内容读取到 *pFileAddress 指向的内存中,//读取长度为 Length 字节fclose(pf);return ret;
}int MyReadFile_V2(void** pFileAddress, PCHAR FilePath)//增加了一个 PCHAR 类型的参数 FilePath,用于指定要打开的文件路径。
{int ret = 0;DWORD Length = 0;//打开文件FILE* pf = fopen(FilePath, "rb");if (pf == NULL){ret = -1;printf("func ReadFile() Error!\n");return ret;}//获取文件长度ret = GetFileLength(pf, &Length);if (ret != 0 && Length == -1){ret = -2;printf("func GetFileLength() Error!\n");return ret;}//分配空间*pFileAddress = (PVOID)malloc(Length);if (*pFileAddress == NULL){ret = -3;printf("func malloc() Error!\n");return ret;}memset(*pFileAddress, 0, Length);//读取文件进入内存fread(*pFileAddress, Length, 1, pf);fclose(pf);return ret;
}int MyWriteFile(PVOID pFileAddress, DWORD FileSize, LPSTR FilePath)
{int ret = 0;FILE *pf = fopen(FilePath, "wb");if (pf == NULL){ret = -5;printf("func fopen() error :%d!\n", ret);return ret;}fwrite(pFileAddress, FileSize, 1, pf);fclose(pf);return ret;
}int FOA_TO_RVA(PVOID FileAddress, DWORD FOA, PDWORD pRVA)
{int ret = 0;PIMAGE_DOS_HEADER pDosHeader				= (PIMAGE_DOS_HEADER)(FileAddress);PIMAGE_FILE_HEADER pFileHeader				= (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);PIMAGE_OPTIONAL_HEADER32 pOptionalHeader	= (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + sizeof(IMAGE_FILE_HEADER));PIMAGE_SECTION_HEADER pSectionGroup			= (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);//FOA在文件头中 或 SectionAlignment 等于 FileAlignment 时RVA等于FOAif (FOA < pOptionalHeader->SizeOfHeaders || pOptionalHeader->SectionAlignment == pOptionalHeader->FileAlignment){*pRVA = FOA;return ret;}//FOA在节区中for (int i = 0; i < pFileHeader->NumberOfSections; i++){if (FOA >= pSectionGroup[i].PointerToRawData && FOA < pSectionGroup[i].PointerToRawData + pSectionGroup[i].SizeOfRawData){*pRVA = pSectionGroup[i].VirtualAddress + FOA - pSectionGroup[i].PointerToRawData;return ret;}}//没有找到地址ret = -4;printf("func FOA_TO_RAV() Error: %d 地址转换失败!\n", ret);return ret;
}int RVA_TO_FOA(PVOID FileAddress, DWORD RVA, PDWORD pFOA)
{int ret = 0;PIMAGE_DOS_HEADER pDosHeader				= (PIMAGE_DOS_HEADER)(FileAddress);PIMAGE_FILE_HEADER pFileHeader				= (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);PIMAGE_OPTIONAL_HEADER32 pOptionalHeader	= (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + sizeof(IMAGE_FILE_HEADER));PIMAGE_SECTION_HEADER pSectionGroup			= (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);//RVA在文件头中 或 SectionAlignment 等于 FileAlignment 时RVA等于FOAif (RVA < pOptionalHeader->SizeOfHeaders || pOptionalHeader->SectionAlignment == pOptionalHeader->FileAlignment){*pFOA = RVA;return ret;}//RVA在节区中for (int i = 0; i < pFileHeader->NumberOfSections; i++){if (RVA >= pSectionGroup[i].VirtualAddress && RVA < pSectionGroup[i].VirtualAddress + pSectionGroup[i].Misc.VirtualSize){*pFOA = pSectionGroup[i].PointerToRawData + RVA - pSectionGroup[i].VirtualAddress;return ret;}}//没有找到地址ret = -4;printf("func RAV_TO_FOA() Error: %d 地址转换失败!\n", ret);return ret;
}

其中这俩函数由其重要,后面一定要明白如何转换:

int FOA_TO_RVA(PVOID FileAddress, DWORD FOA, PDWORD pRVA)
{int ret = 0;PIMAGE_DOS_HEADER pDosHeader				= (PIMAGE_DOS_HEADER)(FileAddress);PIMAGE_FILE_HEADER pFileHeader				= (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);PIMAGE_OPTIONAL_HEADER32 pOptionalHeader	= (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + sizeof(IMAGE_FILE_HEADER));PIMAGE_SECTION_HEADER pSectionGroup			= (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);//定位操作//FOA在文件头中 或 SectionAlignment 等于 FileAlignment 时RVA等于FOAif (FOA < pOptionalHeader->SizeOfHeaders || pOptionalHeader->SectionAlignment == pOptionalHeader->FileAlignment){*pRVA = FOA;return ret;}//FOA在节区中for (int i = 0; i < pFileHeader->NumberOfSections; i++){if (FOA >= pSectionGroup[i].PointerToRawData && FOA < pSectionGroup[i].PointerToRawData + pSectionGroup[i].SizeOfRawData){*pRVA = pSectionGroup[i].VirtualAddress + FOA - pSectionGroup[i].PointerToRawData;return ret;}}
/*这段代码是在遍历PE文件的节区表(Section Table)时,通过比较文件偏移地址(File Offset Address,FOA)来确定指定地址(FOA)所属的虚拟地址(RVA)。
具体解释如下:
pFileHeader->NumberOfSections表示PE文件头中记录的节区数量。
for (int i = 0; i < pFileHeader->NumberOfSections; i++)` 是一个循环,遍历每个节区。
if (FOA >= pSectionGroup[i].PointerToRawData && FOA < pSectionGroup[i].PointerToRawData + pSectionGroup[i].SizeOfRawData)` 是条件语句,检查 FOA 是否在当前节区的文件偏移地址范围内。如果是,则说明 FOA 属于该节区。
*pRVA = pSectionGroup[i].VirtualAddress + FOA - pSectionGroup[i].PointerToRawData;计算 FOA 对应的虚拟地址(RVA),其中 pSectionGroup[i].VirtualAddress是节区的虚拟地址,pSectionGroup[i].PointerToRawData是节区的文件偏移地址。
return ret;返回计算得到的 RVA。
这段代码的作用是根据给定的 FOA,找到对应的 RVA。在PE文件中,FOA 是文件偏移地址,而 RVA 是虚拟地址。通过遍历节区表,判断 FOA 属于哪个节区,然后通过计算得到对应的 RVA。这样的操作通常用于将文件偏移地址转换为内存中的虚拟地址,以便在内存中正确定位数据或代码。*///没有找到地址ret = -4;printf("func FOA_TO_RAV() Error: %d 地址转换失败!\n", ret);return ret;
}int RVA_TO_FOA(PVOID FileAddress, DWORD RVA, PDWORD pFOA)
{int ret = 0;PIMAGE_DOS_HEADER pDosHeader				= (PIMAGE_DOS_HEADER)(FileAddress);PIMAGE_FILE_HEADER pFileHeader				= (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);PIMAGE_OPTIONAL_HEADER32 pOptionalHeader	= (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + sizeof(IMAGE_FILE_HEADER));PIMAGE_SECTION_HEADER pSectionGroup			= (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);//RVA在文件头中 或 SectionAlignment 等于 FileAlignment 时RVA等于FOAif (RVA < pOptionalHeader->SizeOfHeaders || pOptionalHeader->SectionAlignment == pOptionalHeader->FileAlignment){*pFOA = RVA;return ret;}//RVA在节区中for (int i = 0; i < pFileHeader->NumberOfSections; i++){if (RVA >= pSectionGroup[i].VirtualAddress && RVA < pSectionGroup[i].VirtualAddress + pSectionGroup[i].Misc.VirtualSize){*pFOA = pSectionGroup[i].PointerToRawData + RVA - pSectionGroup[i].VirtualAddress;return ret;}}//没有找到地址ret = -4;printf("func RAV_TO_FOA() Error: %d 地址转换失败!\n", ret);return ret;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/314386.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RK3568平台 Android13 GKI架构开发方式

一.GKI简介 GKI&#xff1a;Generic Kernel Image 通用内核映像。 Android13 GMS和EDLA认证的一个难点是google强制要求要支持GKI。GKI通用内核映像&#xff0c;是google为了解决内核碎片化的问题&#xff0c;而设计的通过提供统一核心内核并将SoC和板级驱动从核心内核移至可加…

2024年山东省中职“网络安全”试题——B-3:Web安全之综合渗透测试

B-3&#xff1a;Web安全之综合渗透测试 服务器场景名称&#xff1a;Server2010&#xff08;关闭链接&#xff09; 服务器场景操作系统&#xff1a;"需要环境有问题加q" 使用渗透机场景Kali中的工具扫描服务器&#xff0c;通过扫描服务器得到web端口&#xff0c;登陆…

envoy在arm机器上的编译整理

版本信息&#xff1a; 操作系统:GUN Linux操作系统AARCH64架构。istio-proxy版本&#xff1a;istio-proxy1.15.2 编译环境搭建&#xff1a; 设置代理&#xff0c;确保可以访问Google等外网&#xff0c;这里envoy的第一次编译需要从外网下载依赖库。// 备注&#xff1a;这里一定…

爬虫实战-微博评论爬取

简介 最近在做NLP方面的研究&#xff0c;以前一直在做CV方面。最近由于chatgpt&#xff0c;所以对NLP就非常感兴趣。索性就开始研究起来了。 其实我们都知道&#xff0c;无论是CV方向还是NLP方向的模型实现&#xff0c;都是离不开数据的。哪怕是再先进的代码&#xff0c;都是…

深度学习|3.6 激活函数 3.7 为什么需要非线性激活函数

激活函数 主要有sigmoid函数、tanh函数、relu函数和leaky relu函数 tanh函数相比sigmoid函数是具有优势的&#xff0c;因为tanh函数使得输出值的平均值为0&#xff0c;而sigmoid函数使得输出值的平均值为1/2&#xff0c;对下一层来说tanh输出的0更好进行处理。 激活函数tanh…

Debezium发布历史40

原文地址&#xff1a; https://debezium.io/blog/2018/09/20/materializing-aggregate-views-with-hibernate-and-debezium/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. 使用 Hibernate 和 Debezium 实现聚合…

【排序算法】【二叉树】【滑动窗口】LeetCode220: 存在重复元素 III

作者推荐 【二叉树】【单调双向队列】LeetCode239:滑动窗口最大值 本文涉及的基础知识点 C算法&#xff1a;滑动窗口总结 题目 给你一个整数数组 nums 和两个整数 indexDiff 和 valueDiff 。 找出满足下述条件的下标对 (i, j)&#xff1a; i ! j, abs(i - j) < indexDi…

Vue 插槽:让你的组件更具扩展性(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

PiflowX组件-JDBCWrite

JDBCWrite组件 组件说明 使用JDBC驱动向任意类型的关系型数据库写入数据。 计算引擎 flink 有界性 Sink: Batch Sink: Streaming Append & Upsert Mode 组件分组 Jdbc 端口 Inport&#xff1a;默认端口 outport&#xff1a;默认端口 组件属性 名称展示名称默…

Halcon开运算opening

Halcon开运算 文章目录 Halcon开运算 开运算的计算步骤是先腐蚀&#xff0c;后膨胀。通过腐蚀运算能去除小的非关键区域&#xff0c;也可以把离得很近的元素分隔开&#xff0c;再通过膨胀填补过度腐蚀留下的空隙。因此&#xff0c;通过开运算能去除一些孤立的、细小的点&#x…

Vue3全局属性app.config.globalProperties

文章目录 一、概念二、实践2.1、定义2.2、使用 三、最后 一、概念 一个用于注册能够被应用内所有组件实例访问到的全局属性的对象。点击【前往】访问官网 二、实践 2.1、定义 在main.ts文件中设置app.config.globalPropertie import {createApp} from vue import ElementPl…

fmincon函数的决策变量可以是二维矩阵,但不建议是高维矩阵

1&#xff09;二维矩阵代码 clear all clc% 定义目标函数 fun (x) sum(sum(x.^2));% 初始矩阵 x0 2 rand(2, 2);% 定义空的线性不等式约束 A []; b [];% 定义空的线性等式约束 Aeq []; beq [];% 定义变量的上下界 lb ones(2,2); ub [];% 使用 fmincon 求解 options …