原本打算自己留着,因为写的太差,怕放出来让牛们看了笑话,不过一是最近刚开始学驱动,也遇到过不少问题,所以打算还是把学习笔记放出来,和大家交流下;也希望能给想学驱动的朋友点帮助;
驱动开发学习笔记一
Windows 驱动的基本功能是为上层服务提供一些调用例程,总的来说,驱动程序可以分为用户模式的驱动(如,打印机驱动程序,和VDD)和内核模式的驱动,而内核模式的驱动有如下三种类型:
a ::文件系统驱动 File System drviers 处理I/O请求
b :即插即用驱动 Plug and Play drivers 用于pnp和电源管理的硬件设备
c :非即插即用驱动 NO plug and play drivers 用于扩展系统功能
关于对驱动分类的介绍,还可以进一步划分,可查看,《windows internal Fourth edition》第八章,这里我们只需要知道windows中的驱动可分NT式驱动程序和WDM驱动程序即可;
NT驱动的加载过程之一 DriverEntry:
和所有的应用层面上编写的程序一样,windows驱动程序也有一个入口函数,其函数名一般为DriverEntry,在这个入口函数中主要是对驱动程序进行初始化工作,它由系统程序调用,即System进程,它在系统启动的时候就被创建;当驱动加载的时候,system进程启动一个新的线程,调用执行体组件中的对象管理器,创建一驱动对象;驱动对象是一个DRIVER_OBJECT的结构体,同时,system进程调用执行体组件的配置管理程序,查询此驱动程序对应的注册表项;下面我们来看一下Driver_Entry例程:
/************************************************************************
* 函数名称:DriverEntry
* 功能描述:初始化驱动程序,定位和申请硬件资源,创建内核对象
* 参数列表:
pDriverObject:从I/O管理器中传进来的驱动对象
pRegistryPath:驱动程序在注册表的中的路径
* 返回 值:返回初始化驱动状态
*************************************************************************/
#pragma INITCODE
extern "C" NTSTATUS DriverEntry (
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath )
{
NTSTATUS status;
KdPrint(("Enter DriverEntry\n"));
//注册其他驱动调用函数入口
pDriverObject->DriverUnload = HelloDDKUnload;
pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;
//创建驱动设备对象
status = CreateDevice(pDriverObject);
KdPrint(("DriverEntry end\n"));
return status;
}
DriverEntry的第一个参数是一个指针,指向一个刚被初始化的驱动程序对象,该对象就代表你的驱动程序。WDM驱动程序的DriverEntry例程应完成对这个对象的初始化并返回。非WDM驱动程序需要做大量额外的工作,它们必须探测自己的硬件,为硬件创建设备对象(用于代表硬件),配置并初始化硬件使其正常工作。而对于WDM驱动程序,颇麻烦的硬件探测和配置工作由PnP管理器自动完成,如果你想知道非WDM驱动程序是如何初始化自身的,参见Art Baker的《The Windows NT Device Driver Book (Prentice Hall, 1997)》、Viscarola和Mason的《Windows NT Device Driver Development (Macmillan, 1998)》。
DriverEntry的第二个参数是设备服务键的键名。这个串不是长期存在的(函数返回后可能消失),如果以后想使用该串就必须先把它复制到安全的地方。
这里我们只需要知道,DriverEnter函数的功能只是为由System进程创建的驱动对象进行初始化,例如,为常用IRP注册派遣例程,或者是startI/O例程,以及设置卸载例程函数和创建设备对象;
最后需要说明的是DriverEnter的参数修饰,IN,OUT,INOUT,它们在驱动程序中都被定义为空串: #define IN() #define OUT() 它们的功能是对参数进行注释,当看到一个“IN”参数时,代表其后面的参数纯粹用于输入目的,如在DriverEnter中的第一个参数,你可以改变这个DRIVER_OBJECT指针指向的对象,但你不能改变这个指针本身,也就是不能传一个不是DRIVER_OBJECT类型的指针给它;OUT代表这个参数仅用于输出参数,INOUT代表这个参数即用于输入也用于输出;
由上我们知道,在DriverEnter函数中只是对由系统创建的驱动对象做些初始化工作,而我们在应用程序下用CreateFileAPI函数打开的是设备,所以,在DriverEnter中有一函数用来创建设备对象,由于一个驱动对象可以创建多个设备对象,因此,创建设备对象的过程单独写成一函数;函数原型如下:
/************************************************************************
* 函数名称:CreateDevice
* 功能描述:初始化设备对象
* 参数列表:
pDriverObject:从I/O管理器中传进来的驱动对象
* 返回 值:返回初始化状态
*************************************************************************/
#pragma INITCODE
NTSTATUS CreateDevice (
IN PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
//创建设备名称
UNICODE_STRING devName;
RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
//创建设备
status = IoCreateDevice( pDriverObject,
sizeof(DEVICE_EXTENSION),
&(UNICODE_STRING)devName,
FILE_DEVICE_UNKNOWN,
0, TRUE,
&pDevObj );
if (!NT_SUCCESS(status))
return status;
pDevObj->Flags |= DO_BUFFERED_IO;
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
pDevExt->pDevice = pDevObj;
pDevExt->ustrDeviceName = devName;
//创建符号链接
UNICODE_STRING symLinkName;
RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK");
pDevExt->ustrSymLinkName = symLinkName;
status = IoCreateSymbolicLink( &symLinkName,&devName );
if (!NT_SUCCESS(status))
{
IoDeleteDevice( pDevObj );
return status;
}
return STATUS_SUCCESS;
}
其实,上面这段代码中,真正创建设备的是IoCreateDevice函数,此函数在DDk中声明如下;
NTKERNELAPI
NTSTATUS
IoCreateDevice(
IN PDRIVER_OBJECT DriverObject, //设备对象
IN ULONG DeviceExtensionSize, //扩展大小
IN PUNICODE_STRING DeviceName OPTIONAL, //设备对象名
IN DEVICE_TYPE DeviceType, //设备类型
IN ULONG DeviceCharacteristics, //设备对象特征
IN BOOLEAN Exclusive, //设置设备对象是否可在内核模式下使用
OUT PDEVICE_OBJECT *DeviceObject //输出参数,I/o管理器负责创建这个设备对象
);
CreateDevice这个函数时,有几点需要注意:
1: 设备名称用unicode 字符指定,并且字符串必须是 “\Device\设备名 的形式,如磁盘分区C盘,为\Device\harddisk\volume1,当然你也可以不用指定设备名,但是这样做,不是设备就不需要设备名了,而是I/O管理器为设备创建一设备名,如果指定了设备名,只能被内核模式下的其他驱动程序识别,但是在用户模式下的应用程序能识别它吗?答案是否定的; 为了让用户模式的应用程序能够识别或者是使用设备,有两种方法可以使用: 一是通过创建一符号链接给用户模式应用程序使用,二是使用设备接口,一般NT中很少使用设备接口,而WDM中使用的较多;
2:你可以将符号链接理解为是设备对象的一个别名,设备对象的名称只能被内核模式下的驱动识别,而别名也可以被用户模式下的应用程序识别.创建符号链接用如下函数:
NTKERNELAPI
NTSTATUS
IoCreateSymbolicLink(
IN PUNICODE_STRING SymbolicLinkName,
IN PUNICODE_STRING DeviceName
);
注意:在内核模式下使用字符串必须使用unicode字符串,关于字符串的定义和使用有与之相关的函数,后面会单独介绍;
我们可以再设备扩展中记录我们需要的使用的对象或者是变量,也可以是其它的一些数据结构;
卸载设备驱动程序:
/************************************************************************
* 函数名称:HelloDDKUnload
* 功能描述:负责驱动程序的卸载操作
* 参数列表:
pDriverObject:驱动对象
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
VOID HelloDDKUnload (IN PDRIVER_OBJECT pDriverObject)
{
PDEVICE_OBJECT pNextObj;
KdPrint(("Enter DriverUnload\n"));
pNextObj = pDriverObject->DeviceObject;
while (pNextObj != NULL)
{
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
pNextObj->DeviceExtension;
//删除符号链接
UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
IoDeleteSymbolicLink(&pLinkName);
pNextObj = pNextObj->NextDevice;
IoDeleteDevice( pDevExt->pDevice );
}
}
由于在DriverEnter函数中创建了设备对象和符号链接,有可能还申请了其它一些资源,所以,当退出时,需要删除设备对象和符号链接,以及回收DriverEnter函数中申请的资源;我们知道,驱动对象可能拥有不至一个的设备对象,并且通过链表来管理这些设备对象,所以,在DriverUnload函数中,我们遍历由DriverObject其中一个愈传进来的设备对象链表,并将它们一一删除,删除设备使用函数IoDeleteDevice函数:
派遣函数:
/************************************************************************
* 函数名称:HelloDDKDispatchRoutine
* 功能描述:对读IRP进行处理
* 参数列表:
pDevObj:功能设备对象
pIrp:从IO请求包
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
KdPrint(("Enter HelloDDKDispatchRoutine\n"));
NTSTATUS status = STATUS_SUCCESS;
// 完成IRP
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0; // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKDispatchRoutine\n"));
return status;
}
在win32中,我们都知道应用程序是消息驱动的,而在驱动模式中,可以把驱动理解为是IRP驱动的,驱动程序一般是分层结构,对设备的操作会转化为对HAL,硬件抽象层的操作,IRP由I/O管理器创建,并传递给对应的驱动程序处理,而驱动程序调用我们在DriverEnter中注册的pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;函数来处理与之对应的IRP;
IRP是驱动开发的核心,关于IRP的数据结构和运行机制,学完后单独用一章来总结;
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法