驱动通信2
06 通信封装
缓冲区模式
看这段代码
#define TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS )
其中的METHOD_BUFFERED
字符就是缓冲区模式
f12进去后可也发现有三种缓冲区模式
#define METHOD_BUFFERED 0 //缓冲
#define METHOD_IN_DIRECT 1 //映射
#define METHOD_OUT_DIRECT 2 //映射
#define METHOD_NEITHER 3 //直写
这里METHOD_BUFFERED
是一种,METHOD_IN_DIRECT
和METHOD_OUT_DIRECT
是一种包含了输入和输出,METHOD_NEITHER
是一种。
缓冲(METHOD_BUFFERED)
例如想要传4个字节的数据从R3到R0,那么通过METHOD_BUFFERED
方法,会在R0层中开辟一个指定大小的内存空间,然后将R3中的数据拷贝过去,本质上是个复制的过程
占用空间大且速度慢
假设需要R3向R0传一个非常大的数据,好几个G的大小,那么如果使用METHOD_BUFFERED
的方式就会出现问题,甚至出现蓝屏,所以出现了下面这种映射
的操作方法来满足要求
映射(METHOD_IN/OUT_DIRECT)
把R3地址的物理页解析出来,在R0中映射一份物理页,,共享同一份物理页
映射的时候容易导致缺页异常(例如隐藏驱动)
直写(METHOD_NEITHER)
这种方式比较直接,既不映射,也不复制,直接在R0操作R3的地址
设计思路
通信封装设计思路
- 提供一个注册通信方法,填回调函数
- 销毁的方法,把注册销毁
- 发送通知的方法
进程通知内核需要考虑的点
- 怎么确认是本进程发的消息而不是别的进程发的消息
- 怎么知道操作码是什么
函数指针
由于在编写的时候使用了函数指针,所以这里将函数指针补充一下
先看一下定义:
如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。
举个例子:
int(*p)(int, int);
这个语句就定义了一个指向函数的指针变量 p。
首先它是一个指针变量,所以要有一个“*”,即(*p);
其次前面的 int 表示这个指针变量可以指向返回值类型为 int 型的函数;
后面括号中的两个 int 表示这个指针变量可以指向有两个参数且都是 int 型的函数。
所以合起来这个语句的意思就是:定义了一个指针变量 p,该指针变量可以指向返回值类型为 int 型,且有两个整型参数的函数。p 的类型为 int(*)(int,int)。
所以函数指针的定义是:
函数返回值类型 (* 指针变量名) (函数参数列表);
实现
R3和R0已经共享了一个数据结构,所以不需要填返回的数据
代码结构
调用部分看main.c
和CommStruct.h
中定义的结构体就行
R0
Comm.c
#include "Comm.h"
//全局变量保存
//我们指定的回调函数
CommCallBack gCommCllback = NULL;
//定义默认的派发函数
NTSTATUS DefDispatch(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
//定义默认的派发函数
//走个形式而已
Irp->IoStatus.Status = STATUS_SUCCESS;//设置通信状态为成功
IoCompleteRequest(Irp, 0);//告诉客户端已经接收到通讯,返回0
return STATUS_SUCCESS;
}
//定义我们指定的派发函数
NTSTATUS WriteDispatch(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
DbgBreakPoint();
//拿到当前发送IRP包的栈
PIO_STACK_LOCATION ioStack = IoGetCurrentIrpStackLocation(Irp);
//初始化一个状态码
NTSTATUS status = STATUS_SUCCESS;
//偏移
LARGE_INTEGER ByteOffset = ioStack->Parameters.Write.ByteOffset;
//获取传进来的缓冲区的长度
int length = ioStack->Parameters.Write.Length;
//只有传进来的缓冲区大小与我们要求的传进来的结构体大小一致,才继续执行
//同时还要`gCommCllback`也就是我们指定的回调函数不为空
if (length == sizeof(CommPack) && gCommCllback != NULL)
{
//将缓冲区(R3->R0)放到我们自定义的`CommPack`结构体中进行处理
//相当于将R3传过来的序列化的数据转变成反序列化后的数据
//让缓冲区中的数据按照定义的的结构体的结构排列
CommPack* pack = Irp->AssociatedIrp.SystemBuffer;
//判断一下R3传给我们的数据包中的ID是否和我们指定的一致
if (pack->id == Comm_ID)
//使用我们定义的回调函数执行我们已经转化好的R3传进来的数据
//这里进行一下判断,判断结构化后数据是否可以访问,防止出现意外
//同时通过status接收返回值,判断是否成功
if (MmIsAddressValid(pack)) status = gCommCllback(pack);
}
//定义返回3环的数据长度,因为我们这里采用了`IRP_MJ_READ`的方式(直接操作内存),所以不需要定义返回三环的数据长度
Irp->IoStatus.Information = 0;
//设置通信状态
Irp->IoStatus.Status = status;
//告诉客户端已经接收到通讯,返回0
IoCompleteRequest(Irp, 0);
return status;
}
//驱动注册封装
NTSTATUS DriverRegComm(PDRIVER_OBJECT pDriver, CommCallBack callback)
{
//定义设备名称
UNICODE_STRING uName = { 0 };
//初始化设备名称
RtlInitUnicodeString(&uName, DEVICE_NAME);
//定义设备对象
PDEVICE_OBJECT pDevice = NULL;
//创建设备对象
NTSTATUS status = IoCreateDevice(pDriver, 0, &uName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevice);
if (!NT_SUCCESS(status))
{
KdPrintEx((77, 0, "创建设备对象失败[db]:status:%x\r\n", status));
return status;
}
DbgPrint("创建设备对象成功\r\n");
//创建符号链接
UNICODE_STRING uSymName = { 0 };
//初始化符号连接名
RtlInitUnicodeString(&uSymName, SYM_NAME);
// 将符号名和设备名绑定
status = IoCreateSymbolicLink(&uSymName, &uName);
if (!NT_SUCCESS(status))
{
//如果创建符号链接失败,则删除设备对象
IoDeleteDevice(pDevice);
KdPrintEx((77, 0, "创建符号链接失败[db]:status:%x\r\n", status));
return status;
}
DbgPrint("创建符号链接成功\r\n");
//设备对象初始化信息去掉,保证驱动可以正常通信
pDevice->Flags &= ~DO_DEVICE_INITIALIZING;
//设置通信方式为IO通信,在低版本的操作系统中(win 7 以上)必须定义这个
pDevice->Flags |= DO_BUFFERED_IO;
pDriver->MajorFunction[IRP_MJ_CREATE] = DefDispatch; //填充打开
pDriver->MajorFunction[IRP_MJ_CLOSE] = DefDispatch; //填充关闭
//隐蔽通信
pDriver->MajorFunction[IRP_MJ_WRITE] = WriteDispatch;
//如果一切正常的话
if (NT_SUCCESS(status))
{
//全局变量的gCommCllback值赋值成我们处理好后的数据
gCommCllback = callback;
}
return status;
}
//驱动通信销毁
VOID DriverCommDestory(PDRIVER_OBJECT pDriver)
{
//在这里又初始化了一遍符号链接,为了防止在接下来的去掉符号链接的时候符号链接没有初始化导致蓝屏
UNICODE_STRING uSymName = { 0 };
//初始化符号链接名
RtlInitUnicodeString(&uSymName, SYM_NAME);
//去掉符号链接
IoDeleteSymbolicLink(&uSymName);
//去掉设备
//确认一下设备是否注册
if (pDriver->DeviceObject) IoDeleteDevice(pDriver->DeviceObject);
DbgPrint("销毁成功\r\n");
}
Comm.h
//避免重复包含
#pragma once
#include <ntifs.h>
#include "CommStruct.h"
//定义设备名称
#define DEVICE_NAME L"\\Device\\mydevice"
/*
* CommCallBack 说明
接收R3传进来的消息,并进行封装,相当于定义了一个函数指针
定义标准回调尽量指定调用约定(NTAPI)
意思就是接收R3传进来的数据(PVOID*), 然后调用我们定义的回调函数(NTAPI* CommCallBack)
*/
//需要我们自己定义的回调函数
typedef NTSTATUS(NTAPI* CommCallBack)(PCommPack pack);
//将封装后的数据传进`DriverRegComm`进行注册
//(CommCallBack callback)定义的是等待执行的回调函数函数
NTSTATUS DriverRegComm(PDRIVER_OBJECT pDriver, CommCallBack callback);
//定义销毁函数,传进来驱动对象(因为后面会用驱动对象找设备对象)
VOID DriverCommDestory(PDRIVER_OBJECT pDriver);
CommStruct.h
#pragma once
//定义符号链接名称
#define SYM_NAME L"\\??\\mysym"
//定义通讯结构体中的ID,这个R3和R0的ID要一致
#define Comm_ID 0x123456
//定义一个测试结构体,用来测试封装的通信好不好用
typedef struct _Test
{
//char* name;
int heath;
int level;
}Test, * PTest;
//定义我们结构体中的Code(操作码)
typedef enum _DefCode
{
TEST = 0
}DefCode;
//定义一个结构体,接收R3传进来的数据(R3传进来的数据就是转换成这个结构体)
typedef struct __CommPack
{
//这里面的属性一定要定成64的,因为只有定成64才可以同时接收32和64位的R3传进来的数据
//标识符,只有这个标识过来的才继续执行
ULONG64 id;
//命令码(操作码)
ULONG64 code;
//R3->RO传入的数据
ULONG64 inDate;
//R3->RO传入的数据长度
ULONG64 inLength;
//R0->R3输出的数据
ULONG64 outData;
//R3->RO输出的数据长度
ULONG64 outLength;
}CommPack, * PCommPack;
main.c
#include <ntifs.h>
#include "Comm.h"
VOID Unload(PDRIVER_OBJECT pDriver)
{
//销毁设备
DriverCommDestory(pDriver);
DbgPrint("unload\r\n");
}
//这里要写我们自己定义的回调函数,和Comm.c中定义的`CommPack`一致
NTSTATUS NTAPI Dispatch(PCommPack pack)
{
//预定一个不正确的状态值
NTSTATUS status = STATUS_UNSUCCESSFUL;
switch (pack->code)
{
case TEST:
{
PTest t = (PTest)pack->inDate;
//DbgPrint("%s\r\n", t->name);
//t->name = "helo";
t->heath = 1111;
t->level = 222;
status = STATUS_SUCCESS;
}
break;
}
return status;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
pDriver->DriverUnload = Unload;
//DbgBreakPoint();
DriverRegComm(pDriver, Dispatch);
return STATUS_SUCCESS;
}
R3
Comm.c
#include "Comm.h"
//定义全局变量
//因为下面两个函数都使用
HANDLE hDevice = NULL;
//通信初始化;
BOOLEAN DriverInit()
{
//创建设备句柄
hDevice = CreateFileW(SYM_NAME, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
//判断句柄是否创建成功
if (hDevice == NULL || hDevice == INVALID_HANDLE_VALUE)
{
//防止句柄是INVALID_HANDLE_VALUE不是NULL
hDevice = NULL;
return FALSE;
}
return TRUE;
}
//驱动通信
BOOLEAN DriverComm(ULONG code, PVOID inData, ULONG inLength, PVOID outData, ULONG outLength)
{
//定义要通信R3->R0的结构体
CommPack pack;
//注意所有数据都要转化成ULONG64的
pack.id = (ULONG64)Comm_ID;
pack.code = (ULONG64)code;
pack.inData = (ULONG64)inData;
pack.inLength = (ULONG64)inLength;
pack.outDate = (ULONG64)outData;
pack.outLength = (ULONG64)outLength;
//不要觉得下面这两个if写的奇怪,就要这么写,不要写成else什么的,因为进行了两次判断
//如果句柄的值是NULL,那么就进行初始化
if (!hDevice)
{
DriverInit();
}
if (hDevice)
{
//调用Readfile函数实际的返回长度
DWORD pro = NULL;
return WriteFile(hDevice, &pack, sizeof(CommPack), &pro, NULL);
}
return FALSE;
}
Comm.h
#pragma once
#include <Windows.h>
#include "CommStruct.h"
#include <stdio.h>
//通信初始化;
BOOLEAN DriverInit();
//驱动通信
//code:通信码,inData:输入数据,inLength:输入数据大小,outData:输出数据,outLength:输出数据大小
BOOLEAN DriverComm(ULONG code, PVOID inData, ULONG inLength, PVOID outData, ULONG outLength);
CommStruct.h
#pragma once
#include "Comm.h"
//定义通讯结构体中的ID,这个要和R3程序的ID一致
#define Comm_ID 0x123456
//定义符号链接名称
#define SYM_NAME L"\\??\\mysym"
//定义一个测试结构体,用来测试封装后的inData数据
typedef struct _Test
{
//char* name;
int heath;
int level;
}Test, * PTest;
//定义我们结构体中的Code(操作码)
typedef enum _DefCode
{
TEST = 0
}DefCode;
//定义一个结构体,接收R3传进来的数据(R3传进来的数据就是转换成这个结构体)
typedef struct __CommPack
{
//这里面的属性一定要定成64的,因为只有定成64才可以同时接收32和64位的R3传进来的数据
//标识符,只有这个标识过来的才继续执行
ULONG64 id;
//命令码(操作码)
ULONG64 code;
//R3->RO传入的数据
ULONG64 inData;
//R3->RO传入的数据长度
ULONG64 inLength;
//R0->R3输出的数据
ULONG64 outDate;
//R3->RO输出的数据长度
ULONG64 outLength;
}CommPack, * PCommPack;
main.c
#include "Comm.h"
int main(int argc, char** argv)
{
//测试用的断点
/*
_asm
{
int 3;
}
*/
//结构体初始化
Test t = { 0 };
//t.name = "Bob";
t.heath = 100;
t.level = 23;
//printf("%s\r\n", t.name);
printf("%d\r\n", t.heath);
printf("%d\r\n", t.level);
BOOLEAN bRet = DriverComm(TEST, &t, sizeof(Test), NULL, NULL);
printf("%d\r\n", bRet);
//printf("%s\r\n", t.name);
printf("%d\r\n", t.heath);
printf("%d\r\n", t.level);
system("pause");
return 0;
}