您的位置:首页 > 其它

内核知识第五讲.驱动框架编写,以及3环和0环通信.

2018-01-14 16:43 453 查看

         内核知识第五讲.驱动框架编写,以及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

原创不易,转载请注明出处.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: