一丶了解内核驱动加载方式
内核加载方式有两种方式.
1. 动态加载方式.
2. 静态加载方式
动态加载方式:
动态态加载方式则是调用 3 环 API 进行代码加载.
详情请点击 : 内核驱动加载工具的编写 .
静态加载方式
静态的加载方式则是利用 后缀为. inf 的文件进行加载.
有关. inf 的语法, 可以百度或者通过学习 WDK 中提供的源码例子进行学习.
动态加载一般很常用.
二丶驱动框架的介绍.
在讲解内核驱动框架的是否, 我们要先了解设备是什么. 设备和驱动之间的数据关系是什么.
1. 什么是驱动. 什么是设备
驱动: 驱动则是用来操作设备的.
设备: 设备则是我们常说的外设. 比如键盘. 显示器. 鼠标等等..
其中. 任何一个驱动操作设备. 都应该提供公共的方法.
打开. 关闭. 读. 写. 控制....
如图:
用户操作设备的是否. 这个时候会通过内核驱动. 提供的 回调方法.(也就是公共的接口) 进而来操作设备.
2. 驱动和设备之间的关系.
驱动和设备之间的关系是一对多的关系.
驱动可以操作很多设备.
比如我们的键盘驱动有一个. 但是可以操作的键盘则有很多个. 你键盘坏了. 换了很多. 但是驱动则没换过.
所以如果是数据关系的时候. 驱动作为外键放到设备表中.
例如以下:
设备
驱动
A 键盘
标准驱动
B 键盘
标准驱动
有了数据关系. 那么就可以讲解驱动框架了.
3. 驱动框架的介绍.
驱动对象. 设备对象.
在驱动中有这两个概念.
驱动对象: 简单来说就是一个结构体, 存储了驱动的各种信息.
设备对象: 简单来说也是一个结构体, 存储的是设备的各种信息.
但依据上面的数据关系来说. 设备对象中肯定会存储驱动对象结构体的指针. 驱动对象做外键存储到设备对象中.
设备对象结构体:
typedef struct _DRIVER_OBJECT {
CSHORT Type; //类型
CSHORT Size; //当前结构体大小.内核中任何一个结构体都是这两个成员开头.
ULONG Flags; //通讯协议以及方式.
//
// 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; //设备对象指针,存疑? 不是说数据关系是 设备表中有驱动对象吗. 怎么驱动对象表中有设备对象指针.???????
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; //提供的公共方法的接口. 创建.打开,关闭.读写控制... 里面存放的是函数指针.单用户操作设备的是否.则会调用这些回调函数指针.
//
// 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;
存疑部分:
}
DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT * PDRIVER_OBJECT;
上面标红的部分. 不是说表关系应该是上面那种吗.? 如下图所示
设备
驱动
A 键盘
标准驱动
B 键盘
标准驱动
可为何设计为这样.
原因:
我们的内核驱动可以操作设备. 但是我们要知道有多少设备怎么办. 所以这里给了一个设备对象的指针. 而不是我们说的数据关系.
而在设备对象中. 存储的则是我们的驱动对象指针.
而这里的指针. 则是一个链表形式的. 为了方便遍历.
例如:
struct _DRIVER_OBJECT * DriverObject; //驱动对象做外键存储
typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT {
CSHORT Type;
USHORT Size;
LONG ReferenceCount;
三丶编写驱动框架.
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,
2. 创建虚拟设备.
__inout struct _IRP *Irp)
{
.........
return STATUS_SUCCESS;
}
创建设备等等. 都属于 IO 操作.
IO 操作创建设备的 API
IN PDRIVER_OBJECT DriverObject,//调用者驱动对象的指针.一般是驱动入口的参数
NTSTATUS
IoCreateDevice(
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
我们拼接一个路径
注意, 创建设备的时候. 我们前边需要加上 \Device. 这里因为是转义字符. 所以加了好多 \\
UNICODE_STRING uStrDeviceName;
RtlInitUnicodeString( & uStrDeviceName, L "\\Device\\xxx 名字即可 ");
拼接好则可以给了.
3. 设置通讯方式.
status = IoCreateDevice(pDriverObject,
0,
&ustrDeviceName, //设备路径
FILE_DEVICE_UNKNOWN,//设备类型设置为不知道
FILE_DEVICE_SECURE_OPEN,
FALSE, //是否独占
&pDevObj);
if (status != STATUS_SUCCESS)
{
return status;
}
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 {
.//省略了两个成员,这两个成员一个是类型.一个是大小.
我们有了缓冲区, 但是不知道缓冲区的大小. 这个是否需要通过 IO 函数. 从当前栈中获取参数.
.
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;
IoGetCurrentIrpStackLocation(pIrp)
返回当前 IRP 的栈. 我们从返回值中获取参数即可.
操作完成之后, 我们完成 IRP 请求即可. 这个 IRP 请求主要是为了多线程使用. 有的时候可能在读取的是否. 线程就切换了.
ring0 下的读取回调完整编写.
课堂 3 环和 0 环的完整代码资料:
//获取当前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;
链接: https://pan.baidu.com/s/1edffGy 密码:vpo0
来源: https://www.cnblogs.com/iBinary/p/8283642.html