tiechui_lesson08_内存的分配和链表

主要是将链表结构的使用,在内核开发中使用起来比较方便的一种数据结构【LIST_ENTRY】。

一、内存的分配

主要是学习一些基本操作。现在推荐使用的动态分配函数【ExAllocatePoolWithTag

PVOID tempbuffer = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'xxaa');

 这样可以通过工具来找到标签对应的内存地址,我没找到视频里的【PoolMonEX.exe】,但是找到了【PoolMonX.exe】这个程序也可以用🤣

可以看到实际上的内容是和代码里边的顺序相反的,而且我之前申请的【‘aaxx’】非分页内存块,在驱动卸载后没有被释放掉(故意没free,驱动卸载不会自动释放),这样就只能重启操作系统了。

这个函数的第一参数确定这块内存的形式:

  • 【 PagedPool 】分页池—在需要时能够将页面换出的内存池
  • 【 NonPagedPool 】非分页池—永远不会换出页面,保证驻留在RAM里的内存池

 引用书中的描述,我觉非常好,虽然第一次看的时候没什么感觉😗😗😗

很显然,非分页池是一个“更好”的内存池,因为它不会导致页 面错误。在本书的后面我们会看到一些需要从非分页池分配的例子。 驱动程序要尽可能少地使用非分页池,除非必需。其他任何情况驱动 程序都应该使用分页池。POOL_TYPE这个枚举类型表示内存池的类型。 这个枚举类型包括很多内存池的“类型”,但是只有三种可以被驱动 程序使用:PagedPool、NonPagedPool和NonPagePoolNx(没有执行权限 的非分页池)。

 除此之外,在tiechuiDL视频中提到了更多的参数,在头文件【wdm.h】有这样的定义:

 【NonPagedPoolCacheAligned】传入这个参数,有一个对齐的概念,就是要求操作系统分配的这个内存是宽度对齐的。宽度的大小和寻址基线有关系,比方说32位系统就是四字节对齐,64位就是8字节对齐。

 【NonPagedPoolMustSucceed】传入这个参数,就要求操作系统必须分配成功一个非分页内存。

【NonPagedPoolCacheAlignedMustS】传入这个参数,是上边的超级组合:非分页+字节对齐+必须成功!

1.调试看细节

接下来是双机调试的骚操作,真的炫呀,tiechuiDL的双机调试连接怎么这么快!我就暂时用了WinDbg来看(VS2019调试Win7一直gg😒):

先搞一下入口断点:

bu ListEntry!driverentry

进入入口函数后使用Windbg打断点:

 之后使用g命令跳出,就和视频里边一样命中断点调试了...(还是好奇为啥tiechuiDL这么快???

【 bl 】查看已经打过的断点:

 【 dv /v 】查看变量状态

 【 db 0xfffffa80`1b178000 】查看tempbuffer内存地址的变化

 清零之后【RtlZeroMemory(tempbuffer, 0x1000);

 填充之后【RtlFillMemory(tempbuffer, 0x1000, 0xcc);

释放之后【ExFreePoolWithTag(tempbuffer, 'dcba');】中断一下再看,被别的进程用了...

调试就这样了,学到了咋通过命令看内存,然后介绍了两个运行时函数,用来内存比较:

  •     RtlCompareMemory
  •     RtlEqualMemory

RtlEqualMemory其实是宏定义而且用的比较多,主要是看一些区域相不相等。

2. Lookaside

最后还有一个概念是【Lookaside】

 Windows内存管理中使用了类似于容器的东西,叫做Lookaside对象,每次程序员申请内存都会从Lookaside里面申请,只有不足的时候,Lookaside才会向内存又一次申请内存空间,这样减少了频繁申请内存而导致的内存碎片

当Lookaside对象内部有大量没有使用的内存时候,它会自动让windows回收一部分内存,总之,Lookaside很智能。

 一般Lookaside用于以下情况:

  1. 程序员每次申请固定的内存大小
  2. 申请和回收内存的次数较多,很频繁

其实这个在官网的例子还出现挺频繁的,不过不在这里展开了,下次一定😉

二、链表 LIST_ENTRY

接下来是想通过链表来存储进程信息。

1.定义链表

链表结构的定义:

//
// 链表结构定义
//
typedef struct _MyStruct
{
	LIST_ENTRY list;			// 加入链表

	HANDLE pid;					// 进程PID
	PEPROCESS peprocesspbj;		// 进程对象
	BYTE processname[16];		// 进程名

}MyStruct,*PMyStruct;

新建一个LIST_ENTRY变量:

// 链表变量
LIST_ENTRY listhead = { 0 };

在使用的时候初始化这个链表变量 ,初始化的时候就是在填充结构中的值(和对象初始化好像😗):

InitializeListHead(&listhead);	// 初始化是填充节点的指针值
DbgPrint("%p %p %p\n", &listhead, listhead.Flink, listhead.Blink);

运行看一下:

 是一样的地址。

2.获取进程

创建一个进程通知回调:

//设置进程创建回调
PsSetCreateProcessNotifyRoutine(ProcessNotifyFun,FALSE);

PsSetCreateProcessNotifyRoutine 例程将驱动程序提供的回调例程添加到或从中删除该例程列表,每当创建或删除进程时调用该例程。

这个API在前边的程序中也有使用过,在这里发现了一个查看回调参数的方法,算是一个新收获:去查看具体的结构。

通过这个结构,新创建的回调函数参数就是这样定义:

这样就连成线了✅

 为了获取进程名需要声明一个半文档化(也就是不公开但是存在)函数【PsGetProcessImageFileName】,需要开头声明一下才能使用:

/** 向前声明 */
NTKERNELAPI
UCHAR* PsGetProcessImageFileName(__in PEPROCESS Process);

 回调函数具体的当前样子是:

//08 链表
VOID ProcessNotifyFun(HANDLE pid, HANDLE pid2, BOOLEAN bcareaf)
{
	UNREFERENCED_PARAMETER(pid);
	if (bcareaf)
	{
		DbgPrint("process create,PID is %d", pid2);

		//PEPROCESS tempep = PsGetCurrentProcess();	//这是获取自身进程ID 
		PEPROCESS tempep = NULL;
		PsLookupProcessByProcessId(pid2, &tempep);
		if (!tempep)
		{
			return;
		}

		PUCHAR	processname = PsGetProcessImageFileName(tempep);
		DbgPrint("process name  is %s", processname);

	}
	return;
}

 这里有个小插曲,如果使用PsGetCurrentProcess获取只能是自身的进程就是那个“浏览.exe”,换成PsLookupProcessByProcessId这个函数就可以通过pid进行搜索了。

PsLookupProcessByProcessId 例程接受进程的进程 ID,并返回指向进程的 EPROCESS 结构的引用指针。

3. 存储信息

如果CPU是多核的话,一个核在创建进程,另一个核也在创建进程,那么通过链表保存的时候,就会有冲突,需要通过上锁来进行保护。

上边获取到进程名之后,在回调函数中继续插入到链表中:

//08 链表
VOID ProcessNotifyFun(HANDLE pid, HANDLE pid2, BOOLEAN bcareaf)
{
	UNREFERENCED_PARAMETER(pid);
	if (bcareaf)
	{
		DbgPrint("process create,PID is %d", pid2);

		//PEPROCESS tempep = PsGetCurrentProcess();	//这是获取自身进程ID 
		PEPROCESS tempep = NULL;
		PsLookupProcessByProcessId(pid2, &tempep);
		if (!tempep)
		{
			return;
		}

		ObDereferenceObject(tempep);	// 解引用????

		PUCHAR	processname = PsGetProcessImageFileName(tempep);
		DbgPrint("process name  is %s", processname);

		//
		//保存进程信息
		//
		PMyStruct ptempptr = ExAllocatePoolWithTag(NonPagedPool, sizeof(MyStruct), 'qwer');
		if (ptempptr)
		{
			KIRQL oldirql = 0;
			//PLIST_ENTRY templist = NULL;

			RtlZeroMemory(ptempptr, sizeof(MyStruct));

			//
			//成员赋值
			//
			ptempptr->peprocesspbj = tempep;
			ptempptr->pid = pid2;
			RtlCopyMemory(ptempptr->processname, processname, strlen((const char *)processname));

			//通过CONTAINING_RECORD查找链表
			//templist = CONTAINING_RECORD(ptempptr, MyStruct, list);

			//上锁
			KeAcquireSpinLock(&spinlock, &oldirql);

			//插入信息
			InsertTailList(&listhead, &(ptempptr->list));

			//解锁
			KeReleaseSpinLock(&spinlock,oldirql);
		}

	}
	return;
}

 不太明白为什么要解引用

ObDereferenceObject(tempep);	// 解引用????

问一下AI:

ObReferenceObject 是 Windows 内核提供的一个宏,用于增加对象的引用计数。在 Windows 内核编程中,对象是内核中的一种基本数据结构,可能是进程、线程、文件、事件等等。由于内核对象被多个线程和进程共享,所以需要使用引用计数来管理对象的生命周期。

当一个线程或进程需要访问某个对象时,会调用 ObReferenceObject 宏来增加对象的引用计数。这样可以确保在对象正在被使用时,其他线程或进程不会将其释放掉。当对象不再被使用时,调用 ObDereferenceObject 来减少对象的引用计数,如果引用计数为 0,则表示对象可以被释放掉。

总之,ObReferenceObject 宏的主要作用是增加内核对象的引用计数,避免对象在使用期间被意外删除。

那什么是【引用计数】:

引用计数是一种常见的内存管理技术,它被广泛应用于操作系统、程序库和其他软件系统中。引用计数是一种跟踪对象在程序中被使用次数的方法,当一个对象被创建时,其引用计数初始化为 1。每当有代码引用该对象时,其引用计数加 1;当不再需要该对象时,其引用计数减 1。当引用计数变为 0 时,该对象就可以被释放,以便回收其占用的内存。

引用计数是一种内存管理技术,它的基本原理是跟踪每个对象被引用的次数。当对象的引用计数变为零时,系统就会自动回收该对象所占用的内存空间。

在使用引用计数的语言中,每个对象都有一个引用计数器,用于记录有多少个指针引用了该对象。当有新的指针指向该对象时,该对象的引用计数将会增加1,当指针不再指向该对象时,该对象的引用计数将会减少1。当引用计数器变为0时,表示该对象已经没有被任何指针引用了,此时系统将会回收该对象所占用的内存空间。这种方式可以避免内存泄漏和重复释放内存等问题,同时也可以提高程序运行效率。

ObReferenceObject 宏标记的对象需要再删除引用吗?

是的,使用 ObReferenceObject 宏标记的对象,在使用完成后需要调用ObDereferenceObject 函数进行引用计数的减少。

ObReferenceObject 宏用于增加内核对象的引用计数。当内核对象被打开或者传递给驱动程序处理时,必须先使用 ObReferenceObject 宏增加它的引用计数,这样可以确保该对象在使用期间不会被释放掉。

ObDereferenceObject 函数用于减少内核对象的引用计数。当一个内核对象不再被使用时,必须调用此函数将其引用计数减少。当引用计数为0时,内核对象的内存空间才会被释放。

需要注意的是,在使用 ObReferenceObject 宏和 ObDereferenceObject 函数时,需要遵循一定的规则,例如不要在调用 ObDereferenceObject 函数之前修改被引用对象的句柄等。否则可能会导致未定义的行为或者系统崩溃。

 原来这里调用的是用解引用,那应该是之前的函数有什么地方增加引用了,我又去MSDN搜了一下,发现是有解引用说明的:

如果对 PsLookupProcessByProcessId 的调用成功, PsLookupProcessByProcessID 会增加 Process 参数中返回对象的引用计数。 因此,当驱动程序使用 Process 参数完成时,驱动程序必须调用 ObDereferenceObject取消引用从 PsLookupProcessByProcessID 例程收到的 Process 参数。

看来是谁家的工具就得找谁家的说明书啊,解决完这个疑问之后贴下代码再看下效果:

//卸载函数
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
    ······

	//记得卸载
	PsSetCreateProcessNotifyRoutine(ProcessNotifyFun, TRUE);


	//
	// 只遍历不删除
	//	
	for (templist = listhead.Flink; templist!=&listhead; templist=templist->Flink)
	{
		DbgPrint("FOR");

		tempptr = CONTAINING_RECORD(templist, MyStruct, list);
		DbgPrint(" %d %p %s\n", tempptr->pid, tempptr->peprocesspbj, tempptr->processname);
	}

	//
	//	遍历链表并删除
	//
	PLIST_ENTRY templist = NULL;
	PMyStruct tempptr = NULL;
	while (listhead.Blink!=&listhead)
	{
		DbgPrint("WHILE");

		templist = RemoveTailList(&listhead);
		tempptr = CONTAINING_RECORD(templist, MyStruct, list);
		DbgPrint(" %d %p %s\n", tempptr->pid, tempptr->peprocesspbj, tempptr->processname);
		//DbgPrint("进程名 %s \n",  tempptr->processname);

		ExFreePoolWithTag(tempptr, 'qwer');
	}


}

三、小结 

 视频实现的是尾插法,还有其他的插入链表的方法。然后就是可以尝试和应用层联动,来开启数据存储和数据发送等。

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

APP外包项目的线上维护方案

APP的使用已经非常普及,不论是2C还是2B的APP都已经渗透到了我们生活的方方面面,对于APP的开发公司来说APP项目的线上维护是一个非常重要的问题。如果APP项目比较重要而且用户规模比较大,那更需要专业的技术团队来维护。今天和大家分享这方面的…

计算机网络-SNMP协议与pysnmp

1.概念 2.典型架构 3.snmp的信息交互 4.MIB 4.1常见MIB节点 5.SNMP管理模型 MIB位于被管理进程 6.SNMP的三个版本 6.1 SNMPv1 6.2 SNMPv2C 6.3 SNMPv3 6.3.1 SNMP3的基本操作 6.3.2 SNMP交互GET 6.3.3 SNMP交互-GETBULK 6.3.4 SNMP交互-SET 6.3.5 SNMP交互-trap 6.3.6 SNMP交…

【技术干货】PCB焊盘设计之问题详解

SMT的组装质量与PCB焊盘设计有直接的关系,焊盘的大小比例十分重要。如果PCB焊盘设计正确,贴装时少量的歪斜可以再次回流焊纠正(称为自定位或自校正效应),相反,如果PCB焊盘设计不正确,即使贴装位置十分准确,…

【 图像水印 2019 CVPR】 StegaStamp 论文翻译

【 图像水印 2019 CVPR】 StegaStamp 论文翻译 论文题目:StegaStamp: Invisible Hyperlinks in Physical Photographs 中文题目:物理照片中不可见的超链接 论文链接:https://arxiv.org/abs/1904.05343 论文代码:https://github.co…

Linux内核架构和工作原理

**前言:**作用是将应用层序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址。目前支持模块的动态装卸(裁剪)。Linux内核就是基于这个策略实现的。Linux进程1.采用层次结构,每个进程都依赖于一个父进程。内…

django基础知识详解

1. 安装与介绍 课程特点: 学习难度大,大部分内容需要理解并记忆文件较多易混淆学习阶段注重框架使用,工作阶段注重实现业务逻辑综合应用强,小练习少 1.1 Django框架的介绍 2005年发布,采用Python语言编写的开源web框架早期的时…

分享105个NET源码ASP源码,总有一款适合您

分享105个NET源码,总有一款适合您 源码下载链接:https://pan.baidu.com/s/1zFMIHX6juXdR2CaHMEr5mQ?pwdf5hz 提取码:f5hz 下面是文件的名字,我放了一些图片,文章里不是所有的图主要是放不下...,大家下载后…

每天一个提高效率的Matlab编程小技巧(1)-dbstop if error

相信在matlab调试程序的时候都遇到过这种情况:运行程序时命令行报错,而且出错的位置在我们自己定义的函数里,比如下面这个例子: 主函数main.m: a[1 2 3]; b[4 5]; csum_squares(a,b); 子函数sum_squares.m function csum_squa…

实验一 Python基础编程

实验一 Python基础编程 只为给原因学习编程的同学提供一个思路,让编程更简单!!! 本博主擅长整理粉丝的私信!只要你有需求就可以告诉博主!博主可以帮你解决并发表! 一、实验学时 2学时 二、实…

infuluxdb时序数据库介绍

时序数据库(influxdb) InfluxDB是一个开源的、高性能的时序型数据库,在时序型数据库DB-Engines Ranking上排名第一。 下载地址:https://dl.influxdata.com/influxdb/releases/influxdb2-2.3.0-windows-amd64.zip 启动: CMD到解压…

IDEA编译JDK1.8源码及运行测试

———————————————— 版权声明:本文为CSDN博主「神韵499」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_41055045/article/details/112002440 ————…

缓存穿透、缓存雪崩和缓存击穿

1 缓存穿透 缓存穿透是指查询一个一定不存在的数据,由于缓存中没有,每次查询都要去数据库中查询,导致频繁地访问数据库,从而影响系统的性能。攻击者可以利用这一点,对系统进行拒绝服务攻击。 1.1 缓存穿透举例 攻击者…

Qt Plugin插件开发

一、Qt 插件机制 .1 Qt 插件简介 插件是一种遵循一定规范的应用程序接口编写出来的程序,定位于开发实现应用软件平台不具备的功能的程序。插件与宿主程序之间通过接口联系,就像硬件插卡一样,可以被随时删除,插入和修改&#xff…

docker学习笔记(二)

目录 启动Docker ​编辑 建立 Docker 用户 ​编辑 测试 Docker 是否正常工作 卸载Docker Docker镜像加速器配置 配置镜像 检查加速器是否生效 如何在Linux中的.json文件下保存并退出 如果我是使用vi操作进来的,我该如何保存并退出呢? 如何在Li…

基于粒子群优化算法的最佳方式优化无线传感器节点的位置(Matlab代码实现)

目录 💥1 概述 📚2 运行结果 🎉3 参考文献 👨‍💻4 Matlab代码 💥1 概述 此代码优化了由于电池耗尽而产生覆盖空洞后 WSN 节点的位置。如果活动通信中的任何节点死亡,则通过PSO优化再次定位…

Java Socket和ServerSocket 使用

在Java中,Socket和ServerSocket是用于创建网络连接的重要类。Socket类用于创建客户端套接字,而ServerSocket类用于创建服务器套接字。在本文中,我们将讨论Socket和ServerSocket的作用、使用方法以及相关代码示例。 Socket的作用 Socket是Jav…

【2023 · CANN训练营第一季】应用开发深入讲解——第四章 DVPP初级

学习资源 1.JPEG图片解码 文档 2.图片缩放 文档 媒体数据处理 数据预处理的典型使用场景 数据预处理的多种方式 分为两种,AIPP和DVPP DVPP数据预处理功能 了解两个重要概念 宽stride 和 高stride 理解:这里假设一张图片的宽为500,高为3…

匹配算法之 匈牙利算法详解

参考: 算法学习笔记(5):匈牙利算法漫谈匈牙利算法匈牙利算法、KM算法匈牙利算法(二分图)通俗易懂小白入门)二分图最大匹配——匈牙利算法多目标跟踪之数据关联(匈牙利匹配算法和KM算法)【小白学…

187页9万字企业大数据治理与云平台实施方案(word)

1 项目背景概述 1.1 项目背景理解 1.2 项目需求范围 2 项目技术方案 2.1 咨询研究服务方案 2.1.1 咨询研究服务内容 2.1.2 咨询服务方案 2.2 第三方独立评估 2.2.1 概述 2.2.2 管理办法 2.2.3 考核机制 2.3 安全咨询研究服务方案 2.3.1 安全咨询服务内…

【k8s】【ELK】日志环境部署【待写】

1、日志收集基本概念 k8s中pod的路径: containers log: /var/log/containers/*.log Pod log: /var/log/pods docker log: /var/lib/docker/containers/*/*.log如何收集日志 使用 EFKLogstashKafka 1、filebeat读取容器中的日志,然后写入K…