内核安全编程(一)读写驱动程序1.1

来源:互联网 发布:php 秒杀系统设计思路 编辑:程序博客网 时间:2024/06/06 08:27

内核安全编程(一)读写驱动程序1.1

读写驱动程序,即应用程序或者上层驱动程序发送主功能码为IRP_MJ_READ和IRP_MJ_WRITE的IO请求包(IRP)

DriverEntry函数如下:

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING  RegistryPath){UNICODE_STRING DeviceName,Win32Device;PDEVICE_OBJECT DeviceObject = NULL;NTSTATUS status;unsigned i;KdPrint(("[DriverEntry]\n"));RtlInitUnicodeString(&DeviceName,L"\\Device\\Driver_write0");RtlInitUnicodeString(&Win32Device,L"\\DosDevices\\Driver_write0");for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)DriverObject->MajorFunction[i] = Driver_writeDefaultHandler;DriverObject->MajorFunction[IRP_MJ_CREATE] = Driver_writeCreateClose;DriverObject->MajorFunction[IRP_MJ_CLOSE] = Driver_writeCreateClose;DriverObject->MajorFunction[IRP_MJ_READ]=Driver_ReadWrite;DriverObject->MajorFunction[IRP_MJ_WRITE]=Driver_ReadWrite;//DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=Driver_DevControl;DriverObject->DriverUnload = Driver_writeUnload;status = IoCreateDevice(DriverObject,10,&DeviceName,FILE_DEVICE_UNKNOWN,0,FALSE,&DeviceObject);if (!NT_SUCCESS(status))return status;if (!DeviceObject)return STATUS_UNEXPECTED_IO_ERROR;memset(DeviceObject->DeviceExtension,'A',10);//DeviceObject->Flags |= DO_DIRECT_IO;//DeviceObject->Flags |=DO_BUFFERED_IO;status = IoCreateSymbolicLink(&Win32Device, &DeviceName);DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;return STATUS_SUCCESS;}

主要添加了两个IRP的处理例程:IRP_MJ_READ和IRP_MJ_WRITE的处理例程Driver_ReadWrite,注意在IOCreateDevice时的第二个参数为10,意思是自定义区域的大小为10个字节。然后还有调用memset初始化内存DeviceObject->DeviceExtension这个结构,网上没找到这个结构的详细信息,从书上来看,这里是指定的10个字节的设备扩展,用来保存应用程序(ring3)写入的内容。

Driver_ReadWrite函数如下:

NTSTATUS Driver_ReadWrite(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp){NTSTATUS status=STATUS_SUCCESS;PIO_STACK_LOCATION pSP=IoGetCurrentIrpStackLocation(Irp);PVOID pBuffer=NULL;BOOLEAN bNeither=FALSE;ULONG uLen=0;KdPrint(("[Driver_ReadWrite]\n"));if(DeviceObject->Flags&DO_BUFFERED_IO){KdPrint(("Flags:DO_BUFFER_IO\n"));pBuffer=Irp->AssociatedIrp.SystemBuffer;}else if(DeviceObject->Flags&DO_DIRECT_IO){KdPrint(("Flags:DO_DIRECT_IO\n"));pBuffer=MmGetSystemAddressForMdl(Irp->MdlAddress);}else{KdPrint(("Flags:Neither\n"));bNeither=TRUE;pBuffer=Irp->UserBuffer;}switch(pSP->MajorFunction){case IRP_MJ_READ:uLen=pSP->Parameters.Read.Length;uLen=uLen>10?10:uLen;KdPrint(("IRP_MJ_READ Read Len: %d\n",pSP->Parameters.Read.Length));if(FALSE==bNeither){RtlCopyMemory(pBuffer,DeviceObject->DeviceExtension,uLen);}else{_try{ProbeForWrite(pBuffer,uLen,4);RtlCopyMemory(pBuffer,DeviceObject->DeviceExtension,uLen);}_except(EXCEPTION_EXECUTE_HANDLER){KdPrint(("IRP_MJ_READ exceptio!\n"));status=STATUS_UNSUCCESSFUL;}}break;case IRP_MJ_WRITE:uLen=pSP->Parameters.Write.Length;uLen=uLen>10?10:uLen;KdPrint(("IRP_MJ_WRITE Write Len:%d\n",pSP->Parameters.Write.Length));if(FALSE==bNeither){RtlCopyMemory(DeviceObject->DeviceExtension,pBuffer,uLen);}else{_try{ProbeForRead(pBuffer,uLen,4);RtlCopyMemory(DeviceObject->DeviceExtension,pBuffer,uLen);}_except(EXCEPTION_EXECUTE_HANDLER){KdPrint(("IRP_MJ_WRITE exception!\n"));status=STATUS_UNSUCCESSFUL;}}break;}Irp->IoStatus.Status=status;Irp->IoStatus.Information=uLen;IoCompleteRequest(Irp,IO_NO_INCREMENT);return status;}

当设备收到IRP请求为IRP_MJ_READ或者IRP_MJ_WRITE时会调用此函数。

几点注意的地方:

1.函数IoGetCurrentIrpStackLocation返回一个IO_STACK_LOCATION的指针。该结构描述如下:

typedef struct _IO_STACK_LOCATION {  UCHAR  MajorFunction;  UCHAR  MinorFunction;  UCHAR  Flags;  UCHAR  Control;  union {           .....             }}

成员MajorFunction在MSDN的描述:

MajorFunctionThe IRP major function code indicating the type of I/O operation to be performed.

由于很多时候几个IRP请求可能使用同一个例程函数,因此这里可以通过该结构来获取对应的IRP请求。

2.这里有一个地方让我困惑了许久:

//DeviceObject->Flags |= DO_DIRECT_IO;//DeviceObject->Flags |=DO_BUFFERED_IO;...DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;if(DeviceObject->Flags&DO_DIRECT_IO)else if(DeviceObject->Flags&DO_DIRECT_IO)
对这种操作判断方式不甚明白,自己动手查了一下,发现:
DO_DEVICE_INITIALIZING=0x00000080=10000000(bin)
那么~DO_DEVICE_INITIALIZING=0x7F=11111111(bin)
因此如果Device->Flags|DO_DIRECT_IO(0x10)&11111111&DO_DIRECT_IO那么判断分支刚好为0...(不知道能不能这样理解...)



3.当采用DO_BUFFERED_IO访问IO数据时,用Irp->AssociatedIrp.SystemBuffer获取输入参数,这里pBuffer即指向ring3的一块内存(输入参数)
  当采用DO_DIRECT_IO访问IO数据时,要用Irp->MdlAddress获取输入参数,MmGetSystemAddressForMdl貌似是将一个ring3的地址映射到ring0的地址空间,不甚理解...
 直接看后面,分别判断IRP是Read or Write, 如果是Read则将前面初始化的那块内存空间的数据直接拷贝到输入的参数中(ring3地址),如果是Write则对ring3的地址直接进行copy,另外如果既不是DO_DIRECT_IO也不是DO_BUFFER_IO,那么就得先判断ring3的这块地址是否可读或者可写,其他的就没啥问题了。
ps1:
ProbeForRead(***),这类函数就是检查传入的地址参数是否合法,什么叫合法,第一,如果是只读地址,地址指向必须可读,如果是可写地址,可写的地址必须可写。第二,也是最重要,地址必须是应用层地址,原因很简单,如果我调用NtAllocateVirtualMemory里的&uExtraMem是内核地址,还让我调用成功,那我应用层就可以修改内核数据了!!!!

ps2:
关于ProbeForRead这里有个有意思的漏洞,大意是利用竞争条件在调用ProbeForRead和执行下面的操作之间,对ring3的地址进行修改,本来判断有效的地址,被修改后就无效了,可能造成内核堆栈溢出等...

原创粉丝点击