驱动框架
R3操作一个文件
create->read/write/deviceiocontrol->clean->close
DIspatch分发函数存放在Driverobject的一个数组MajorFunction里。0x1b个,28个。用来接收应用层API请求。就是应用层的请求封装在一个IRP包,请求内核操作。
驱动有两个模型,NT模型和WDM模型。
NT模型
DriverEntry()(单线程环境),Close和clean区别,close是FileObject引用为0,cleanup是handle引用为0(XCB和文件一对一,FileObject是多个)
WDM模型
WDMAddDevice(即插即用,设备创建在改例程里完成)
WDMPnp()即插即用。
设备连接名和符号名
c:\windows\test.txt
\??\c:
\device\harddiskvolum3\windows\1.txt
\device\harddiskvolum3这个就是设备名,\??\c:就是符号链接。
DO_BUFFERED_IO规定R3和R0之间read和write通信的方式:
1,buffered io
2,direct io
3,neither io
DO_DEVICE_INITIALIZING
buffered io是基于缓存的通信,内核会生成一个同样大小的缓存把应用层的数据拷贝到这个缓存中,反过来也是相同。这些由操作系统的IO管理器操作。
direct io,内存中的数据是一个虚拟内存,实际存放的是个物理内存中,在这种通信方式下,是把这块物理内存锁住。不让他交换出去,然后将它映射(通过MDL)到内核的虚拟内存地址。
neither io是直接把应用层内存发给内核,这种最不安全。使用这种要做到,必须内核应用层是同一个进程空间中,读的话ProbeForRead(buf,....)/写的话要ProbeForWrite(buf,...)这两个函数是保证地址必须是应用层的地址,还要在try/except里。
DO_DEVICE_INITIALIZING
DO_DEVICE_INITIALIZING这是一个标志。目的是防止其他组件在驱动程序完成初始化设备对象之前向设备发送IO请求,所以完成初始化时要清理掉这个标志,这里负责清理的是在DriverEntry中创建的设备对象,由IO管理器负责清除该标志,其他地方创建的设备对象(比如过滤设备对象),由驱动程序负责清除。
通用的分发函数
NTSTATUS DispatchCommon(PDEVICE_OBJECT pObject, PIRP pIrp)
{
pIrp->IoStatus.Status = STATUS_SUCCESS;//这个是给ring3的返回值
pIrp->IoStatus.Information = 0;//记录Irp特殊数据,比如读写就是有效字节数
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;//这个是给内核驱动管理框架的
}
DriverEntry里的流程,创建设备对象,定义通信模式,创建符号链接,完成通用的分发函数初始化。
DriverUnload卸载函数,删除资源防止内存泄露。
重命名之类的是次功能,不在majorfunction里。
IRP结构
分发函数DispatchRead
从内核读数据,比如加解密中的解密,代码示例如下:
NTSTATUS DispatchRead(PDEVICE_OBJECT pObject, PIRP pIrp)
{
//首先要把数据发给应用层,所以要知道大小。
UNREFERENCED_PARAMETER(pObject);
PVOID pReadBuffer = NULL;
ULONG uReadLength = 0;
PIO_STACK_LOCATION pStack = NULL;
ULONG uMin = 0;
ULONG uHelloStr = 0;
uHelloStr = (ULONG)(wcslen(L"hello world")+1)*sizeof(WCHAR);
//第一步,拿到缓存的地址和长度
//从头部拿缓存地址
pReadBuffer = pIrp->AssociatedIrp.SystemBuffer;
//从栈上拿缓存长度
pStack = IoGetCurrentIrpStackLocation(pIrp);
uReadLength = pStack->Parameters.Read.Length; //第二步:读,写等操作
uMin = uReadLength>uHelloStr?uHelloStr:uReadLength;//传内核和应用层两者小的长度,比如应用层传的长度大,会造成数据溢出,传出敏感数据,或者无效数据导致蓝屏。如果传的比要的小,会溢出。
RtlCopyMemory(pReadBuffer, L"hello world",uMin);
//第三步,完成IRP
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = uMin;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
写分发函数,DispatchWrite
流程是从irp头部拿到首地址,栈上拿到长度,处理,然后解锁IRP。
NTSTATUS DispatchWrite(PDEVICE_OBJECT pObject, PIRP pIrp)
{
UNREFERENCED_PARAMETER(pObject);
PVOID pWriteBuff = NULL;
ULONG uWriteLength = 0;
PIO_STACK_LOCATION pStack = NULL;
PVOID pBuffer = NULL;
pWriteBuff = pIrp->AssociatedIrp.SystemBuffer;
pStack = IoGetCurrentIrpStackLocation(pIrp);
uWriteLength = pStack->Parameters.Write.Length;
pBuffer = ExAllocatePoolWithTag(PagedPool,//内存是否分页,非分页任何时候使用,资源少,分页只能在无中断级别使用。
uWriteLength, 'TSET');
if(pBuffer == NULL)
{
pIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_INSUFFICIENT_RESOURCES;
}
memset(pBuffer, 0, uWriteLength);
RtlCopyMemory(pBuffer, pWriteBuff, uWriteLength);
ExFreePool(pBuffer);
pBuffer=NULL;
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = uWriteLength;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
DisPatchIoControl
内核和应用进行特殊的通信。
#define IOCTRL_BASE 0x800
#define MYIOCTRL_CODE(i) \
CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTRL_BASE+i, METHOD_BUFFERED,FILE_ANY_ACCESS)
#define CTL_HELLO MYIOCTRL_CODE(0)
#define CTL_PRINT MYIOCTRL_CODE(1)
#define CTL_BYE MYIOCTRL_CODE(2)
NTSTATUS DispatchIoctrl(PDEVICE_OBJECT pObject, PIRP pIrp)
{
UNREFERENCED_PARAMETER(pObject);
ULONG uIoctrlCode = 0;
PVOID pInputBuff = NULL;
PVOID pOutputBuff = NULL;
ULONG uInputLength = 0;
ULONG uOutputLength = 0;
PIO_STACK_LOCATION pStack = NULL;
pInputBuff = pOutputBuff = pIrp->AssociatedIrp.SystemBuffer;
pStack = IoGetCurrentIrpStackLocation(pIrp);
uInputLength = pStack->Parameters.DeviceIoControl.InputBufferLength;
uOutputLength = pStack->Parameters.DeviceIoControl.OutputBufferLength;
uIoctrlCode = pStack->Parameters.DeviceIoControl.IoControlCode;
switch(uIoctrlCode)
{
case CTL_HELLO:
DbgPrint("Hello iocontrol\n");
break;
case CTL_PRINT:
DbgPrint("%ws\n", pInputBuff);
//*(DWORD *)pOutputBuff =2;
break;
case CTL_BYE:
DbgPrint("Goodbye iocontrol\n");
break;
default:
DbgPrint("Unknown iocontrol\n");
}
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;//sizeof(DWORD);
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
驱动的运行
1.创建一个服务(注册表),HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\服务名,启动由GROUP与StartType决定加载早晚,StartType越小越早,同一个StartType,按GroupOrder顺序来启动(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GroupOrderList)
2.对象管理器生成驱动对象(DriverObject),并传递给DriverEntry执行入口函数。
3.创建控制设备对象
4.创建控制设备符号链接(ring3可见)
5.如果是过滤驱动,创建过滤设备对象,绑定
5.注册分发函数
6.初始化操作
7.应用程序打开驱动设备对象(handle),向驱动设备对象发送打开读写等IRP请求。