零环与三环通信

CPU的分级

我们都知道,CPU有环的概念。我们的所有可视化界面与任何的操作都在用户层进行,也就是口中的"三环"。在我们看不见的地方,比如驱动、硬件数据交互等操作,都在内核层执行,也就是"零环"。环的层数越低,权限相对越高。至于一环与二环,并未使用。
在这里插入图片描述

零环与三环的数据交互

与内核层交互,无非也就是收发数据。但我们要提供的缓冲区是用户态地址,所以不能在内核态随意使用。故而提供了三种设备对象的读写方式。
分别为缓冲区设备读写直接读写非前两者方式
以下对三种方式做简单介绍(下图为自己画的,有问题或有错误欢迎指出)

缓冲区读写

将用户态缓冲区拷贝到内核态,在内核态使用完毕,再拷贝回用户态。
不追求效率且图简单的情况下可以使用该方式。
在这里插入图片描述

直接读写方式

对用户态地址进行重新映射,映射到内核空间中。
这也是最安全又快速的一种方式,在我们使用时,尽量使用直接方式
而我们要演示的也是该种方式
在这里插入图片描述

第三种,即两者都不方式

这种方式,零环得到的缓冲区的地址是用户层的地址,这样是极度不安全的。一旦发生了进程切换,地址将发生错误。
在这里插入图片描述

以下演示直接方式

零环(内核层)遍历驱动,将遍历到的所有数据上传到三环程序上显示。

驱动代码

#include <ntddk.h>

VOID DriverUnload(PDRIVER_OBJECT pDriver);

//定义控制码
#define IOCTL_GET_DRIVERINFO  CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_IN_DIRECT ,FILE_ANY_ACCESS)//获取驱动信息


//LDR_DATA_TABLE_ENTRY结构体
typedef struct _LDR_DATA_TABLE_ENTRY {

	LIST_ENTRY InLoadOrderLinks;	//双向链表
	LIST_ENTRY InMemoryOrderLinks;
	LIST_ENTRY InInitializationOrderLinks;
	PVOID DllBase;
	PVOID EntryPoint;
	ULONG SizeOfImage;
	UNICODE_STRING FullDllName;
	UNICODE_STRING BaseDllName;
	ULONG Flags;
	USHORT LoadCount;
	USHORT TlsIndex;
	union U1 {
		LIST_ENTRY HashLinks;
		struct S1 {
			PVOID SectionPointer;
			ULONG CheckSum;
		}s1;
	}u1;
	union U2 {
		struct S2 {
			ULONG TimeDateStamp;
		}s2;
		struct S3 {
			PVOID LoadedImports;
		}s3;
	}u2;
}LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;


//获取驱动信息
void GetDriveINFO(PDEVICE_OBJECT pDeviceObject, PCHAR pOutBuf)
{
	//获取所有驱动信息
	ULONG dwInfo = 0;
	PDRIVER_OBJECT pDriver = pDeviceObject->DriverObject;
	PLDR_DATA_TABLE_ENTRY pLdr = (PLDR_DATA_TABLE_ENTRY)pDriver->DriverSection;
	LIST_ENTRY* pTemp = &pLdr->InLoadOrderLinks; //将第一个驱动暂存,因为驱动信息是一个链表
	PCHAR pTempBuf = pOutBuf;
	ULONG n = 0;
	do {
		PLDR_DATA_TABLE_ENTRY pDriverInfo = (PLDR_DATA_TABLE_ENTRY)pTemp;
		//构造结构体
		/*
		pOutBuf 结构体大小
		pOutBuf + 4 驱动名字
		*/

		ULONG Size = pDriverInfo->BaseDllName.Length + 4 + 2; //结构体大小 = 驱动名称长度 + 4(保存缓冲区大小的四个字节) + 2(空出两个字节)
		RtlCopyMemory(pOutBuf,&Size,4); //将大小拷贝到缓冲区
		RtlCopyMemory(pOutBuf + 4,pDriverInfo->BaseDllName.Buffer,pDriverInfo->BaseDllName.Length);//将驱动名字拷贝到缓冲区+4的位置
		pOutBuf += Size; //缓冲区 + 结构体大小 = 指向下一个结构体

		pTemp = pTemp->Blink;//指向下一个
	} while (pTemp != &pLdr->InLoadOrderLinks); 
}


//IRP默认处理方式,即什么都不做
NTSTATUS DefaultIRPHandle(PDEVICE_OBJECT pDeviceObject, PIRP pIrp) {
	UNREFERENCED_PARAMETER(pDeviceObject);
	//1.设置IRP完成状态
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	//2.设置IRP操作了多少字节
	pIrp->IoStatus.Information = 0;
	//3.处理IRP
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}


//IRP处理方式,通过控制码匹配处理函数
NTSTATUS CtrlCodeHandle(PDEVICE_OBJECT pDeviceObject, PIRP pIrp) {
	UNREFERENCED_PARAMETER(pIrp);
	//设置IRP完成状态
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	//获取IRP栈
	PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
	//获取控制码
	ULONG dwCtrlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
	//获取传入缓存区和大小
	PCHAR pInBuf = pIrp->AssociatedIrp.SystemBuffer;
	ULONG dwInLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
	//获取传出缓冲区和大小
	PCHAR pOutBuf = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
	ULONG dwOutLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
	//记录IRP操作了多少字节
	ULONG dwInfo = 0;
	switch (dwCtrlCode)
	{
	case IOCTL_GET_DRIVERINFO:
		GetDriveINFO(pDeviceObject, pOutBuf);//调用获取驱动信息函数
		break;
	}
	//1.设置IRP完成状态
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	//2.设置IRP操作了多少字节
	pIrp->IoStatus.Information = dwInfo;
	//3.处理IRP
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}


//入口函数
NTSTATUS DriverEntry(
	PDRIVER_OBJECT pDriver,
	PUNICODE_STRING pPath)
{
	//_asm int 3;
	UNREFERENCED_PARAMETER(pPath);

	//定义设备名
	UNICODE_STRING strDeviceName = { 0 };    //仅供驱动对象使用
	UNICODE_STRING strSymbolLinkName = { 0 };//符号连接名
	PDEVICE_OBJECT pDevice_object = NULL;

	//设备名必须在Device路径下
	RtlInitUnicodeString(&strDeviceName, L"\\Device\\MyDevice");
	//符号名必须在DosDevice路径下
	RtlInitUnicodeString(&strSymbolLinkName, L"\\DosDevices\\MyDriverSymbol");
	//创建设备对象(创建后需要释放)
	NTSTATUS nStatus = IoCreateDevice(
		pDriver,        //驱动对象
		0, 					  //设备扩展对象大小
		&strDeviceName, 	  //设备名称
		FILE_DEVICE_UNKNOWN,  //设备类型(所有类型)
		0, 					  //设备特征信息
		FALSE, 				  //设备是否为独占的
		&pDevice_object);	  //创建完成的设备对象指针
	if (NT_SUCCESS(nStatus) == FALSE) {
		KdPrint(("CreateDevice Error"));
		return nStatus;
	}
	//设置通讯方式
	pDevice_object->Flags |= DO_DIRECT_IO;//直接读写方式,MDL方式
	//绑定符号连接
	nStatus = IoCreateSymbolicLink(&strSymbolLinkName, &strDeviceName);
	if (NT_SUCCESS(nStatus) == FALSE) {
		KdPrint(("CreateSymbolicLink Error"));
		return nStatus;
	}
	//设置驱动对象IRP派遣函数
	for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) {
		pDriver->MajorFunction[i] = DefaultIRPHandle;  // 设置一个默认派遣函数,该函数可以什么都不做
	}

	//控制码
	pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = CtrlCodeHandle;
	DbgPrint("驱动加载成功\n");
	//驱动卸载回调函数
	pDriver->DriverUnload = DriverUnload;

	return STATUS_SUCCESS;
}


//驱动卸载函数
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
	UNREFERENCED_PARAMETER(pDriver);
	//一个驱动对象可以创建很多设备对象
	//创建的所有设备对象形成一个链表,链表的头节点在驱动对象的DeviceObject字段上
	PDEVICE_OBJECT objDevice = pDriver->DeviceObject;
	PDEVICE_OBJECT NextDevice = NULL;
	// 删除符号链接
	UNICODE_STRING DosSymName;
	RtlInitUnicodeString(&DosSymName, L"\\DosDevices\\MyDriverSymbol");
	IoDeleteSymbolicLink(&DosSymName);
	while (objDevice != NULL) {
		//设备对象中存在NextDevice字段指向同驱动对象的下一个设备对象
		NextDevice = objDevice->NextDevice;
		IoDeleteDevice(objDevice);
		objDevice = NextDevice;
	}
	DbgPrint("驱动成功卸载\n");
}

内存情况
在这里插入图片描述

用户层代码

#include <stdio.h>
#include <Windows.h>

//定义控制码
#define IOCTL_GET_DRIVERINFO  CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_IN_DIRECT ,FILE_ANY_ACCESS)//获取驱动信息

int main() 
{
	//1.打开设备(CreateFile第一个参数传入设备路径打开的就是设备)
	HANDLE hDevice = CreateFile(
		L"\\\\.\\MyDriverSymbol",    //设备符号连接名
		GENERIC_ALL, NULL, NULL,
		OPEN_EXISTING,          //打开模式
		FILE_ATTRIBUTE_NORMAL,
		NULL
	);
	if (hDevice == INVALID_HANDLE_VALUE) {
		MessageBox(NULL,L"设备打开失败", L"Test",NULL);
		return 0;
	}
	char Inputbuf[100] = "Input_CTRL";//发送到内核层的信息
	PCHAR DirverINFO = new CHAR[20000]{};//接收零环传进的信息缓冲区 ps:不一定非得20000,随意多少,只要够用
	DWORD dwRealSize = 0;

	DeviceIoControl(hDevice, IOCTL_GET_DRIVERINFO, Inputbuf, 500, DirverINFO, 20000, &dwRealSize, NULL);  //向内核层发送控制码,将返回的信息存储到DirverINFO中

	/*
	* 结构体需要与内核层定义的偏移相同
	pOutBuf 结构体大小
	pOutBuf + 4 驱动名字
	*/
	system("pause");
	while (true)//循环遍历内核层传过来的信息
	{	
		DWORD szSize = *PDWORD(DirverINFO);//结构体块的大小
		CHAR* szDriverName = (DirverINFO + 0x4);//驱动名字

		if (szSize == 0)  //循环结束的条件,块大小等于0时结束循环
		{
			break;
		}
		printf("%S \n",szDriverName);
		DirverINFO += szSize;  //得到下一个驱动信息的位置
	}
	system("pause");
	return 0;
}

运行效果如下

驱动安装

在这里插入图片描述

三环程序运行

在这里插入图片描述

结尾

到这我们就完美实现了零环与三环之间的数据交互。
看懂该篇文章你可能需要有C语言基础、驱动开发基础。以及内核相关知识
(有问题欢迎提出)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值