windows 驱动与内核调试 学习3
Posted 不会写代码的丝丽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了windows 驱动与内核调试 学习3相关的知识,希望对你有一定的参考价值。
前言
windows 驱动与内核调试 学习2 这篇介绍基础的驱动读写,我们看如下一例子
char szBuffer[1];
char* p= szBuffer-500;
//读取驱动在一个不确定的位置
if (ReadFile(hFile, p, 200, &drBytes, NULL))
std::cout << "read ok " << drBytes << " "<< szBuffer << std::endl;
else
std::cout << "read failure " << std::endl;
因为驱动运行在ring0
所以,可以随意写入存在一定安全隐患。另外还有线程安全问题如多线程对于同一IO
地址进行读写。为了解决相关问题Windows
为DEVICE_OBJECT
对象提供一个flags
字段可以增加io
方式应对相关问题
其中有两种方式分别为DO_DIRECT_IO
和DO_BUFFERED_IO
,两则使用场景不同。DO_BUFFERED_IO
适合在小数据传输中使用便捷,DO_DIRECT_IO
使用在大数据传输操作繁琐但效率高。
可参阅如下文档
windows驱动编程中的DO_DIRECT_IO和DO_BUFFERED_IO标志位
Methods for Accessing Data Buffers
上图你可以很明显看出两则差别
DO_BUFFERED_IO
这种模式会申请和用户空间等同大小的内存块在内核空间,如果是写入请求那么操作性还会申请内存块的同时会将原内存信息拷贝到新申请内存块中。
操作系统做的事情类似如下代码所示(例子来自参考链接并作稍作修改):
PVOID uva; // 用户模式传入缓冲区地址.可以理解调用驱动时传入的
ULONG length; // 用户模式传入缓冲区地址长度
//新建一块内存区域
PVOID sva; = ExAllocatePoolWithQuota(NonPagedPoolCacheAligned, length);
//如果写入模式,会进行内存区块拷贝
if (writing)
RtlCopyMemory(sva, uva, length);
//拷贝地址到特定字段
Irp->AssociatedIrp.SystemBuffer = sva;
//将长度拷贝IRP栈中
PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp);
if (reading)
stack->Parameters.Read.Length = length;
else
stack->Parameters.Write.Length = length;
<code to send and await IRP>
//如果时读请求,拷贝内存到用户模式传入的地址
if (reading)
RtlCopyMemory(uva, sva, length);
//内核地址释放
ExFreePool(sva);
案例:
//驱动被加载的时候会调用此函数
NTSTATUS
DriverEntry(
_In_ struct _DRIVER_OBJECT* DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
//如果你没有用到参数需要告诉系统。
UNREFERENCED_PARAMETER(RegistryPath);
//打印信息
DbgPrint("hello drive loaded");
//触发一个断点
//DbgBreakPoint();
//驱动卸载回调注册
DriverObject->DriverUnload = myUnload;
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl;
UNICODE_STRING ustrDevName;
RtlInitUnicodeString(&ustrDevName, L"\\\\Device\\\\MytestDriver");
PDEVICE_OBJECT pDevObj = NULL;
auto ret = IoCreateDevice(DriverObject, 0, &ustrDevName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevObj);
if (NT_SUCCESS(ret))
//指定IO模式
pDevObj->Flags |= DO_BUFFERED_IO;
DbgPrint("IoCreateDevice 成功 \\r\\n");
else
DbgPrint("IoCreateDevice 失败 %d\\r\\n", ret);
return STATUS_FAIL_CHECK;
UNICODE_STRING symbolDevName;
RtlInitUnicodeString(&symbolDevName, L"\\\\DosDevices\\\\MytestDriver");
ret = IoCreateSymbolicLink(&symbolDevName, &ustrDevName);
if (NT_SUCCESS(ret))
DbgPrint("IoCreateSymbolicLink 成功 \\r\\n");
else
DbgPrint("IoCreateSymbolicLink 失败%d\\r\\n", ret);
IoDeleteDevice(pDevObj);
return STATUS_FAIL_CHECK;
return STATUS_SUCCESS;
我们查看对应的写入函数
NTSTATUS
DispatchWrite(
_In_ struct _DEVICE_OBJECT* DeviceObject,
_Inout_ struct _IRP* Irp
)
DbgPrint("DispatchWrite");
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION pIrp = IoGetCurrentIrpStackLocation(Irp);
ULONG nLength = pIrp->Parameters.Write.Length;
DbgPrint("DispatchWrite UserBuffer:%p bytes:%d content %s", Irp->UserBuffer, nLength, Irp->UserBuffer);
int cLen = pIrp->Parameters.Write.Length;
DbgPrint("DispatchWrite SystemBuffer:%p bytes:%d content %s", Irp->AssociatedIrp.SystemBuffer, cLen, Irp->AssociatedIrp.SystemBuffer);
Irp->iostatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 6;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
读取函数
NTSTATUS
DispatchRead(
_In_ struct _DEVICE_OBJECT* DeviceObject,
_Inout_ struct _IRP* Irp
)
DbgPrint("DispatchRead");
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION pIrp = IoGetCurrentIrpStackLocation(Irp);
ULONG nLength = pIrp->Parameters.Read.Length;
//打印原始用户传入的缓存区
DbgPrint("DispatchRead UserBuffer:%p bytes:%d", Irp->UserBuffer, nLength);
int cLen = pIrp->Parameters.Read.Length;
DbgPrint("DispatchRead SystemBuffer:%p bytes:%d", Irp->AssociatedIrp.SystemBuffer, cLen);
memcpy(Irp->AssociatedIrp.SystemBuffer,"helloread",10);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 10;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
我们打印驱动触发读写后的样子
DO_DIRECT_IO
这个IO模式是使用用来传输大数据时使用。微软提供两种模式让我们使用:
这里用DMA方式做讲解。
我们首先需要明白DMA的概念,我们假设拷贝一个电影从u盘到内存,在之前远古时代需要CPU参与,在现代有DMA的硬件加持,cpu只需要传递需求给DMA即可。但是使用DMA需要固化虚拟地址和物理地址映射关系(比如虚拟地址0x111可以映射硬件内存地址是可以随时变化的)。
在我们使用个IO模式的时候,操作会构建一个叫MDL的宏,去表述相关结构
typedef struct _MDL
struct _MDL *Next;
CSHORT Size;
CSHORT MdlFlags;
struct _EPROCESS *Process;
PVOID MappedSystemVa;
PVOID StartVa;
ULONG ByteCount;
ULONG ByteOffset;
MDL, *PMDL;
这个结构存储的虚拟地址信息
并且在个数据结构内存后面紧贴一个物理地址映射数组(但是没有API让驱动开发者访问)。
我们不会驱动中直接碰触这个数据结构,而是用微软提供相关的宏。如下图
操作系统其实做了如下类似代码:
KPROCESSOR_MODE mode; // either KernelMode or UserMode
//创建MDL对象
PMDL mdl = IoAllocateMdl(uva, length, FALSE, TRUE, Irp);
//锁定虚拟地址和物理的映射 方便执行DMA
MmProbeAndLockPages(mdl, mode,
reading ? IoWriteAccess : IoReadAccess);
<code to send and await IRP>
//解决映射
MmUnlockPages(mdl);
//释放MDL
ExFreePool(mdl);
我们看看官方使用指南
案例:
NTSTATUS
DispatchRead(
_In_ struct _DEVICE_OBJECT* DeviceObject,
_Inout_ struct _IRP* Irp
)
DbgPrint("DispatchRead");
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION pIrp = IoGetCurrentIrpStackLocation(Irp);
ULONG nLength = pIrp->Parameters.Read.Length;
//打印原始用户传入的缓存区
DbgPrint("DispatchRead UserBuffer:%p bytes:%d", Irp->UserBuffer, nLength);
PVOID kenelP = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
DbgPrint("DispatchRead MmGetSystemAddressForMdlSafe:%p ", kenelP);
memcpy(kenelP, "helloread", 10);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 10;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
NTSTATUS
DispatchWrite(
_In_ struct _DEVICE_OBJECT* DeviceObject,
_Inout_ struct _IRP* Irp
)
DbgPrint("DispatchWrite");
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION pIrp = IoGetCurrentIrpStackLocation(Irp);
ULONG nLength = pIrp->Parameters.Write.Length;
DbgPrint("DispatchWrite UserBuffer:%p bytes:%d content %s", Irp->UserBuffer, nLength, Irp->UserBuffer);
PVOID kenelP = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
DbgPrint("DispatchWrite SystemBuffer:%p content %s", kenelP, kenelP);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 6;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
//驱动被加载的时候会调用此函数
NTSTATUS
DriverEntry(
_In_ struct _DRIVER_OBJECT* DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
//如果你没有用到参数需要告诉系统。
UNREFERENCED_PARAMETER(RegistryPath);
//打印信息
DbgPrint("hello drive loaded");
//触发一个断点
//DbgBreakPoint();
//驱动卸载回调注册
DriverObject->DriverUnload = myUnload;
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl;
UNICODE_STRING ustrDevName;
RtlInitUnicodeString(&ustrDevName, L"\\\\Device\\\\MytestDriver");
PDEVICE_OBJECT pDevObj = NULL;
auto ret = IoCreateDevice(DriverObject, 0, &ustrDevName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevObj);
if (NT_SUCCESS(ret))
//指定IO模式
pDevObj->Flags |= DO_DIRECT_IO;
DbgPrint("IoCreateDevice 成功 \\r\\n");
else
DbgPrint("IoCreateDevice 失败 %d\\r\\n", ret);
return STATUS_FAIL_CHECK;
UNICODE_STRING symbolDevName;
RtlInitUnicodeString(&symbolDevName, L"\\\\DosDevices\\\\MytestDriver");
ret = IoCreateSymbolicLink(&symbolDevName, &ustrDevName);
if (NT_SUCCESS(ret))
DbgPrint("IoCreateSymbolicLink 成功 \\r\\n");
else
DbgPrint("IoCreateSymbolicLink 失败%d\\r\\n", ret);
IoDeleteDevice(pDevObj);
return STATUS_FAIL_CHECK;
return STATUS_SUCCESS;
以上是关于windows 驱动与内核调试 学习3的主要内容,如果未能解决你的问题,请参考以下文章