首页 > 系统相关 >驱动开发:内核读写内存浮点数

驱动开发:内核读写内存浮点数

时间:2023-05-30 09:25:11浏览次数:40  
标签:字节 浮点数 内存 Address BYTE 读写 内核

如前所述,在前几章内容中笔者简单介绍了内存读写的基本实现方式,这其中包括了CR3切换读写,MDL映射读写,内存拷贝读写,本章将在如前所述的读写函数进一步封装,并以此来实现驱动读写内存浮点数的目的。内存浮点数的读写依赖于读写内存字节的实现,因为浮点数本质上也可以看作是一个字节集,对于单精度浮点数来说这个字节集列表是4字节,而对于双精度浮点数,此列表长度则为8字节。

如下代码片段摘取自本人的LyMemory驱动读写项目,函数ReadProcessMemoryByte用于读取内存特定字节类型的数据,函数WriteProcessMemoryByte则用于写入字节类型数据,完整代码如下所示;

这段代码中依然采用了《驱动开发:内核MDL读写进程内存》中所示的读写方法,通过MDL附加到进程并RtlCopyMemory拷贝数据,至于如何读写字节集只需要循环读写即可实现;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

#include <ntifs.h>
#include <windef.h>

// 读取内存字节
BYTE ReadProcessMemoryByte(HANDLE Pid, ULONG64 Address, DWORD Size)
{
	KAPC_STATE state = { 0 };
	BYTE OpCode;

	PEPROCESS Process;
	PsLookupProcessByProcessId((HANDLE)Pid, &Process);

	// 绑定进程对象,进入进程地址空间
	KeStackAttachProcess(Process, &state);

	__try
	{
		// ProbeForRead 检查内存地址是否有效, RtlCopyMemory 读取内存
		ProbeForRead((HANDLE)Address, Size, 1);
		RtlCopyMemory(&OpCode, (BYTE *)Address, Size);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		// 调用KeUnstackDetachProcess解除与进程的绑定,退出进程地址空间
		KeUnstackDetachProcess(&state);

		// 让内核对象引用数减1
		ObDereferenceObject(Process);
		// DbgPrint("读取进程 %d 的地址 %x 出错", ptr->Pid, ptr->Address);
		return FALSE;
	}

	// 解除绑定
	KeUnstackDetachProcess(&state);
	// 让内核对象引用数减1
	ObDereferenceObject(Process);
	DbgPrint("[内核读字节] # 读取地址: 0x%x 读取数据: %x \n", Address, OpCode);

	return OpCode;
}

// 写入内存字节
BOOLEAN WriteProcessMemoryByte(HANDLE Pid, ULONG64 Address, DWORD Size, BYTE *OpCode)
{
	KAPC_STATE state = { 0 };

	PEPROCESS Process;
	PsLookupProcessByProcessId((HANDLE)Pid, &Process);

	// 绑定进程,进入进程的地址空间
	KeStackAttachProcess(Process, &state);

	// 创建MDL地址描述符
	PMDL mdl = IoAllocateMdl((HANDLE)Address, Size, 0, 0, NULL);
	if (mdl == NULL)
	{
		return FALSE;
	}

	//使MDL与驱动进行绑定
	MmBuildMdlForNonPagedPool(mdl);
	BYTE* ChangeData = NULL;

	__try
	{
		// 将MDL映射到我们驱动里的一个变量,对该变量读写就是对MDL对应的物理内存读写
		ChangeData = (BYTE *)MmMapLockedPages(mdl, KernelMode);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		// DbgPrint("映射内存失败");
		IoFreeMdl(mdl);

		// 解除映射
		KeUnstackDetachProcess(&state);
		// 让内核对象引用数减1
		ObDereferenceObject(Process);
		return FALSE;
	}

	// 写入数据到指定位置
	RtlCopyMemory(ChangeData, OpCode, Size);
	DbgPrint("[内核写字节] # 写入地址: 0x%x 写入数据: %x \n", Address, OpCode);

	// 让内核对象引用数减1
	ObDereferenceObject(Process);
	MmUnmapLockedPages(ChangeData, mdl);
	KeUnstackDetachProcess(&state);
	return TRUE;
}

实现读取内存字节集并将读入的数据放入到LySharkReadByte字节列表中,这段代码如下所示,通过调用ReadProcessMemoryByte都内存字节并每次0x401000 + i在基址上面增加变量i以此来实现字节集读取;

// 驱动入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	// 读内存字节集
	BYTE LySharkReadByte[8] = { 0 };

	for (size_t i = 0; i < 8; i++)
	{
		LySharkReadByte[i] = ReadProcessMemoryByte(4884, 0x401000 + i, 1);
	}

	// 输出读取的内存字节
	for (size_t i = 0; i < 8; i++)
	{
		DbgPrint("[+] 打印数据: %x \n", LySharkReadByte[i]);
	}

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

运行如上代码片段,你会看到如下图所示的读取效果;

那么如何实现写内存字节集呢?其实写入内存字节集与读取基本类似,通过填充LySharkWriteByte字节集列表,并调用WriteProcessMemoryByte函数依次循环字节集列表即可实现写出字节集的目的;

// 驱动入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	// 内存写字节集
	BYTE LySharkWriteByte[8] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };

	for (size_t i = 0; i < 8; i++)
	{
		BOOLEAN ref = WriteProcessMemoryByte(4884, 0x401000 + i, 1, LySharkWriteByte[i]);
		DbgPrint("[*] 写出状态: %d \n", ref);
	}

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

运行如上代码片段,即可将LySharkWriteByte[8]中的字节集写出到内存0x401000 + i的位置处,输出效果图如下所示;

接下来不如本章的重点内容,首先如何实现读内存单精度与双精度浮点数的目的,实现原理是通过读取BYTE类型的前4或者8字节的数据,并通过*((FLOAT*)buffpyr)将其转换为浮点数,通过此方法即可实现字节集到浮点数的转换,而决定是单精度还是双精度则只是一个字节集长度问题,这段读写代码实现原理如下所示;

// 读内存单精度浮点数
FLOAT ReadProcessFloat(DWORD Pid, ULONG64 Address)
{
	BYTE buff[4] = { 0 };
	BYTE* buffpyr = buff;

	for (DWORD x = 0; x < 4; x++)
	{
		BYTE item = ReadProcessMemoryByte(Pid, Address + x, 1);
		buff[x] = item;
	}

	return *((FLOAT*)buffpyr);
}

// 读内存双精度浮点数
DOUBLE ReadProcessMemoryDouble(DWORD Pid, ULONG64 Address)
{
	BYTE buff[8] = { 0 };
	BYTE* buffpyr = buff;

	for (DWORD x = 0; x < 8; x++)
	{
		BYTE item = ReadProcessMemoryByte(Pid, Address + x, 1);
		buff[x] = item;
	}

	return *((DOUBLE*)buffpyr);
}

// 驱动卸载例程
VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint("Uninstall Driver \n");
}

// 驱动入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	// 读取单精度
	FLOAT fl = ReadProcessFloat(4884, 0x401000);
	DbgPrint("[读取单精度] = %d \n", fl);

	// 读取双精度浮点数
	DOUBLE fl = ReadProcessMemoryDouble(4884, 0x401000);
	DbgPrint("[读取双精度] = %d \n", fl);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

如上代码就是实现浮点数读写的关键所在,这段代码中的浮点数传值如果在内核中会提示无法解析的外部符号 _fltused此处只用于演示核心原理,如果想要实现不报错,该代码中的传值操作应在应用层进行,而传入参数也应改为字节类型即可。

同理,对于写内存浮点数而言依旧如此,只是在接收到用户层传递参数后应对其dtoc双精度浮点数转为CHAR或者ftoc单精度浮点数转为CHAR类型,再写出即可;

// 将DOUBLE适配为合适的Char类型
VOID dtoc(double dvalue, unsigned char* arr)
{
	unsigned char* pf;
	unsigned char* px;
	unsigned char i;

	// unsigned char型指针取得浮点数的首地址
	pf = (unsigned char*)&dvalue;

	// 字符数组arr准备存储浮点数的四个字节,px指针指向字节数组arr
	px = arr;

	for (i = 0; i < 8; i++)
	{
		// 使用unsigned char型指针从低地址一个字节一个字节取出
		*(px + i) = *(pf + i);
	}
}

// 将Float适配为合适的Char类型
VOID ftoc(float fvalue, unsigned char* arr)
{
	unsigned char* pf;
	unsigned char* px;
	unsigned char i;

	// unsigned char型指针取得浮点数的首地址
	pf = (unsigned char*)&fvalue;

	// 字符数组arr准备存储浮点数的四个字节,px指针指向字节数组arr
	px = arr;

	for (i = 0; i < 4; i++)
	{
		// 使用unsigned char型指针从低地址一个字节一个字节取出
		*(px + i) = *(pf + i);
	}
}

// 写内存单精度浮点数
BOOL WriteProcessMemoryFloat(DWORD Pid, ULONG64 Address, FLOAT write)
{
	BYTE buff[4] = { 0 };
	ftoc(write, buff);

	for (DWORD x = 0; x < 4; x++)
	{
		BYTE item = WriteProcessMemoryByte(Pid, Address + x, buff[x], 1);
		buff[x] = item;
	}

	return TRUE;
}

// 写内存双精度浮点数
BOOL WriteProcessMemoryDouble(DWORD Pid, ULONG64 Address, DOUBLE write)
{
	BYTE buff[8] = { 0 };
	dtoc(write, buff);

	for (DWORD x = 0; x < 8; x++)
	{
		BYTE item = WriteProcessMemoryByte(Pid, Address + x, buff[x], 1);
		buff[x] = item;
	}

	return TRUE;
}

// 驱动卸载例程
VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint("Uninstall Driver \n");
}

// 驱动入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	// 写单精度
	FLOAT LySharkFloat1 = 12.5;
	INT fl = WriteProcessMemoryFloat(4884, 0x401000, LySharkFloat1);
	DbgPrint("[写单精度] = %d \n", fl);

	// 读取双精度浮点数
	DOUBLE LySharkFloat2 = 12.5;
	INT d1 = WriteProcessMemoryDouble(4884, 0x401000, LySharkFloat2);
	DbgPrint("[写双精度] = %d \n", d1);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

标签:字节,浮点数,内存,Address,BYTE,读写,内核
From: https://www.cnblogs.com/LyShark/p/17182633.html

相关文章

  • 如何修改notebook内核对应的python地址
    要修改JupyterNotebook内核对应的Python地址,可以按照以下步骤进行:打开终端或命令提示符。列出当前可用的JupyterNotebook内核。运行以下命令:jupyterkernelspeclist,这将列出已安装的内核及其位置。找到您想要修改的内核对应的配置文件。在上一步的输出中,找到......
  • 什么是浮点数加减运算里的对阶,阶码和尾数
    在浮点数加减运算中,对阶是一种重要的步骤,它用于将参与运算的浮点数调整为同一数量级,以便进行精确的计算。对阶涉及到阶码和尾数的概念。在本文中,我将解释这些概念并提供具体的例子,以便更好地理解。首先,浮点数表示法是一种用于表示实数的方法,其中数值被分为阶码和尾数两部分。通常......
  • [ChatGPT 勘误] SAP SLT 是只能将数据从其他DB 往 HANA 写,还是支持双向读写?
    我的SAP技术交流群里有朋友遇到一个问题:同志们帮忙确认一个问题SLT是只能将数据从其他DB往HANA写是吧,,不能从HANA往其他系统写,,是的么?这个朋友求助ChatGPT,后者的回答是,SAPSLT只能将其他数据同步到SAPHANA数据库,而不支持将SAPHANA数据库里的数据导出并同步到其他......
  • 驱动开发:内核解析内存四级页表
    当今操作系统普遍采用64位架构,CPU最大寻址能力虽然达到了64位,但其实仅仅只是用到了48位进行寻址,其内存管理采用了9-9-9-9-12的分页模式,9-9-9-9-12分页表示物理地址拥有四级页表,微软将这四级依次命名为PXE、PPE、PDE、PTE这四项。关于内存管理和分页模式,不同的操作系统和体系结构......
  • 【2023 · CANN训练营第一季】——Ascend C算子代码分析—Add算子(内核调用符方式)
    前言:AscendC算子(TIKC++)使用C/C++作为前端开发语言,通过四层接口抽象、并行编程范式、孪生调试等技术,极大提高算子开发效率,助力AI开发者低成本完成算子开发和模型调优部署。学习完理论后,上代码,通过实践理解AscendC算子的概念,掌握开发流程,以及内核调用符方式的调试方法。一、算子分......
  • 浅探荀子性“恶”伦理观的理论内核
    [摘要)荀子立足于现实的利益关系,挖掘“性”、“伪”关系矛盾展开其伦理思想。于是人性与礼义道德之间构成一对矛盾体:道德总是基于人性并不断验证人性恶的存在;人性总是背离道德又不断服从道德,实现对恶的超越。接着他提出“众人之伪”的道德层次论,实现“性”与“伪”的统一:“无性则......
  • Rockchip RK3399 - 移植uboot 2023.04和内核FIT uImage制作
    ----------------------------------------------------------------------------------------------------------------------------开发板:NanoPC-T4开发板eMMC:16GBLPDDR3:4GB显示屏:15.6HDMI接口显示屏u-boot:2023.04linux  :5.2.8------------------------------------------......
  • redhat6和redhat7系统内核升级
    redhat6和redhat7系统内核升级应用环境:客户漏扫内核版本不满足要求。咨询测试:同事说6通过挂载7yum源方式,可以更新到7的内核版本,测试发现行不通。因系统版本问题更新后reboot系统不能进入。查找国内6的内核源,已被清除,通过其它下载渠道下载了一个6的版本,下文更新示例。国内7的内核......
  • 5.26 C++文件读写操作
    程序运行时产生的数据都属于临时数据,程序—旦运行结束都会被释放通过文件可以将数据持久化C++中对文件操作需要包含头文件<fstream>文件类型分为两种:1.文本文件:文件以文本的ASCII码形式存储在计算机中2.二进制文件:文件以文本的二进制形式存储在计算机中操作文件的三大类:ofst......
  • k8s中,怎么修改pod的内核参数?
    对于docker容器,可以在run的时候,指定--sysctl和对应的内核参数 那么,对于pod该如何进行修改? 接下来的内容,将会告诉你,如何进行修改 方法非常的简单...... 通过initContainer 示例如下: 1、编辑deployment文件kubectleditdeployphp-apache  2、增加init......