2.1 PE结构:文件映射进内存
PE结构是Windows系统下最常用的可执行文件格式,理解PE文件格式不仅可以理解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,在任何一款操作系统中,可执行程序在被装入内存之前都是以文件的形式存放在磁盘中的,在早期DOS操作系统中,是以COM文件的格式存储的,该文件格式限制了只能使用代码段,堆栈寻址也被限制在了64KB的段中,由于PC芯片的快速发展这种文件格式极大的制约了软件的发展。
为了应对这种局面,微软的工程师们就发明了新的文件格式(EXE文件),该文件格式在代码段前面增加了文件头结构,文件头中包括各种说明数据,如程序的入口地址,堆栈的位置,重定位表等,显然可执行文件的格式是操作系统工作方式的真实写照,不同的系统之间文件格式千差万别,从而导致不同系统中的可执行文件无法跨平台运行。
PE结构包含了各类结构体,DOS头,PE标识,文件头,可选头,目录结构,节表,导入表,导出表,重定位表,资源表等等,要想掌握PE结构首相要对这些表有一个整体上的认识,Windows NT 系统中可执行文件使用微软设计的新的文件格式,也就是至今还在使用的PE格式,PE文件的基本结构如下图所示:

在PE文件中,代码,已初始化的数据,资源和重定位信息等数据被按照属性分类放到不同的Section(节区/或简称为节)中,而每个节区的属性和位置等信息用一个IMAGE_SECTION_HEADER结构来描述,所有的IMAGE_SECTION_HEADER结构组成了一个节表(Section Table),节表数据在PE文件中被放在所有节数据的前面。
在PE文件中将同样属性的数据分类放在一起是为了统一描述这些数据装入内存后的页面属性,由于数据是按照属性在节中放置的,不同用途但是属性相同的数据可能被放在同一个节中,PE文件头被放置在节和节表的前面,上面介绍的是真正的PE文件,为了兼容以前的DOS系统,所以保留了DOS的文件格式,接下来笔者将带大家从最基本的读入文件开始依次实现对PE文件的解析,并使用C语言实现一个PeView结构解析器。
在解析PE文件之前,我们首先要做的则是将PE文件从磁盘中读入到内存,有两种方式可以实现,一种是通过ReadFile函数将完整的数据读入内存,该方法会消耗更多的内存资源这里并不推荐使用,第二种方式则是采用映射的模式,所谓的映射则是将一个磁盘中的部分数据读入内存,当需要使用该片区域时由操作系统动态的装载一部分,该方式也是笔者推荐的一种实现模式;
一般来说映射文件的流程是,使用CreateFile()打开一个磁盘文件,接着使用CreateFileMapping()函数创建文件的内存映像,最后使用MapViewOfFile()读取映射中的内存并返回一个句柄,后面的程序就可以通过该句柄操作打开后的文件。
CreateFile
用来创建或打开文件的API函数,它可以接受一个文件名作为输入参数,并返回一个文件句柄。文件句柄是用来标识打开的文件的唯一标识符,后续对该文件的操作需要使用这个句柄。下面是CreateFile函数的原型:
HANDLE CreateFile(
LPCTSTR lpFileName, // 文件名或路径
DWORD dwDesiredAccess, // 访问权限
DWORD dwShareMode, // 共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全属性
DWORD dwCreationDisposition, // 创建选项
DWORD dwFlagsAndAttributes, // 文件属性
HANDLE hTemplateFile // 模板文件句柄
);
其中,各个参数的含义如下:
- lpFileName:指向null结尾字符串的指针,该字符串是文件名或文件的路径。
- dwDesiredAccess:一个32位的
AccessMask值,用来表示对文件的访问权限。 - dwShareMode: 一个32位的
ShareMode值,它表示其他进程可以如何访问文件。 - lpSecurityAttributes:指向
SECURITY_ATTRIBUTES结构体的指针,表示安全属性。 - dwCreationDisposition:一个32位的值,它表示对文件的创建选项如何操作。
- dwFlagsAndAttributes:一个32位的值,用来指定文件的属性和标志。
- hTemplateFile:可选的模板文件句柄,用来将文件属性/属性设置为其它文件的属性/属性。
函数返回值为一个文件对象的句柄,如果函数执行失败,则返回INVALID_HANDLE_VALUE(即-1)。
CreateFileMapping
用来创建文件的内存映像的API函数。它可以将一个文件映射到内存中,这样我们就可以像访问内存一样访问文件。这个函数需要传入一个文件句柄以及一个映像的大小。它返回一个句柄,表示创建的内存映像。下面是CreateFileMapping函数的原型:
HANDLE CreateFileMapping(
HANDLE hFile, // 文件句柄
LPSECURITY_ATTRIBUTES lpAttributes, // 安全属性
DWORD flProtect, // 内存保护属性
DWORD dwMaximumSizeHigh, // 文件映像的高32位字节大小
DWORD dwMaximumSizeLow, // 文件映像的低32位字节大小
LPCTSTR lpName // 映像名
);
其中,各个参数的含义如下:
- hFile:要映射到内存中的文件的句柄
- lpAttributes:指向
SECURITY_ATTRIBUTES结构体的指针,它描述内存映射对象的安全性,如果为NULL,则内存映射对象不可继承。 - flProtect:一组标志位,它们指定内存映射区域的内存保护属性;
- dwMaximumSizeHigh:文件映像的高32位字节大小
- dwMaximumSizeLow:文件映像的低32位字节大小
- lpName:映像名,可以为NULL;而且,如果该参数不为空,映像对象就成为本地系统对象,可以通过名字查找映像。
函数返回值为一个文件映射对象的句柄,如果函数执行失败,返回值为NULL。
MapViewOfFile
用来读取映射中的内存的API函数。它需要传入一个映像的句柄以及一个偏移量,用来指定从哪个位置开始读取内存。该函数返回一个指向映射内存的指针,我们可以使用它来读取或修改映射内存中的数据。下面是MapViewOfFile函数的原型:
LPVOID MapViewOfFile(
HANDLE hFileMappingObject, // 文件映射对象的句柄
DWORD dwDesiredAccess, // 访问权限
DWORD dwFileOffsetHigh, // 文件偏移的高32位字节个数
DWORD dwFileOffsetLow, // 文件偏移的低32位字节个数
SIZE_T dwNumberOfBytesToMap // 要映射到内存中的字节数
);
其中,各个参数的含义如下:
- hFileMappingObject:文件映射对象的句柄,可以使用
CreateFileMapping函数创建,表示要映射到内存中的文件或共享内存的句柄。 - dwDesiredAccess:一个32位的
AccessMask值,用来表示对内存的访问权限。可以设置为FILE_MAP_READ、FILE_MAP_WRITE、FILE_MAP_ALL_ACCESS等。 - dwFileOffsetHigh:文件偏移的高32位字节个数。
- dwFileOffsetLow:文件偏移的低32位字节个数。
- dwNumberOfBytesToMap:要映射到内存中的字节数。
函数返回值为指向映射内存的指针,如果函数执行失败,则返回NULL。在使用完内存映像后,读者记得使用UnmapViewOfFile()函数来释放映像内存,使用CloseHandle()函数来关闭文件句柄和映像句柄,以便操作系统可以回收资源。
有了上述几个关键API函数那么实现内存映射功能将会变得很容易实现,直接来看一下如下代码,当程序运行后会自动将c://pe/x86.exe目录下的文件读入内存,并返回一个lpMapAddress文件句柄;
#include <iostream>
#include <Windows.h>
#include <ImageHlp.h>
#pragma comment(lib,"Imagehlp.lib")
// --------------------------------------------------
// 定义全局变量,来存储 DOS头部/NT头部/Section头部
// --------------------------------------------------
PIMAGE_DOS_HEADER DosHeader = nullptr;
PIMAGE_NT_HEADERS NtHeader = nullptr;
PIMAGE_FILE_HEADER FileHead = nullptr;
PIMAGE_SECTION_HEADER pSection = nullptr;
// --------------------------------------------------
// 读取并设置文件基址以及文件大小
// --------------------------------------------------
CHAR GlobalFilePath[2048] = { 0 }; // 保存文件路径
DWORD GlobalFileSize = 0; // 定义文件大小
DWORD GlobalFileBase = 0; // 保存文件的基地址
DWORD IsOpen = 0; // 设置文件是否已经打开
// --------------------------------------------------
// 打开文件操作
// --------------------------------------------------
HANDLE OpenPeFile(LPCSTR FileName)
{
HANDLE hFile, hMapFile, lpMapAddress = NULL;
hFile = CreateFileA(FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("[-] 打开文件失败 \n");
exit(0);
}
GlobalFileSize = GetFileSize(hFile, NULL);
if (GlobalFileSize != 0)
{
printf("[+] 已读入文件 \n");
}
hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, GlobalFileSize, NULL);
if (hMapFile == NULL)
{
printf("[-] 创建映射对象失败\n");
exit(0);
}
lpMapAddress = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, GlobalFileSize);
if (lpMapAddress != NULL)
{
// 设置读入文件基地址
GlobalFileBase = (DWORD)lpMapAddress;
// 获取DOS头并判断是不是一个有效的DOS文件
DosHeader = (PIMAGE_DOS_HEADER)GlobalFileBase;
if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("[-] 文件不属于DOS结构 \n");
exit(0);
}
// 获取 NT 头并判断是不是一个有效的PE文件
NtHeader = (PIMAGE_NT_HEADERS)(GlobalFileBase + DosHeader->e_lfanew);
if (NtHeader->Signature != IMAGE_NT_SIGNATURE)
{
printf("[-] 文件不属于PE结构 \n");
exit(0);
}
// 判断是不是32位程序
if (NtHeader->OptionalHeader.Magic != 0x010B)
{
printf("[-] 无法调试非32位PE文件\n");
exit(0);
}
// 获取到文件头指针
FileHead = &NtHeader->FileHeader;
// 获取到节表头
pSection = IMAGE_FIRST_SECTION(NtHeader);
}
return lpMapAddress;
}
int main(int argc, char * argv[])
{
HANDLE BaseAddr = OpenPeFile("c://pe/x86.exe");
printf("[+] 入口地址 = %x \n", BaseAddr);
system("pause");
return 0;
}
2.1 PE结构:文件映射进内存的更多相关文章
- Android mmap 文件映射到内存介绍
本文链接: Android mmap 文件映射到内存介绍 Android开发中,我们可能需要记录一些文件.例如记录log文件.如果使用流来写文件,频繁操作文件io可能会引起性能问题. 为了降低写文件的 ...
- iOS将大文件映射到内存(读取大文件)
http://blog.csdn.net/xyt243135803/article/details/40995759 在<中国区GPS偏移纠正(适用于Google地图)>一文中曾读取一个7 ...
- 【PE结构】由浅入深PE基础学习-菜鸟手动查询导出表、相对虚拟地址(RVA)与文件偏移地址转换(FOA)
0 前言 此篇文章想写如何通过工具手查导出表.PE文件代码编程过程中的原理.文笔不是很好,内容也是查阅了很多的资料后整合出来的.希望借此加深对PE文件格式的理解,也希望可以对看雪论坛有所贡献.因为了解 ...
- PE结构详解
1 基本概念 下表描述了贯穿于本文中的一些概念: 名称 描述 地址 是“虚拟地址”而不是“物理地址”.为什么不是“物理地址”呢?因为数据在内存的位置经常在变,这样可以节省内存开支.避开错误的内存位置等 ...
- Windows Pe 第三章 PE头文件(上)
第三章 PE头文件 本章是全书重点,所以要好好理解,概念比较多,但是非常重要. PE头文件记录了PE文件中所有的数据的组织方式,它类似于一本书的目录,通过目录我们可以快速定位到某个具体的章节:通过P ...
- [二]Java虚拟机 jvm内存结构 运行时数据内存 class文件与jvm内存结构的映射 jvm数据类型 虚拟机栈 方法区 堆 含义
前言简介 class文件是源代码经过编译后的一种平台中立的格式 里面包含了虚拟机运行所需要的所有信息,相当于 JVM的机器语言 JVM全称是Java Virtual Machine ,既然是虚拟机, ...
- 将文件读取到内存、打印pe结构
#include <stdio.h> #include <malloc.h> #include <stdlib.h> #include <string.h&g ...
- PE结构学习笔记--关于AddressOfEntryPoint位置在文件中怎么确定问题
第一次学习PE结构,也不知道有没有更好的办法. 1.AddressOfEntryPoint 这个成员在OptionalHeader里面,OptionalHeader的类型是一个IMAGE_OPTION ...
- 《Java核心技术卷二》笔记(二)文件操作和内存映射文件
文件操作 上一篇已经总结了流操作,其中也包括文件的读写.文件系统除了读写以为还有很多其他的操作,如复制.移动.删除.目录浏览.属性读写等.在Java7之前,一直使用File类用于文件的操作.Java7 ...
- (代码篇)从基础文件IO说起虚拟内存,内存文件映射,零拷贝
上一篇讲解了基础文件IO的理论发展,这里结合java看看各项理论的具体实现. 传统IO-intsmaze 传统文件IO操作的基础代码如下: FileInputStream in = new FileI ...
随机推荐
- 干掉 Navicat! 一款数据分析师必备的数据库可视化工具
数据开发,离不开数据库,一款优秀的数据库开发和管理工具可以达到事半功倍的效果.市面上比较流行的数据库管理工具主要有Navicat.DBeaver.SQLyog等等,Navicat是其中的无冕之王,其拳 ...
- AcWing 第 12 场周赛
题目链接:Here AcWing 3805. 环形数组 签到题,循环减少出现次数,如果是 cnt[x] = 1 的话加入新的数组中 const int N = 1e3 + 10; int cnt[N] ...
- Codeforces 1092C Prefixes and Suffixes【字符串+思维】
题目链接:点这里 题意:理解错了题意导致WA好几发,QAQ暴击 题意是判断给你的2*n-2个字符串是前缀还是后缀,不是判断这个字符串的内容...我真的欲哭无泪,理解能力太菜了 思路:将两个n-1长的字 ...
- 自主创新国产化科技:智能制造之 SMT 产线监控管理可视化
SMT(Surface Mounted Technology,表面贴片技术)指的是在印刷电路板 (Printed Circuit Board,PCB)基础上进行加工的系列工艺流程的简称,是电子组装行业 ...
- 阿里云 Serverless 异步任务处理系统在数据分析领域的应用
异步任务处理系统中的数据分析 数据处理.机器学习训练.数据统计分析是最为常见的一类离线任务.这类任务往往都是经过了一系列的预处理后,由上游统一发送到任务平台进行批量训练及分析.在处理语言方面,Pyth ...
- S3C2440移植linux3.4.2内核之内核框架介绍及简单修改
目录 uboot启动内核分析 简单配置内核 编译内核 设置机器ID 修改晶振 uboot启动内核分析 进入cmd_bootm.c,找到对应的bootm命令对应的do_bootm(): int do ...
- SpringMVC的特性及应用
Spring MVC特点 清晰地角色划分 灵活的配置功能 提供了大量的控制器接口和实现类 真正的View层实现无关(JSP.Velocity.Xslt等) 国际化支持 面向接口编程 Spring提供了 ...
- 云网络智慧课堂-Qt程序代码开发规范
序言: 编程规范可以提升代码可读性,提高可维护性. 目录: 一.命名规范 二.内存管理规范 三.函数方法规范 四.控制语句规范 五.注释规范 六.排版规范 七.版本管理规范 八.界面编程 词义解释:强 ...
- Could not get a resource from the pool 异常定位和解决
最近在服务中经常看到以下错误,进行下定位和问题解决分析: 2023-12-08 00:10:58.248 WARN [terra-sr-server,a9006fd27ccb81d0,a9006fd2 ...
- SD Host控制器微架构设计-02
SD_clk 测试模式下,选择hclk,将扫描链中的时钟保持一致 clk_en表示可以通过软硬件关闭时钟 sd_if模块 模块中设置一些寄存器,我们可以对寄存器进行读写或者对于寄存器中的某些域段进行读 ...