9.4 Windows驱动开发:内核PE结构VA与FOA转换

本章将继续探索内核中解析PE文件的相关内容,PE文件中FOA与VA,RVA之间的转换也是很重要的,所谓的FOA是文件中的地址,VA则是内存装入后的虚拟地址,RVA是内存基址与当前地址的相对偏移,本章还是需要用到《内核解析PE结构导出表》中所封装的KernelMapFile()映射函数,在映射后对其PE格式进行相应的解析,并实现转换函数。

首先先来演示一下内存VA地址与FOA地址互相转换的方式,通过使用WinHEX打开一个二进制文件,打开后我们只需要关注如下蓝色注释为映像建议装入基址,黄色注释为映像装入后的RVA偏移。

通过上方的截图结合PE文件结构图我们可得知0000158B为映像装入内存后的RVA偏移,紧随其后的00400000则是映像的建议装入基址,为什么是建议而不是绝对?别急后面慢来来解释。

通过上方的已知条件我们就可以计算出程序实际装入内存后的入口地址了,公式如下:
VA(实际装入地址) = ImageBase(基址) + RVA(偏移) => 00400000 + 0000158B = 0040158B

找到了程序的OEP以后,接着我们来判断一下这个0040158B属于那个节区,以.text节区为例,下图我们通过观察区段可知,第一处橙色位置00000B44 (节区尺寸),第二处紫色位置00001000 (节区RVA),第三处00000C00 (文件对齐尺寸),第四处00000400 (文件中的偏移),第五处60000020 (节区属性)

得到了上方text节的相关数据,我们就可以判断程序的OEP到底落在了那个节区中,这里以.text节为例子,计算公式如下:

虚拟地址开始位置:节区基地址 + 节区RVA => 00400000 + 00001000 = 00401000
虚拟地址结束位置:text节地址 + 节区尺寸 => 00401000 + 00000B44 = 00401B44

经过计算得知 .text 节所在区间(401000 - 401B44) 你的装入VA地址0040158B只要在区间里面就证明在本节区中,此处的VA地址是在401000 - 401B44区间内的,则说明它属于.text节。

经过上面的公式计算我们知道了程序的OEP位置是落在了.text节,此时你兴致勃勃的打开x64DBG想去验证一下公式是否计算正确不料,这地址根本不是400000开头啊,这是什么鬼?

上图中出现的这种情况就是关于随机基址的问题,在新版的VS编译器上存在一个选项是否要启用随机基址(默认启用),至于这个随机基址的作用,猜测可能是为了防止缓冲区溢出之类的烂七八糟的东西。

为了方便我们调试,我们需要手动干掉它,其对应到PE文件中的结构为 IMAGE_NT_HEADERS -> IMAGE_OPTIONAL_HEADER -> DllCharacteristics 相对于PE头的偏移为90字节,只需要修改这个标志即可,修改方式 x64:6081 改 2081 相对于 x86:4081 改 0081 以X86程序为例,修改后如下图所示。

经过上面对标志位的修改,程序再次载入就能够停在0040158B的位置,也就是程序的OEP,接下来我们将通过公式计算出该OEP对应到文件中的位置。

.text(节首地址) = ImageBase + 节区RVA => 00400000 + 00001000 = 00401000
VA(虚拟地址) = ImageBase + RVA(偏移) => 00400000 + 0000158B = 0040158B
RVA(相对偏移) = VA - (.text节首地址) => 0040158B - 00401000 = 58B
FOA(文件偏移) = RVA + .text节对应到文件中的偏移 => 58B + 400 = 98B

经过公式的计算,我们找到了虚拟地址0040158B对应到文件中的位置是98B,通过WinHEX定位过去,即可看到OEP处的机器码指令了。

接着我们来计算一下.text节区的结束地址,通过文件的偏移加上文件对齐尺寸即可得到.text节的结束地址400+C00= 1000,那么我们主要就在文件偏移为(98B - 1000)在该区间中找空白的地方,此处我找到了在文件偏移为1000之前的位置有一段空白区域,如下图:

接着我么通过公式计算一下文件偏移为0xF43的位置,其对应到VA虚拟地址是多少,公式如下:

.text(节首地址) = ImageBase + 节区RVA => 00400000 + 00001000 = 00401000
VPK(实际大小) = (text节首地址 - ImageBase) - 实际偏移 => 401000-400000-400 = C00
VA(虚拟地址) = FOA(.text节) + ImageBase + VPK => F43+400000+C00 = 401B43

计算后直接X64DBG跳转过去,我们从00401B44的位置向下全部填充为90(nop),然后直接保存文件。

再次使用WinHEX查看文件偏移为0xF43的位置,会发现已经全部替换成了90指令,说明计算正确。

到此文件偏移与虚拟偏移的转换就结束了,那么这些功能该如何实现呢,接下来将以此实现这些转换细节。

9.4.1 FOA偏移转换为VA偏移

首先来实现将FOA地址转换为VA地址,这段代码实现起来很简单,如下所示,此处将dwFOA地址0x84EC00转换为对应内存的虚拟地址。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint("hello lyshark \n");NTSTATUS status = STATUS_SUCCESS;HANDLE hFile = NULL;HANDLE hSection = NULL;PVOID pBaseAddress = NULL;UNICODE_STRING FileName = { 0 };// 初始化字符串RtlInitUnicodeString(&FileName, L"\\??\\C:\\Windows\\System32\\ntoskrnl.exe");// 内存映射文件status = KernelMapFile(FileName, &hFile, &hSection, &pBaseAddress);if (!NT_SUCCESS(status)){return 0;}// 获取PE头数据集PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHeaders);PIMAGE_FILE_HEADER pFileHeader = &pNtHeaders->FileHeader;DWORD64 dwFOA = 0x84EC00;DWORD64 ImageBase = pNtHeaders->OptionalHeader.ImageBase;DWORD NumberOfSectinsCount = pNtHeaders->FileHeader.NumberOfSections;DbgPrint("镜像基址 = %p | 节表数量 = %d \n", ImageBase, NumberOfSectinsCount);for (int each = 0; each < NumberOfSectinsCount; each++){DWORD64 PointerRawStart = pSection[each].PointerToRawData;                                // 文件偏移开始位置DWORD64 PointerRawEnds = pSection[each].PointerToRawData + pSection[each].SizeOfRawData;  // 文件偏移结束位置// DbgPrint("文件开始偏移 = %p | 文件结束偏移 = %p \n", PointerRawStart, PointerRawEnds);if (dwFOA >= PointerRawStart && dwFOA <= PointerRawEnds){DWORD64 RVA = pSection[each].VirtualAddress + (dwFOA - pSection[each].PointerToRawData);     // 计算出RVADWORD64 VA = RVA + pNtHeaders->OptionalHeader.ImageBase;                                     // 计算出VADbgPrint("FOA偏移 [ %p ] --> 对应VA地址 [ %p ] \n", dwFOA, VA);}}ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);ZwClose(hSection);ZwClose(hFile);Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}

运行效果如下所示,此处之所以出现两个结果是因为没有及时返回,一般我们取第一个结果就是最准确的;

9.4.2 VA偏移转换为FOA偏移

将VA内存地址转换为FOA文件偏移,代码与如上基本保持一致。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint("hello lyshark \n");NTSTATUS status = STATUS_SUCCESS;HANDLE hFile = NULL;HANDLE hSection = NULL;PVOID pBaseAddress = NULL;UNICODE_STRING FileName = { 0 };// 初始化字符串RtlInitUnicodeString(&FileName, L"\\??\\C:\\Windows\\System32\\ntoskrnl.exe");// 内存映射文件status = KernelMapFile(FileName, &hFile, &hSection, &pBaseAddress);if (!NT_SUCCESS(status)){return 0;}// 获取PE头数据集PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHeaders);PIMAGE_FILE_HEADER pFileHeader = &pNtHeaders->FileHeader;DWORD64 dwVA = 0x00007FF6D3389200;DWORD64 ImageBase = pNtHeaders->OptionalHeader.ImageBase;DWORD NumberOfSectinsCount = pNtHeaders->FileHeader.NumberOfSections;DbgPrint("镜像基址 = %p | 节表数量 = %d \n", ImageBase, NumberOfSectinsCount);for (DWORD each = 0; each < NumberOfSectinsCount; each++){DWORD Section_Start = ImageBase + pSection[each].VirtualAddress;                                  // 获取节的开始地址DWORD Section_Ends = ImageBase + pSection[each].VirtualAddress + pSection[each].Misc.VirtualSize; // 获取节的结束地址DbgPrint("Section开始地址 = %p | Section结束地址 = %p \n", Section_Start, Section_Ends);if (dwVA >= Section_Start && dwVA <= Section_Ends){DWORD RVA = dwVA - pNtHeaders->OptionalHeader.ImageBase;                                    // 计算RVADWORD FOA = pSection[each].PointerToRawData + (RVA - pSection[each].VirtualAddress);       // 计算FOADbgPrint("VA偏移 [ %p ] --> 对应FOA地址 [ %p ] \n", dwVA, FOA);}}ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);ZwClose(hSection);ZwClose(hFile);Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}

运行效果如下所示,此处没有出现想要的结果是因为我们当前的VA内存地址并非实际装载地址,仅仅是PE磁盘中的地址,此处如果换成内存中的PE则可以提取出正确的结果;

9.4.3 RVA偏移转换为FOA偏移

将相对偏移地址转换为FOA文件偏移地址,此处仅仅只是多了一步pNtHeaders->OptionalHeader.ImageBase + dwRVARVA转换为VA的过程其转换结果与VA转FOA一致。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{DbgPrint("hello lyshark \n");NTSTATUS status = STATUS_SUCCESS;HANDLE hFile = NULL;HANDLE hSection = NULL;PVOID pBaseAddress = NULL;UNICODE_STRING FileName = { 0 };// 初始化字符串RtlInitUnicodeString(&FileName, L"\\??\\C:\\Windows\\System32\\ntoskrnl.exe");// 内存映射文件status = KernelMapFile(FileName, &hFile, &hSection, &pBaseAddress);if (!NT_SUCCESS(status)){return 0;}// 获取PE头数据集PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHeaders);PIMAGE_FILE_HEADER pFileHeader = &pNtHeaders->FileHeader;DWORD64 dwRVA = 0x89200;DWORD64 ImageBase = pNtHeaders->OptionalHeader.ImageBase;DWORD NumberOfSectinsCount = pNtHeaders->FileHeader.NumberOfSections;DbgPrint("镜像基址 = %p | 节表数量 = %d \n", ImageBase, NumberOfSectinsCount);for (DWORD each = 0; each < NumberOfSectinsCount; each++){DWORD Section_Start = pSection[each].VirtualAddress;                                  // 计算RVA开始位置DWORD Section_Ends = pSection[each].VirtualAddress + pSection[each].Misc.VirtualSize; // 计算RVA结束位置if (dwRVA >= Section_Start && dwRVA <= Section_Ends){DWORD VA = pNtHeaders->OptionalHeader.ImageBase + dwRVA;                                  // 得到VA地址DWORD FOA = pSection[each].PointerToRawData + (dwRVA - pSection[each].VirtualAddress);    // 得到FOADbgPrint("RVA偏移 [ %p ] --> 对应FOA地址 [ %p ] \n", dwRVA, FOA);}}ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);ZwClose(hSection);ZwClose(hFile);Driver->DriverUnload = UnDriver;return STATUS_SUCCESS;
}

运行效果如下所示;

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

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

相关文章

内部网关协议_路由信息协议RIP_开放路径优先OSPF协议_基本知识

目录: 因特网路由选择协议概述 路由信息协议RIP 开放路径优先OSPF协议 因特网路由选择协议概述 一.路由选择分类 静态路由选择和动态路由选择 静态路由选择: 采用人工配置的方式给路由器添加网络路由、默认路由和特定主机路由等路由条目。静态路由选择简单、开销小&#…

代码随想录算法训练营第四十四天【动态规划part06】 | 完全背包、518. 零钱兑换 II、377. 组合总和 Ⅳ

完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 题目链接&#xff1a; 题目页…

《实现领域驱动设计》笔记——上下文映射图

一个项目的上下文映射图可以用方式来表示。比较容易的一种是画一个简单的框图表示两个或多个限界上下文之间的映射关系。该框图表示了不同的限界上下文在解决方案空间中是如何通过集成相互关联的。另一种更详细的方式是通过限界上下文集成的源代码实现来表示。 上下文映射图为什…

php字符串处理函数的使用

php字符串处理函数的使用 trim() trim()函数的功能用于去除字符串首尾的空白字符(包括空格、制表符、换行符等&#xff09;。它可以用于清理用户输入的数据或去除字符串中的多余空格。 <?php $char" holle world! ";echo trim($char) ?>str_repl…

Bean基本注解开发

Commponent 使用Component注解代替<bean>标签 <!--注解扫描:扫描指定的基本包及其子包下的类&#xff0c;识别使用了Component注解的文件--><context:component-scan base-package"org.xfy"></context:component-scan> package org.xfy.Dao.…

登陆页面模板

简单好看的登陆页面 vue项目代码 可忽略js部分 先来个效果图 <template><div class"login"><div class"content"><p >账户密码登录</p><div class"unit"><label class"label">用户名</…

springcloud宿舍管理系统源码

开发技术&#xff1a; jdk1.8&#xff0c;mysql5.7&#xff0c;idea&#xff0c;vscode springcloud springboot mybatis vue elementui 功能介绍&#xff1a; 用户端&#xff1a; 登录注册 首页展示轮播&#xff0c;公告&#xff0c;报修&#xff0c;晚归登记&#xff0…

【bug 回顾】上传图片超时

测试 bug 问题分析 - 上传图片超时 最近在测试上遇到一个莫名奇妙的问题&#xff0c;最后也没有得到具体是哪块的原因&#xff0c;看各位大佬有没有思路&#xff1f;&#xff1f; 一 、背景 现在我们有三台服务器&#xff0c;用来布两套环境。其中另外一台服务器3配置的 tom…

SpringSecurity5|12.实现RememberMe 及 实现原理分析

security/day08 这个功能大家还熟悉么&#xff1f;我们在登录网站的时候&#xff0c;除了让你输入用户名和密码&#xff0c;还会有个勾选框&#xff1a; 记住我&#xff01;&#xff01;&#xff01;不是让大家记住我哈。 值得一提的是&#xff0c;Spring Security 也提供了这个…

Midjourney绘画提示词Prompt参考教程

Midjourney绘画提示词Prompt参考教程&#xff1a;无需魔法使用。 一、AI工具 SparkAi&#xff1a; SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常…

二、类与对象(二)

8 this指针 8.1 this指针的引入 我们先来定义一个日期的类Date&#xff1a; #include <iostream> using namespace std; class Date { public:void Init(int year, int month, int day){_year year;_month month;_day day;}void Print(){cout << _year <&l…

在使用tomcat运行项目时,遇到端口80被占用的情况问题解决

问题描述&#xff1a;Failed to initialize end point associated with ProtocolHandler ["http-bio-80"] java.net.BindException: Address already in use: NET_Bind <null>:80 在学习springmvc的时候&#xff0c;跟着黑马视频进行学习&#xff0c;结果&…

在.bashrc文件修改环境变量的做法

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> ~/.bashrc文件是linux下保存环境变量的系统文件。原以为使用sed命令修改.bashrc文件&#xff0c;实际上不行&#xff0c;需要使用echo命令。具体示例如下…

HTML+CSS+ElementUI搭建个人博客静态页面展示(纯前端)

网站演示 登录页面 门户页面 搭建过程 技术选取:HTML/CSS VUE2 ElementUI(Version - 2.15.14)编程软件:VSCode 环境配置与搭建 安装指令 1. 先确保你的电脑已经安装好了npm和node npm -vnode -v2. ElementUI下载&#xff0c;推荐使用 npm 的方式安装 npm i element-ui…

给sprite上增加刷光动效

游戏引擎 —— cocos creator 3.52 此动效给动态修改尺寸的图片增加一层刷光的效果&#xff0c;直接贴代码 CCEffect %{techniques:- passes:- vert: sprite-vs:vertfrag: sprite-fs:fragdepthStencilState:depthTest: falsedepthWrite: falseblendState:targets:- blend: tr…

K8S(一)

一、kubernetes 概述 1、kubernetes 基本介绍 kubernetes&#xff0c;简称 K8s&#xff0c;是用 8 代替 8 个字符“ubernete”而成的缩写。是一个开源的&#xff0c;用于管理云平台中多个主机上的容器化的应用&#xff0c;Kubernetes 的目标是让部署容器化的 应用简单并且高效…

Pytorch中的tensor维度理解

Pytorch中的tensor维度理解 文章目录 Pytorch中的tensor维度理解摘要打消心理恐惧&#xff0c;从三维学起三维tensor参考文献 摘要 面对pytorch编程中的tensor时&#xff0c;我不时会感到恐惧。对里面数据是怎么排布的&#xff0c;一直没有一个直观的理解。今天我想把这个事情…

趣学python编程 (五、常用IDE环境推荐)

Python环境指的是在计算机上安装Python解释器和相关的库&#xff0c;它是运行Python代码所必需的。那么开始Python编程前&#xff0c;准备安装好开发环境是前提。 默认的电脑上只是让人办公使用的&#xff0c;不带python编程开发环境。只有安装python环境&#xff0c;才可以编写…

Mybatis-Plus《学习笔记 22版尚硅谷 》——感谢【尚硅谷】官方文档

Mybatis-Plus《学习笔记 22版尚硅谷 》 一、MyBatis-Plus1.简介2.特性3.支持数据库4.框架结构5.官方地址 二、入门案例1.开发环境2.建库建表3.创建工程4.配置编码5.测试查询 三、增删改查1.BaseMapper<T>2.调用Mapper层实现CRUD2.1 插入2.2 删除a、根据ID删除数据b、根据…

代码随想录算法训练营第四十二天【动态规划part04】 | 01背包、416. 分割等和子集

01背包问题 题目链接&#xff1a; 题目页面 求解思路&#xff1a; 确定dp数组及其下标含义&#xff1a;dp[i][j] 表示从下标为 [0] 到 [i] 的物品里任意选取&#xff0c;放进容量为j的背包&#xff0c;此时的价值总和最大值确定递推公式&#xff1a; 不放物品i&#xff0c;…