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

news2025/2/27 13:27:43

本章将继续探索内核中解析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);     // 计算出RVA
            DWORD64 VA = RVA + pNtHeaders->OptionalHeader.ImageBase;                                     // 计算出VA
            DbgPrint("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;                                    // 计算RVA
            DWORD FOA = pSection[each].PointerToRawData + (RVA - pSection[each].VirtualAddress);       // 计算FOA
            
            DbgPrint("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);    // 得到FOA
            DbgPrint("RVA偏移 [ %p ] --> 对应FOA地址 [ %p ] \n", dwRVA, FOA);
        }
    }

    ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);
    ZwClose(hSection);
    ZwClose(hFile);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

运行效果如下所示;

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【论文阅读笔记】Emu Edit: Precise Image Editing via Recognition and Generation Tasks

【论文阅读笔记】Emu Edit: Precise Image Editing via Recognition and Generation Tasks 论文阅读笔记论文信息摘要背景方法结果额外 关键发现作者动机相关工作1. 使用输入和编辑图像的对齐和详细描述来执行特定的编辑2. 另一类图像编辑模型采用输入掩码作为附加输入 。3. 为…

第三节-Android10.0 Binder通信原理(三)-ServiceManager篇

1、概述 在Android中&#xff0c;系统提供的服务被包装成一个个系统级service&#xff0c;这些service往往会在设备启动之时添加进Android系统&#xff0c;当某个应用想要调用系统某个服务的功能时&#xff0c;往往是向系统发出请求&#xff0c;调用该服务的外部接口。在上一节…

Vue批量全局处理undefined和null转为““ 空字符串

我们在处理后台返回的信息&#xff0c;有的时候返回的是undefined或者null&#xff0c;这种字符串容易引起用户的误解&#xff0c;所以需要我们把这些字符串处理一下。 如果每个页面都单独处理&#xff0c;那么页面会很冗余&#xff0c;并且后期如果有修改容易遗漏&#xff0c…

生成式AI与大语言模型,东软已经准备就绪

伴随着ChatGPT的火爆全球&#xff0c;数以百计的大语言模型也争先恐后地加入了这一战局&#xff0c;掀起了一场轰轰烈烈的“百模大战”。毋庸置疑的是&#xff0c;继方兴未艾的人工智能普及大潮之后&#xff0c;生成式AI与大语言模型正在全球开启新一轮生产力革新的科技浪潮。 …

PostgreSQL (Hologres) 日期生成

PostgreSQL 生成指定日期下一个月的日期 &#xff08;在Hologres中&#xff0c;不支持递归查询&#xff09; SELECTto_char(T, YYYYMMDD)::int4 AS date_int,date(T) AS date_str,date_part(year, T)::int4 AS year_int,date_part(month, T)::int4 AS month_int,date_part(da…

中职组网络安全B模块-渗透提权2

任务五&#xff1a;渗透提权2 任务环境说明&#xff1a; 仅能获取xxx的IP地址 用户名&#xff1a;test&#xff0c;密码&#xff1a;123456 访问服务器主机&#xff0c;找到主机中管理员名称&#xff0c;将管理员名称作为Flag值提交&#xff1b; Flag:doyoudoyoudo 访问服…

使用Pytorch从零开始构建DCGAN

在本文中&#xff0c;我们将深入研究生成建模的世界&#xff0c;并使用流行的 PyTorch 框架探索 DCGAN&#xff08;生成对抗网络 (GAN) 的一种变体&#xff09;的实现。具体来说&#xff0c;我们将使用 CelebA 数据集&#xff08;名人面部图像的集合&#xff09;来生成逼真的合…

微服务学习|初识Docker、使用Docker、自定义镜像、DockerCompose、Docker镜像仓库

初识Docker 项目部署的问题 大型项目组件较多&#xff0c;运行环境也较为复杂&#xff0c;部署时会碰到一些问题 依赖关系复杂&#xff0c;容易出现兼容性问题 开发、测试、生产环境有差异 Docker如何解决依赖的兼容问题的? 将应用的Libs (函数库)、Deps (依赖)配置与应用…

c语言:用迭代法解决递归问题

题目&#xff1a; 解释&#xff1a;题目的意思就是用迭代法的空间和时间复杂的太高了&#xff0c;需要我们减小空间与时间的复杂度&#xff0c;我就想到了迭代法&#xff0c;思路和代码如下&#xff1a; #include <stdio.h> //这里是递归法转迭代法 int main() {int x,i…

Spark---转换算子、行动算子、持久化算子

一、转换算子和行动算子 1、Transformations转换算子 1&#xff09;、概念 Transformations类算子是一类算子&#xff08;函数&#xff09;叫做转换算子&#xff0c;如map、flatMap、reduceByKey等。Transformations算子是延迟执行&#xff0c;也叫懒加载执行。 2)、Transf…

Leetcode——121 买卖股票的最佳时机

(超时。。。。。。&#xff09;除了暴力法我是真的。。。。。。 class Solution {public int maxProfit(int[] prices) {int len prices.length;int max0;for(int i0;i<len-1;i){for(int ji1;j<len;j){int income prices[j] - prices[i];if(income>max){maxincome;…

路由的控制与转发原理

场景1&#xff1a;路由器收到数据包后&#xff0c;会根据数据包的目标IP地址&#xff0c;计算出目标网段&#xff0c;再确定终端设备的具体位置。这个过程中&#xff0c;还需要计算出接口&#xff0c;或数据包下一跳的地址。最终会生成一条路由&#xff0c;即路径&#xff0c;存…

外部中断为什么会误触发?

今天在写外部中断的程序的时候&#xff0c;发现中断特别容易受到干扰&#xff0c;我把手放在对应的中断引脚上&#xff0c;中断就一直触发&#xff0c;没有停过。经过一天的学习&#xff0c;找到了几个解决方法&#xff0c;所以写了这篇笔记。如果你的中断也时不时会误触发&…

基于JavaWeb+SSM+Vue教学辅助微信小程序系统的设计和实现

基于JavaWebSSMVue教学辅助微信小程序系统的设计和实现 源码获取入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码获取入口 前言 1.1 概述 随着信息时代的快速发展&#xff0c;互联网的优势和普及&#xff0c;人们生活…

二、防火墙-安全策略

学习防火墙之前&#xff0c;对路由交换应要有一定的认识 1、安全策略基本概念2、匹配顺序3、缺省包过滤4、安全策略发展史4.1.基于ACL包过滤4.2.融合UTM安全策略4.3.一体化安全策略 5、Local区域安全策略5.1.针对OSPF协议配置Local区域安全策略5.2.其他协议 6、ASPF6.1.帮助FTP…

PCIE链路训练-状态跳转1

A&#xff1a;12ms超时&#xff0c;或者再任何lane上检测到Electrical Idle Exit&#xff1b; B&#xff1a; 1.发送“receiver detection”之后没有一个lane的接收逻辑被rx检测到 2.不满足条件c&#xff0c;比如两次detection出现差别&#xff1b; C&#xff1a;发送端在没…

文本分析:NLP 魔法!

一、说明 这是一个关于 NLP 和分类项目的博客。NLP 是自然语言处理&#xff0c;目前需求量很大。让我们了解如何利用 NLP。我们将通过编码来理解流程和概念。我将在本博客中介绍 BagOfWords 和 n-gram 以及朴素贝叶斯分类模型。这个博客的独特之处&#xff08;这使得它很长&…

竞赛选题 车道线检测(自动驾驶 机器视觉)

0 前言 无人驾驶技术是机器学习为主的一门前沿领域&#xff0c;在无人驾驶领域中机器学习的各种算法随处可见&#xff0c;今天学长给大家介绍无人驾驶技术中的车道线检测。 1 车道线检测 在无人驾驶领域每一个任务都是相当复杂&#xff0c;看上去无从下手。那么面对这样极其…

Oracle 的 Java SE、OpenJDK、Database 链接

1 访问主站 Oracle | Cloud Applications and Cloud Platform 2 开发者 2.1 OpenJDK (这里的不用登录&#xff0c;就可以下载) JDK Builds from Oracle 2.2 JavaSE (需要登录&#xff0c;才可以下载) Java Downloads | Oracle 2.3 DataBase (MySQL为例) MySQL :: MySQL Dow…

排序算法--快速排序

实现逻辑 ① 从数列中挑出一个元素&#xff0c;称为 “基准”&#xff08;pivot&#xff09;&#xff0c; ② 重新排序数列&#xff0c;所有元素比基准值小的摆放在基准前面&#xff0c;所有元素比基准值大的摆在基准的后面&#xff08;相同的数可以到任一边&#xff09;。在这…