应用层到内核数据的输入与输出,常规的通讯(派遣函数)

功能:应用层与内核可以通讯,交互数据,这里演示的是常规IO输入输出的通讯。

学习自B站 UP主:写驱动的女装大佬《中级篇第三课》

设备扩展

* IoCreateDevice创建设备 
    参数1:driver 设备对象;
	参数20 设备扩展的大小;
	参数3&deviceanme 设备名字;
	参数4FILE_DEVICE_UNKNOWN(未知设备) 设备类型
	参数50  默认参数
	参数6:TRUE  默认参数
	参数7&pdevice 输出参数,设备创建成功后,会把设备对象传出来
*/
// 参数2就是扩展设备大小,就是一段内存空间,设置好后windows会分配给这个内存空间,例子中设置200是200个字节
status = IoCreateDevice(driver,200, &deviceanme, FILE_DEVICE_UNKNOWN,0,TRUE,&pdevice);

驱动层代码


/**********************驱动程序学习例程************************
*  平台:visual studio 2019 内核程序
*  功能:前台应用CMD程序与驱动设备io常规通讯,通过读取,写入,自定义控制实现
*  编写:逆行者
*  日期:2021-01-16
*  更改记录:未更改
******************************************************************/

#include <ntddk.h>
#include <windef.h>

#define DEVICE_NAME L"\\Device\\MyfirstDevice" //定义一个驱动的名字
#define SYM_NAME    L"\\??\\MyfirstDevice"//定义一个符号链接

//CTL_CODE:通过这个宏运算后得到一个特殊的数字
//第一个参数是设备类型,就是创建设备里面填的参数
//第二个参数是通讯码,0x800是微软自己用的,0x9888是自己定义的一个值
//第三个参数是读写方式
#define IOCTL_MUL  CTL_CODE(FILE_DEVICE_UNKNOWN,0x9888,METHOD_BUFFERED,FILE_ANY_ACCESS)


VOID nothing(HANDLE ppid, HANDLE mypid, BOOLEAN bcreate)
{
    
    
	//回调函数,有程序打开或关闭显示提示信息
	DbgPrint("ProcessNotify\n");

}

/*
MyCreate自己定义的驱动设备的派遣函数
pdevice:设备对象

*/
NTSTATUS MyCreate(PDEVICE_OBJECT pdevice, PIRP pirp)
{
    
    
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device has be opende\n");

	pirp->IoStatus.Status = status; //请求成功
	pirp->IoStatus.Information = 0;//信息处理了0字节
	IoCompleteRequest(pirp,IO_NO_INCREMENT);//完成请求
	return STATUS_SUCCESS;

}

/*
MyClose关闭函数

*/
NTSTATUS MyClose(PDEVICE_OBJECT pdevice, PIRP pirp)
{
    
    
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device has be closed\n");

	pirp->IoStatus.Status = status; //请求成功
	pirp->IoStatus.Information = 0;//信息处理了0字节
	IoCompleteRequest(pirp, IO_NO_INCREMENT);//完成请求
	return STATUS_SUCCESS;

}


/*
MyCleanUp清理函数

*/
NTSTATUS MyCleanUp(PDEVICE_OBJECT pdevice, PIRP pirp)
{
    
    
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device has be CleanUped\n");

	pirp->IoStatus.Status = status; //请求成功
	pirp->IoStatus.Information = 0;//信息处理了0字节
	IoCompleteRequest(pirp, IO_NO_INCREMENT);//完成请求
	return STATUS_SUCCESS;

}


/*
功能:MyRead读取函数,当收到应用层读取请求的时候,把内核的数据传给应用层

*/
NTSTATUS MyRead(PDEVICE_OBJECT pdevice, PIRP pirp)
{
    
    
	NTSTATUS status = STATUS_SUCCESS;
	
	DbgPrint("My Device has be Readed\n");

	//IoGetCurrentIrpStackLocation 获取应用层传过来的IRP请求
	PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);

	// readsize 就是应用层ReaFile函数的 参数3 的读取大小的字节数,本例中是:50
	ULONG readsize = pstack->Parameters.Read.Length;
	
	// 要读取的缓冲区,对应的是应用层ReaFile函数的参数2 数组所在的内存,本例中是:readbuffer
	// 内核与应用层的这片缓冲区readbuffer,虚拟地址不一样,但是指向的是同一块物理内存
	// 在内核修改readbuffer缓冲数据后,应用层的数据也相应改变
	PCHAR readbuffer = pirp->AssociatedIrp.SystemBuffer;

	/*RtlCopyMemory复制一段内容到缓冲区
	* 参数1:readbuffer 缓冲区,实际就是一个数组,也就是一片内存区域
	* 参数2:复制进入缓冲区的内容
	* 参数3:复制的长度
	*/
	RtlCopyMemory(readbuffer, "This Message Come From Kernel.", strlen("This Message Come From Kernel."));

	pirp->IoStatus.Status = status; //请求成功
	
	//成员变量.Information 对应的应用层ReadFile函数的 第4个参数 实际读取的大小 本例中是:&bread
	//返回后.Information 值会放入应用层的 &bread
	pirp->IoStatus.Information = strlen("This Message Come From Kernel.");//这里是返回到应用层的数据,实际操作的缓冲区大小

	DbgPrint("Really Read Info Len is %d", strlen("This Message Come From Kernel."));
	
	IoCompleteRequest(pirp, IO_NO_INCREMENT);//完成请求
	
	return STATUS_SUCCESS;

}


/*
MyWrite写函数 应用层通过IRP请求,写入一些数据到内核

*/
NTSTATUS MyWrite(PDEVICE_OBJECT pdevice, PIRP pirp)
{
    
    
	NTSTATUS status = STATUS_SUCCESS;

	DbgPrint("My Device has be Writeed\n");

	//IoGetCurrentIrpStackLocation 获取应用层传过来的IRP请求
	PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);

	// readsize 就是应用层ReaFile函数的 参数3 的写入大小的字节数,本例中是:50
	ULONG writesize = pstack->Parameters.Write.Length;

	// .SystemBuffer是应用层传过来的内容,对应的是应用层WriteFile函数的参数2 里面的内容,本例中是:"This Message come from R3."
	// 内核与应用层都指向这个缓冲去区,虚拟地址不一样,但是指向的是同一块物理内存
	// 所以在内核修改Writebuffer缓冲数据后,应用层的数据也相应改变
	PCHAR writebuffer = pirp->AssociatedIrp.SystemBuffer;
	
	//内存清零操作,先把设备扩展清零,待会要写入数据
	RtlZeroMemory(pdevice->DeviceExtension,200);
	//把应用层传过来的数据复制到设备扩展里面
	RtlCopyMemory(pdevice->DeviceExtension, writebuffer, writesize);
	//打印出内核写入缓冲区地址,和设备扩展里面的内容
	DbgPrint("--%p--%s\n", writebuffer, (PCHAR)pdevice->DeviceExtension);

	pirp->IoStatus.Status = status; //请求成功
	pirp->IoStatus.Information = 13;//这里是返回到应用层的数据,这里执行后数据会返回给应用层的函数WriteFile 第四个参数&bread接受
	IoCompleteRequest(pirp, IO_NO_INCREMENT);//完成请求
	return STATUS_SUCCESS;

}




/*
MyControl 自定义控制 函数

*/
NTSTATUS MyControl(PDEVICE_OBJECT pdevice, PIRP pirp)
{
    
    
	NTSTATUS status = STATUS_SUCCESS;
	DbgPrint("My Device has be Controled\n");

	
	PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);

	//获取控制码功,就是从应用层传过来的一窜数字,用这个数字来判断需要执行下面的哪个内核功能
	ULONG iocode = pstack->Parameters.DeviceIoControl.IoControlCode;

	//输入的长度
	ULONG inlen = pstack->Parameters.DeviceIoControl.InputBufferLength;

	//输出的长度
	ULONG outlen = pstack->Parameters.DeviceIoControl.OutputBufferLength;

	//操作信息

	ULONG ioinfo = 0;

	DbgPrint("--kernel ControlCode %d--\n", iocode);
	switch (iocode)
	{
    
    
	
	case IOCTL_MUL:
	{
    
    
		//自定义的乘法
		
		//取出数据
		DWORD indata = *(PDWORD)pirp->AssociatedIrp.SystemBuffer;

		DbgPrint("--Kernel Indata %d--\n",indata);

		indata = indata * 5;
		
		*(PDWORD)pirp->AssociatedIrp.SystemBuffer = indata;

		ioinfo = 666;
		break;
		
		
		
	}

	default:
		//如果不是我们要的操作符合,就判断为失败
		status = STATUS_UNSUCCESSFUL;
		ioinfo = 0;
		break;
	}

	pirp->IoStatus.Status = status; //请求成功
	pirp->IoStatus.Information = ioinfo;//信息处理了0字节
	IoCompleteRequest(pirp, IO_NO_INCREMENT);//完成请求
	return STATUS_SUCCESS;

}


VOID DrvUnload(PDRIVER_OBJECT pdriver)
{
    
    
	DbgPrint("Unload\n");
	
	if (pdriver->DeviceObject)
	{
    
    
		//删除派遣函数
		IoDeleteDevice(pdriver->DeviceObject);
		
		UNICODE_STRING symname = {
    
     0 };
		RtlInitUnicodeString(&symname, SYM_NAME);
		//删除符号连接
		IoDeleteSymbolicLink(&symname);

	}


	
}



NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
    
    
	NTSTATUS status = STATUS_SUCCESS; //定义一个返回值
	UNICODE_STRING deviceanme = {
    
     0 };//定义设备的名字
	PDEVICE_OBJECT pdevice = NULL; //设备创建后的输出参数,如果不为NULL就表示设备创建成功了
	driver->DriverUnload=DrvUnload;
	RtlInitUnicodeString(&deviceanme, DEVICE_NAME);//初始化一个字符串
	/*
	* IoCreateDevice创建设备 
	    参数1:driver 设备对象;
		参数2:0 设备扩展的大小;
		参数3:&deviceanme 设备名字;
		参数4:FILE_DEVICE_UNKNOWN(未知设备) 设备类型
		参数5:0  默认参数
		参数6:TRUE  默认参数
		参数7:&pdevice 输出参数,设备创建成功后,会把设备对象传出来
	*/
	status = IoCreateDevice(driver,200, &deviceanme, FILE_DEVICE_UNKNOWN,0,TRUE,&pdevice);

	if (!NT_SUCCESS(status))
	{
    
    
		//如果设备创建设备就打印下面的信息
		DbgPrint("Create Device Failed:%x\n",status);
		//返回错误信息
		return status;
	}

	//设备的读写方式(必须设置,否则蓝屏)
	pdevice->Flags |= DO_BUFFERED_IO;

	
	//到这里 设备就创建成功了
	
	
	UNICODE_STRING symname = {
    
     0 };//定义一个符号连接名称
	RtlInitUnicodeString(&symname, SYM_NAME);//初始化一个字符串
	/*IoCreateSymbolicLink创建符号连接
	
	  参数1:&symname 符号连接名字
	  参数2:&deviceanme 设备名字
	
	*/
	status = IoCreateSymbolicLink(&symname, &deviceanme);
	if (!NT_SUCCESS(status))
	{
    
    
		//如果创建符号连接失败就打印下面的信息
		DbgPrint("Create SymbolicLink Failed:%x\n", status);
		//既然创建符号连接失败,就要删除这个设备
		IoDeleteDevice(pdevice);
		//返回错误信息
		return status;
	}

	//到这里 符号连接就创建成功了

	//驱动的功能都写在下面,派遣函数

	//打开设备
	driver->MajorFunction[IRP_MJ_CREATE] = MyCreate;
	
	//关闭设备
	driver->MajorFunction[IRP_MJ_CLOSE] = MyClose;
	
	//清理设备
	driver->MajorFunction[IRP_MJ_CLEANUP] = MyCleanUp;
	
	//读取,应用层把内核数据读取出去
	driver->MajorFunction[IRP_MJ_READ] = MyRead;
	
	//写入取,应用层把数据写入内核
	driver->MajorFunction[IRP_MJ_WRITE] = MyWrite;

	//自定义控制
	driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyControl;

	return 0;
}



应用层cmd代码

/**********************驱动程序学习例程************************
*  平台:visual studio 2019 CMD控制台程序
*  功能:前台应用CMD程序与驱动设备io常规通讯,通过读取,写入,自定义控制实现
*  编写:逆行者
*  日期:2021-01-16
*  更改记录:未更改
******************************************************************/


// MyDriverR3.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <Windows.h>
#include <stdlib.h>
#include <winioctl.h>//内核自定义控制派遣函数相关

//与驱动层相同的宏定义 ,这个宏将生成一个控制码,内核也可以用同样的代码生成一个同样的数字,
//这个数字将传入内核,被内核获取后就知道需要执行那个内核功能
#define IOCTL_MUL  CTL_CODE(FILE_DEVICE_UNKNOWN,0x9888,METHOD_BUFFERED,FILE_ANY_ACCESS)

int main()
{
    
    

    HANDLE hdevice = NULL; // 建立一个句柄
    CHAR readbuffer[50] = {
    
     0 };//定义一个50个字节的缓冲区,初始化为0
    DWORD bread = 0;//实际读取多少字节

    /*CreateFile打开一个驱动设备
    * 参数1:\\\\.\\MyfirstDevice 符号连接 
    *       对应驱动程序是这样写的 \\??\\MyfirstDevice
    * 参数2:GENERIC_READ|GENERIC_WRITE 读写方式
    * 参数3:0 是否读栈
    * 参数4:NULL 描述符
    
    */
    hdevice = CreateFile("\\\\.\\MyfirstDevice",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

    if (hdevice==INVALID_HANDLE_VALUE)
    {
    
    
        printf("Open Device Faile\n");
        system("pause");
        return 0;

    }
    //到这里驱动设备就打开成功
    printf("Open Sucess\n");
    system("pause");

    /*ReadFile 读取内核缓冲区的内容,里面的参数会传入内核
    * 参数1:hdevice 句柄
    * 参数2:(PVOID)readbuffer 要读取的缓冲区
    * 参数3: 50 读取大小单位字节
    * 参数4:返回值,是内核执行结束后返回应用层的值,&bread 实际读取多少字节,真实读取字节大小
    * 参数5:NULL 默认
    */
    ReadFile(hdevice,(PVOID)readbuffer,50,&bread,NULL);
    //%p 是指针打印
    printf("-%p-%s--%d--\n",readbuffer,readbuffer, bread);

    printf("Write!\n");

    system("pause");

    /*WriteFile 向内核缓冲区写入内容,里面的参数会传入内核
  * 参数1:hdevice 句柄
  * 参数2:写入数据的内容
  * 参数3: 写入数据的长度  字节大小
  * 参数4:返回值,是内核执行结束后返回应用层的值,&bread 实际写入多少字节,真实写入字节大小
  * 参数5:NULL 默认
  */
    WriteFile(hdevice,"This Message come from R3.",strlen("This Message come from R3."), &bread, NULL);
    printf("-write size--%d--\n",bread);

    printf("DeviceIo!ControlCode --%d\n", IOCTL_MUL);
    system("pause");

    DWORD a = 888, b = 0;
    //DeviceIoControl自定义控制
    //参数1:句柄
    //参数2:与驱动相同的宏
    //参数3:内核读取的数据的地址,这里一个内存址值,内核可通过pirp->AssociatedIrp.SystemBuffer 获取这个值
    //参数4:输出到内核数据的大小,单位字节
     //参数5:内核存入数据的地址,可在内核中处理a的数据,与内核中的把处理好的数据赋值给pirp->AssociatedIrp.SystemBuffer,应用层的&b就获得了内核处理过的值
    //参数6:字节数
    //参数7:实际操作的字节数,实际是内核通过pirp->IoStatus.Information 返回过来的一个值
    //参数8:NULL 默认
    DeviceIoControl(hdevice, IOCTL_MUL, &a,4,&b,4,&bread,NULL);

    printf("--in %d--out %d--really info %d\n", a,b,bread);


    CloseHandle(hdevice);
    system("pause");

    return 0;
   
}

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件

运行后效果图:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/lygl35/article/details/112691616