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

news2025/1/8 20:13:35

本章将继续探索内核中解析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指令,说明计算正确。

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

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

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("hello lyshark.com \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;
}

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

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

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("hello lyshark.com \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则可以提取出正确的结果;

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

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("hello lyshark.com \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/602614.html

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

相关文章

第二十篇、基于Arduino uno,获取MFRC-522 RFID射频IC卡模块的信息,并进行识别——结果导向

0、结果 说明&#xff1a;先来看看串口调试助手显示的结果&#xff0c;"卡号"对应的是IC卡的卡号&#xff0c;"1号已进入"表示识别到正确的卡&#xff0c;也就是之前录入的卡号&#xff0c;因此可以应用到门禁。如果是你想要的&#xff0c;可以接着往下看…

OLED和LCD对比

OLED和LCD对比 今天等离子已不复存在&#xff0c;OLED 出现了新的竞争者。在本概述中&#xff0c;我们将了解这两种技术之间的差异、优缺点等。 我们将从简短介绍每种显示技术的工作原理开始。我们将使用术语“面板”&#xff0c;因为显示技术是功能性电视屏幕的一个组成部分…

(字符串 ) 151. 反转字符串中的单词 ——【Leetcode每日一题】

❓151. 反转字符串中的单词 难度&#xff1a;中等 给你一个字符串 s &#xff0c;请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。 注意&…

面向对象编程 实验三 sduwh 子窗口与控件的基本用法、资源的使用 参考实验报告1

源自网络收集&#xff0c;仅供参考 实验三收集到两份完整报告&#xff0c;这是其一&#xff0c;另一份见本专栏下一篇文章。 实验题目 《面向对象程序设计》 实验三 实验题目&#xff1a;子窗口与控件的基本用法、资源的使用 整体目的&#xff1a;理解、窗口之间的消息传送…

DAY08_JavaScrip

目录 1 JavaScript1.1 JavaScript简介1.2 JavaScript引入方式1.2.1 内联脚本1.2.2 内部脚本1.2.3 外部脚本 1.3 JavaScript基础语法1.3.1 书写语法1.3.2 输出语句1.3.3 变量1.3.4 数据类型1.3.5 运算符1.3.5.1 \和区别1.3.5.2 类型转换 1.3.6 流程控制语句1.3.6.1 if 语句1.3.6…

常见设计模式

单例模式 单例对象的类必须保证只有一个实例存在&#xff0c;整个系统只能使用一个对象实例&#xff0c;优点&#xff1a;不会频繁地创建和销毁对象&#xff0c;浪费系统资源。缺点是没有抽象层&#xff0c;难以扩展。 单例模式的常见写法&#xff1a; 饿汉式单例模式的写法&…

免安装版MySQL数据库的安装和卸载

说明&#xff1a;MySQL早些版本有分安装版和免安装版&#xff0c;可在官网&#xff08;https://dev.mysql.com/downloads/mysql/&#xff09;下载&#xff0c;推荐使用最新版本&#xff0c;是免安装版的&#xff0c;下载完配置一下就可以用。 推荐使用最新版本 安装 我这里以…

第3章“程序的机器级表示”:过程

文章目录 3.7 过程3.7.1 栈帧3.7.2 转移控制3.7.3 寄存器使用惯例3.7.4 过程示例3.7.5 递归过程 3.7 过程 一个过程调用包括将数据&#xff08;以过程参数和返回值的形式&#xff09;和控制从代码的一部分传递到另一部分。另外&#xff0c;它还必须在进入时为过程的局部变量分…

金融人不能错过的中国人民大学与加拿大女王大学金融硕士,你不能不知道

金融行业是一个发展飞速的行业&#xff0c;越来越多的优秀人士的涌入&#xff0c;让本就卷起来的金融行业变得异常拥挤&#xff0c;怎么办&#xff0c;想留有一席之地只能不断的提升与攀登&#xff0c;金融人不能错过的中国人民大学与加拿大女王大学金融硕士&#xff0c;你不能…

架构EA演进

架构演进 目录概述需求&#xff1a; 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for change,challenge Survive. happy for hardess to solve den…

spring cloud搭建(service)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

【C++】海量数据处理面试题(位图和布隆过滤器)

都是大厂面试题哦~ 文章目录 一.位图面试题 1.给定100亿个整数&#xff0c;设计算法找到只出现一次的整数 2.给两个文件&#xff0c;分别有100亿个整数&#xff0c;我们只有1G内存&#xff0c;如何找到两个文件交集&#xff1f; 3.1个文件有100亿个int&#xff0c;1G内…

等价类,边界值,场景法的使用方法和运用场景

等价类&#xff1a; 在很多情况下&#xff0c;很多人想到的测试方法是穷举测试&#xff0c;穷举测试是最全面的测试&#xff0c;但是数据量很大的情况下不太现实&#xff0c;测试效率太低&#xff0c;后来为了减少测试人员的工作量和提高测试的效率和以达到最好的测试质量&…

启明星辰集团CEO严望佳:与AI共生,共建以人为本的数字善治生态体系

近日&#xff0c;2023中国国际大数据产业博览会在贵阳成功召开。启明星辰集团董事长兼首席执行官严望佳应邀出席大会“数据安全产业高质量发展”高端对话&#xff0c;发表“主动应对ChatGPT技术冲击&#xff0c;加强数据安全风险防控”主题演讲&#xff0c;同与会人士共探数据安…

32.有序序列插入一个整数(刷题)

描述 有一个有序数字序列&#xff0c;从小到大排序&#xff0c;将一个新输入的数插入到序列中&#xff0c;保证插入新数后&#xff0c;序列仍然是升序。 输入描述&#xff1a; 第一行输入一个整数N(0≤N≤50)。 第二行输入N个升序排列的整数&#xff0c;输入用空格分隔的N个…

2023年前端面试题汇总-浏览器原理

1. 浏览器安全 1.1. 什么是 XSS 攻击&#xff1f; 1.1. 1. 概念 XSS 攻击指的是跨站脚本攻击&#xff0c;是一种代码注入攻击。攻击者通过在网站注入恶意脚本&#xff0c;使之在用户的浏览器上运行&#xff0c;从而盗取用户的信息如 cookie 等。 XSS 的本质是因为网站没有对…

企业要从哪些方面着手进行数据安全治理?

什么是数据安全治理&#xff1f; 数据安全治理是指组织基于业务发展与合规要求&#xff0c;制定全面且系统的数据安全策略、流程与技术措施&#xff0c;对数据生命周期中的安全风险进行管控与优化的一系列管理活动。它需要从组织层面建立数据安全管理框架&#xff0c;保证敏感数…

2023-6-2-DIS研究

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f4a5;&#x1f4a5;&#x1f4a5;欢迎来到&#x1f91e;汤姆&#x1f91e;的csdn博文&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f49f;&#x1f49f;喜欢的朋友可以关注一下&#xf…

Java 列表导出

一、具体实现 import java.net.URLEncoder; import com.alibaba.excel.EasyExcel;List<实体> targets xxx; response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("UTF-8"); String fileName URLEncoder.encode(&…

深入理解设计原则之里氏替换原则(LSP)

系列文章目录 C高性能优化编程系列 深入理解设计原则系列 深入理解设计模式系列 高级C并发线程编程 LSP&#xff1a;里氏替换原则 系列文章目录1、里氏替换原则的定义和解读2、里氏替换原则可以用于哪些设计模式中&#xff1f;3、如何使用里氏替换原则来降低代码耦合度&#…