内核知识第五讲.驱动框架编写,以及3环和0环通信.
内核知识第五讲.驱动框架编写,以及3环和0环通信.
内核知识第五讲.驱动框架编写,以及3环和0环通信.
一丶了解内核驱动加载方式
内核加载方式有两种方式.
1.动态加载方式.
2.静态加载方式
动态加载方式:
动态态加载方式则是调用3环API 进行代码加载.
详情请点击 : 内核驱动加载工具的编写.
静态加载方式
静态的加载方式则是利用 后缀为.inf 的文件进行加载.
有关.inf的语法,可以百度或者通过学习WDK中提供的源码例子进行学习.
动态加载一般很常用.
二丶驱动框架的介绍.
在讲解内核驱动框架的是否,我们要先了解设备是什么. 设备和驱动之间的数据关系是什么.
1.什么是驱动.什么是设备
驱动: 驱动则是用来操作设备的.
设备: 设备则是我们常说的外设. 比如键盘. 显示器. 鼠标等等..
其中.任何一个驱动操作设备. 都应该提供公共的方法.
打开. 关闭. 读. 写. 控制….
如图:
用户操作设备的是否. 这个时候会通过内核驱动.提供的 回调方法.(也就是公共的接口)进而来操作设备.
2.驱动和设备之间的关系.
驱动和设备之间的关系是一对多的关系.
驱动可以操作很多设备.
比如我们的键盘驱动有一个. 但是可以操作的键盘则有很多个. 你键盘坏了.换了很多.但是驱动则没换过.
所以如果是数据关系的时候. 驱动作为外键放到设备表中.
例如以下:
设备 |
驱动 |
A键盘 |
标准驱动 |
B键盘 |
标准驱动 |
有了数据关系.那么就可以讲解驱动框架了.
3.驱动框架的介绍.
驱动对象.设备对象.
在驱动中有这两个概念.
驱动对象: 简单来说就是一个结构体,存储了驱动的各种信息.
设备对象: 简单来说也是一个结构体,存储的是设备的各种信息.
但依据上面的数据关系来说. 设备对象中肯定会存储驱动对象结构体的指针. 驱动对象做外键存储到设备对象中.
设备对象结构体:
typedef struct _DRIVER_OBJECT { CSHORT Type; //类型 CSHORT Size; //当前结构体大小.内核中任何一个结构体都是这两个成员开头. // // The following links all of the devices created by a single driver // together on a list, and the Flags word provides an extensible flag // location for driver objects. // PDEVICE_OBJECT DeviceObject;//设备对象指针,存疑? 不是说数据关系是 设备表中有驱动对象吗. 怎么驱动对象表中有设备对象指针.??????? ULONG Flags; //通讯协议以及方式. // // The following section describes where the driver is loaded. The count // field is used to count the number of times the driver has had its // registered reinitialization routine invoked. // PVOID DriverStart; ULONG DriverSize; PVOID DriverSection; PDRIVER_EXTENSION DriverExtension; // // The driver name field is used by the error log thread // determine the name of the driver that an I/O request is/was bound. // UNICODE_STRING DriverName; // // The following section is for registry support. Thise is a pointer // to the path to the hardware information in the registry // PUNICODE_STRING HardwareDatabase; // // The following section contains the optional pointer to an array of // alternate entry points to a driver for "fast I/O" support. Fast I/O // is performed by invoking the driver routine directly with separate // parameters, rather than using the standard IRP call mechanism. Note // that these functions may only be used for synchronous I/O, and when // the file is cached. // PFAST_IO_DISPATCH FastIoDispatch; // // The following section describes the entry points to this particular // driver. Note that the major function dispatch table must be the last // field in the object so that it remains extensible. // PDRIVER_INITIALIZE DriverInit; PDRIVER_STARTIO DriverStartIo; PDRIVER_UNLOAD DriverUnload; PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; //提供的公共方法的接口. 创建.打开,关闭.读写控制... 里面存放的是函数指针.单用户操作设备的是否.则会调用这些回调函数指针. } DRIVER_OBJECT; typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;
存疑部分:
上面标红的部分. 不是说表关系应该是上面那种吗.?如下图所示
设备 |
驱动 |
A键盘 |
标准驱动 |
B键盘 |
标准驱动 |
可为何设计为这样.
原因:
我们的内核驱动可以操作设备. 但是我们要知道有多少设备怎么办. 所以这里给了一个设备对象的指针. 而不是我们说的数据关系.
而在设备对象中.存储的则是我们的驱动对象指针.
而这里的指针.则是一个链表形式的. 为了方便遍历.
例如:
typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT { CSHORT Type; USHORT Size; LONG ReferenceCount; struct _DRIVER_OBJECT *DriverObject; //驱动对象做外键存储 struct _DEVICE_OBJECT *NextDevice; //链表. struct _DEVICE_OBJECT *AttachedDevice; struct _IRP *CurrentIrp; PIO_TIMER Timer; ULONG Flags; // See above: DO_... ULONG Characteristics; // See ntioapi: FILE_... __volatile PVPB Vpb; PVOID DeviceExtension; DEVICE_TYPE DeviceType; CCHAR StackSize; union { LIST_ENTRY ListEntry; WAIT_CONTEXT_BLOCK Wcb; } Queue; ULONG AlignmentRequirement; KDEVICE_QUEUE DeviceQueue; KDPC Dpc; // // The following field is for exclusive use by the filesystem to keep // track of the number of Fsp threads currently using the device // ULONG ActiveThreadCount; PSECURITY_DESCRIPTOR SecurityDescriptor; KEVENT DeviceLock; USHORT SectorSize; USHORT Spare1; struct _DEVOBJ_EXTENSION *DeviceObjectExtension; PVOID Reserved; } DEVICE_OBJECT; typedef struct _DEVICE_OBJECT *PDEVICE_OBJECT;
三丶编写驱动框架.
上面我们已经简单的了解了驱动对象.设备对象是什么了.那么现在开始编写驱动框架
步骤
1.首先注册设备回调函数.当用户对设备进行操作的是否.驱动会调用这个回调函数进行操作.
2.创建设备.创建虚拟的设备给用户使用.
3.指定通讯方式. 什么意思?比如ring3中操作设备进行读写的时候 如果用ReadFile读取.那么你们的通讯方式是否是字符串
4.创建符号连接.
符号连接: 我们知道.在操作系统下有C盘.D盘一说.但是在驱动下面.则没有怎么一说.只有卷一说.所以我们要绑定一下.
PS: 鉴于篇幅原因.只写重点.如果想要完整的驱动框架. 请下载资料进行查看.
1.注册回调函数.
pDriverObject->MajorFunction[IRP_MJ_CREATE] = 创建的回调函数指针; pDriverObject->MajorFunction[IRP_MJ_CLOSE] = 关闭的回调函数指针; pDriverObject->MajorFunction[IRP_MJ_READ] = 读取的回调函数指针; pDriverObject->MajorFunction[IRP_MJ_WRITE] = 写入的回调函数指针; pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 控制的回调函数指针;
回调函数的写法.
NTSTATUS 自定义的函数名(__in struct _DEVICE_OBJECT *DeviceObject, __inout struct _IRP *Irp) { ......... return STATUS_SUCCESS; }
2.创建虚拟设备.
创建设备等等.都属于IO操作.
IO操作创建设备的API
NTSTATUS IoCreateDevice( IN PDRIVER_OBJECT DriverObject, //调用者驱动对象的指针.一般是驱动入口的参数 IN ULONG DeviceExtensionSize, //设备扩展大小 IN PUNICODE_STRING DeviceName OPTIONAL, //设备对象名称,注意不是我们ring3下的路径. IN DEVICE_TYPE DeviceType, //我们的设备类型 IN ULONG DeviceCharacteristics, //驱动的附加信息. IN BOOLEAN Exclusive, //创建的设备是否其余的可以使用,是否独占 OUT PDEVICE_OBJECT *DeviceObject //传出参数,设备的信息. );
注意红点标注:
在内核中并没有路径一说.所以这个路径是特殊的.
UNICODE_STRING 内核中新的字符串格式.其实是一个结构体.系统提供了操作这种结构体的API
我们拼接一个路径
UNICODE_STRING uStrDeviceName;
RtlInitUnicodeString(&uStrDeviceName,L”\\Device\\xxx名字即可”);
注意,创建设备的时候.我们前边需要加上 \Device. 这里因为是转义字符.所以加了好多\\
拼接好则可以给了.
status = IoCreateDevice(pDriverObject, 0, &ustrDeviceName, //设备路径 FILE_DEVICE_UNKNOWN,//设备类型设置为不知道 FILE_DEVICE_SECURE_OPEN, FALSE, //是否独占 &pDevObj); if (status != STATUS_SUCCESS) { return status; }
3.设置通讯方式.
pDevObj->Flags |= DO_BUFFERED_IO; //指定通讯方式,为缓冲区
4.创建符号连接
我们创建的符号连接.可以通过 Win0bj工具进行查看. 这个工具可以查看所有设备.但是只有0环才可以操作.
status = IoCreateSymbolicLink(&g_ustrSymbolName, &ustrDeviceName); if (status != STATUS_SUCCESS) { //删除设备 IoDeleteDevice(pDevObj); return status; }
注意符号连接名字.我们也是初始化得出的.
RtlInitUnicodeString(&g_ustrSymbolName, L"\\DosDevices\\xxxx名字");
完整的框架已经写完.剩下的就是 三环和0环驱动通讯. 依赖我们写的框架.
四丶三环和0环的通讯.
三环操作设备的API就是 CreateFile ReadFile…..等等.不做介绍了.
利用三环程序.操作我们的设备.从中读取内容.
HANDLE hFile = CreateFile("\\\\?\\符号连接名称", GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
打开我们的设备.注意文件名并不是路径.而是我们绑定的符号连接. 这里我们可以写成\\?
读取内容.
char szBuf[10]; ReadFile(hFile, szBuff, sizeof(szBuff), &dwBytes, NULL)
请注意,当读取设备的是否.我们一开始注册的回调函数就会来. 这时候我们给它什么都可以了.
但是此时需要讲解一下通讯协议.
当读取的是否,回调函数会来. 然后操作系统会填写 struct _IRP 结构体.用来和我们的零环通信.
typedef struct _IRP { . //省略了两个成员,这两个成员一个是类型.一个是大小. . PMDL MdlAddress; ULONG Flags; union { struct _IRP *MasterIrp; . . PVOID SystemBuffer;//ring3下的缓冲区.操作系统会填写.我们给里面填写什么内容.那么ring3就读取到什么. } AssociatedIrp; . . IO_STATUS_BLOCK IoStatus; KPROCESSOR_MODE RequestorMode; BOOLEAN PendingReturned; . . BOOLEAN Cancel; KIRQL CancelIrql; . . PDRIVER_CANCEL CancelRoutine; PVOID UserBuffer; union { struct { . . union { KDEVICE_QUEUE_ENTRY DeviceQueueEntry; struct { PVOID DriverContext[4]; }; }; . . PETHREAD Thread; . . LIST_ENTRY ListEntry; . . } Overlay; . . } Tail; } IRP, *PIRP;
我们有了缓冲区,但是不知道缓冲区的大小.这个是否需要通过IO函数.从当前栈中获取参数.
IoGetCurrentIrpStackLocation(pIrp)
返回当前IRP的栈.我们从返回值中获取参数即可.
操作完成之后,我们完成IRP请求即可.这个IRP请求主要是为了多线程使用.有的时候可能在读取的是否.线程就切换了.
ring0下的读取回调完整编写.
//获取当前irp堆栈 PIO_STACK_LOCATION pIrpStack = NULL; PVOID lpBuff = NULL; ULONG Length = 0; //PsGetCurrentThreadId(); KdBreakPoint(); __try { pIrpStack = IoGetCurrentIrpStackLocation(pIrp); //check lpBuff = pIrp->AssociatedIrp.SystemBuffer; Length = pIrpStack->Parameters.Read.Length; KdPrint(("[FirstWDK] DispatchRead\n")); RtlStringCbCopyA(lpBuff, Length, "Hello World!"); //check pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 6; //完成Irp请求 IoCompleteRequest(pIrp, IO_NO_INCREMENT); //check } __except(1) { } return STATUS_SUCCESS;
课堂3环和0环的完整代码资料:
链接:https://pan.baidu.com/s/1edffGy 密码:vpo0
原创不易,转载请注明出处.