这两天怎么老想玩DriverWorks!!!
今天抽了一个小时写个小驱动练习一下KIrp和KMemory配合访问三种IO方式中的数据.KIrp是对IRP请求对象的包装,它有很多成员函数用于操作IRP结构.
1.Buffered IO:
在Buffered IO方式中IO管理器会分配一块堆内存然后把用户态缓冲区数据copy进来再传给驱动程序,而输出时IO管理器会把堆内存中的数据copy回用户态缓冲区.
我们使用下面这样的代码来得到输入/输出缓冲区和大小
KIrp I;
....
ULONG inputSize = I.IoctlInputBufferSize();
ULONG outputSize = I.IoctlOutputBufferSize();
PVOID inputBuffer = I.IoctlBuffer();
PVOID outputBuffer = I.IoctlBuffer();
一目了然吧!
我想要介绍一下这几个函数,但你实际上可以跳过下面的一小段因为用DriverWorks根本用不着知道这些细节.
IoctlBuffer,IoctlInputBufferSize,IoctlOutputBufferSize函数实现非常简单:
inline PVOID& KIrp::IoctlBuffer(void)
{
return m_Irp->AssociatedIrp.SystemBuffer;
}
inline ULONG& KIrp::IoctlOutputBufferSize(EStackLocation s)
{
ValidateStackLocation(s);
if (s == CURRENT)
return IoGetCurrentIrpStackLocation(m_Irp)->
Parameters.DeviceIoControl.OutputBufferLength;
else
return IoGetNextIrpStackLocation(m_Irp)->
Parameters.DeviceIoControl.OutputBufferLength;
}
inline ULONG& KIrp::IoctlInputBufferSize(EStackLocation s)
{
ValidateStackLocation(s);
if (s == CURRENT)
return IoGetCurrentIrpStackLocation(m_Irp)->
Parameters.DeviceIoControl.InputBufferLength;
else
return IoGetNextIrpStackLocation(m_Irp)->
Parameters.DeviceIoControl.InputBufferLength;
}
在IoctlOutputBufferSize和IoctlInputBufferSize中S有一个默认参数CURRENT,上面的代码就是这样调用它们的.可以看到其实是对IO_STACK_LOCATION结构直接的操作.
2.Direct IO:
Direct IO方式中IO管理器分将用户态缓冲区映射到核心态(地址不一样,但其实是一块内存),并锁定内存不要分页机制将内存交换到外存中.然后IO管理器将缓冲区在核心态的的地址传给驱动程序.Direct IO的分成METHOD_IN_DIRECT和METHOD_OUT_DIRECT
2.1 在METHOD_IN_DIRECT中我们使用这样的代码来访问缓冲区及大小
KIrp I;
....
KMemory Mem(I.Mdl());
PUCHAR pBuffer = (PUCHAR) Mem.MapToSystemSpace();
ULONG writeSize = I.WriteSize();
ULONG bytesSent = 0;
2.2 在METHOD_OUT_DIRECT中要像这样来访问缓冲区及大小
KIrp I;
....
KMemory Mem(I.Mdl());
PUCHAR pBuffer = (PUCHAR) Mem.MapToSystemSpace();
ULONG readSize = I.ReadSize();
ULONG bytesRead = 0;
I.Mdl函数是这样实现的:
inline PMDL& KIrp::Mdl(void)
{
return m_Irp->MdlAddress;
}
它直接返回IRP结构的MdlAddress字段,而MapToSystemSpace的实现代码是这样的:
inline PVOID KMemory::MapToSystemSpace(void)
{
return GetSystemAddressForMdl(m_pMdl);
}
它调用下面这个函数,这里我不太明白为什么不直接调用MmGetSystemAddressForMdl
inline PVOID GetSystemAddressForMdl(PMDL pMdl)
{
CSHORT canFail; // original fail flag in MDL
if(!(pMdl->MdlFlags &
(MDL_MAPPED_TO_SYSTEM_VA | MDL_SOURCE_IS_NONPAGED_POOL))) {
// get the current fail flag
canFail = (CSHORT)(pMdl->MdlFlags & MDL_MAPPING_CAN_FAIL);
// set 'mapping can fail' so we don't bugcheck
// if MmMapLockedPages fails
pMdl->MdlFlags |= MDL_MAPPING_CAN_FAIL;
// try to map the buffer
pMdl->MappedSystemVa =
(PVOID)(((ULONG_PTR)MmMapLockedPages(pMdl, KernelMode)) |
MmGetMdlByteOffset(pMdl));
// set the original flag back in the MDL
if(!canFail) {
pMdl->MdlFlags &= ~MDL_MAPPING_CAN_FAIL;
}
}
return pMdl->MappedSystemVa;
}
3 Neither IO:
在Neither IO,IO管理器直接把用户态的缓冲区地址和大小传给驱动,不做任何处理.这是很危险的,通常情况下,我们需要写一些小的助手驱动以进入Ring0或是调用一些底层功能,如果可以肯定驱动的派遣函是运行在被动级并且在我们自己指定程序的进程上下文里,那么用用这种方式也无所谓.除此以外尽可能不用这样的IO方式.通常需要使用这样的代码来访问缓冲区和大小:
KIrp I;
....
ULONG inputSize = I.IoctlInputBufferSize();
ULONG outputSize = I.IoctlOutputBufferSize();
PVOID inputBuffer = I.IoctlType3InputBuffer();
PVOID outputBuffer = I.UserBuffer();
IoctlType3InputBuffer和UserBuffer的实现代码:
inline PVOID& KIrp::IoctlType3InputBuffer(EStackLocation s)
{
ValidateStackLocation(s);
if (s == CURRENT)
return IoGetCurrentIrpStackLocation(m_Irp)->
Parameters.DeviceIoControl.Type3InputBuffer;
else
return IoGetNextIrpStackLocation(m_Irp)->
Parameters.DeviceIoControl.Type3InputBuffer;
}
inline PVOID& KIrp::UserBuffer(void)
{
return m_Irp->UserBuffer;
}
4. 现在可以总结一下,根据这些我们可以学到在DDK层面上应该怎么操作这些IO方式中的缓冲区:
4.1 在驱动的派遣函数中可以使用IoGetCurrentIrpStackLocation得到IO_STACK_LOCATION可以根据它的Control字段确定是什么派遣函数(因为可以将多个派遣函数地址指向一个派遣函数来处理多个请求).在Control等于IRP_MJ_DEVICE_CONTROL时来要根据DeviceIoControl的子字段IoControlCode来确定是什么控制代码.
4.2 对于Buffered IO和Neither IO可以使用它的DeviceIoControl字段和三个子字段来访问缓冲区和大小参数:
typedef struct _IO_STACK_LOCATION {
....
union {
...
struct {
ULONG OutputBufferLength;
ULONG InputBufferLength;
ULONG IoControlCode;
PVOID Type3InputBuffer;
} DeviceIoControl;
...
}
....
} IO_STACK_LOCATION, *PIO_STACK_LOCATION;
4.3 对于Direct IO则要使用MmGetSystemAddressForMdl来将IRP结构的MdlAddress(内存描述表)字段映射到内核地址空间并锁定,得到一个内核空间地址才能进一步操作.Direct IO方式中的输入输出大小参数由IO_STACK_LOCATION的Read.Length和Write.Length指示
typedef struct _IO_STACK_LOCATION {
....
union {
...
struct {
ULONG Length;
ULONG Key;
LARGE_INTEGER ByteOffset;
} Read;
struct {
ULONG Length;
ULONG Key;
LARGE_INTEGER ByteOffset;
} Write;
...
}
....
} IO_STACK_LOCATION, *PIO_STACK_LOCATION;
说不看库源代码的又忍不住开始分析,呵呵!其实可以把DriverWorks的对象类搞清楚怎么用就可以了.如果是想顺便把自己的DDK开发能力也提高一下的话,库的源代码也可以参考.
有了昨天的DriverWorks基本介绍,我想下面的代码应该不是很难懂吧!
//Interface.h
#ifndef __INTERFACE_H__
#define __INTERFACE_H__
#define DEVICE_NAME L"IoDemoDevice"
#define FILE_DEVICE_IODEMO 0x8000
#define IOCTL_DO_BUFFERED_IO \
CTL_CODE(FILE_DEVICE_IODEMO,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_DO_DIRECT_IN \
CTL_CODE(FILE_DEVICE_IODEMO,0x801,METHOD_IN_DIRECT,FILE_ANY_ACCESS)
#define IOCTL_DO_DIRECT_OUT \
CTL_CODE(FILE_DEVICE_IODEMO,0x802,METHOD_OUT_DIRECT,FILE_ANY_ACCESS)
#define IOCTL_DO_NEITHER_IO \
CTL_CODE(FILE_DEVICE_IODEMO,0x803,METHOD_NEITHER,FILE_ANY_ACCESS)
#endif
//IODemo.cpp
#define VDW_MAIN
#define DRIVER_FUNCTION_UNLOAD
#define DRIVER_FUNCTION_CREATE
#define DRIVER_FUNCTION_CLOSE
#define DRIVER_FUNCTION_CLEANUP
#define DRIVER_FUNCTION_DEVICE_CONTROL
#include "vdw.h"
#include "Interface.h"
class IoDemoDriver:public KDriver
{
SAFE_DESTRUCTORS;
public:
virtual NTSTATUS DriverEntry(IN PUNICODE_STRING RegistryPath);
virtual VOID Unload();
};
DECLARE_DRIVER_CLASS(IoDemoDriver,NULL)
class IoDemoDevice:public KDevice
{
SAFE_DESTRUCTORS;
public:
IoDemoDevice();
DEVMEMBER_DISPATCHERS;
};
IoDemoDevice::IoDemoDevice():KDevice()
{
if (!NT_SUCCESS(m_ConstructorStatus))
{
DbgPrint(__FUNCTION__": Failed to create device MyDevice");
return;
}
}
NTSTATUS IoDemoDevice::Create(KIrp I)
{
DbgPrint(__FUNCTION__":IRP 0x%08X\n",I);
I.Information()=0;
I.Complete(STATUS_SUCCESS);
return STATUS_SUCCESS;
}
NTSTATUS IoDemoDevice::Close(KIrp I)
{
DbgPrint(__FUNCTION__":IRP 0x%08X\n",I);
I.Information()=0;
I.Complete(STATUS_SUCCESS);
return STATUS_SUCCESS;
}
NTSTATUS IoDemoDevice::CleanUp(KIrp I)
{
DbgPrint(__FUNCTION__":IRP 0x%08X\n",I);
I.Information()=0;
I.Complete(STATUS_SUCCESS);
return STATUS_SUCCESS;
}
NTSTATUS IoDemoDevice::DeviceControl(KIrp I)
{
DbgPrint(__FUNCTION__":IRP 0x%08X\n",I);
switch(I.IoctlCode())
{
case IOCTL_DO_BUFFERED_IO:
{
DbgPrint(__FUNCTION__":IOCTL_DO_BUFFERED_IO\n",I);
ULONG inputSize = I.IoctlInputBufferSize();
ULONG outputSize = I.IoctlOutputBufferSize();
PVOID inputBuffer = I.IoctlBuffer();
PVOID outputBuffer = I.IoctlBuffer();
//显示出传进来的字符串
if(inputSize)
DbgPrint("inputBuffer:%s",(char*)inputBuffer);
//返回一个字符串
char chDoBufferedIO[]="DO_BUFFERED_IO";
strncpy((char*)outputBuffer,chDoBufferedIO,outputSize);
I.Information()=strlen(chDoBufferedIO);
}
break;
case IOCTL_DO_DIRECT_IN:
{
DbgPrint(__FUNCTION__":IOCTL_DO_DIRECT_IN\n",I);
KMemory Mem(I.Mdl());
PUCHAR pBuffer = (PUCHAR) Mem.MapToSystemSpace();
ULONG writeSize = I.WriteSize();
ULONG bytesSent = 0;
if(writeSize)
DbgPrint("pBuffer:%s",(char*)pBuffer);
I.Information()=0;
}
break;
case IOCTL_DO_DIRECT_OUT:
{
DbgPrint(__FUNCTION__":IOCTL_DO_DIRECT_OUT\n",I);
KMemory Mem(I.Mdl());
PUCHAR pBuffer = (PUCHAR) Mem.MapToSystemSpace();
ULONG readSize = I.ReadSize();
ULONG bytesRead = 0;
if(readSize)
{
char chDirectOut[]="DO_DIRECT_OUT";
strncpy((char*)pBuffer,chDirectOut,readSize);
I.Information()=strlen(chDirectOut);
}
}
break;
case IOCTL_DO_NEITHER_IO:
{
DbgPrint(__FUNCTION__":IOCTL_DO_NEITHER_IO\n",I);
ULONG inputSize = I.IoctlInputBufferSize();
ULONG outputSize = I.IoctlOutputBufferSize();
PVOID inputBuffer = I.IoctlType3InputBuffer();
PVOID outputBuffer = I.UserBuffer();
//显示出传进来的字符串
if(inputSize)
DbgPrint("inputBuffer:%s",(char*)inputBuffer);
//返回一个字符串
char chDoNeitherIo[]="DO_NEITHER_IO";
strncpy((char*)outputBuffer,chDoNeitherIo,outputSize);
I.Information()=strlen(chDoNeitherIo);
}
break;
default:
I.Information()=0;
I.Complete(STATUS_INVALID_DEVICE_REQUEST);
return STATUS_INVALID_DEVICE_REQUEST;
}
I.Complete(STATUS_SUCCESS);
return STATUS_SUCCESS;
}
NTSTATUS IoDemoDriver::DriverEntry(IN PUNICODE_STRING RegistryPath)
{
NTSTATUS status = STATUS_SUCCESS;
DbgPrint(__FUNCTION__":RegistryPath:%S\n",RegistryPath->Buffer);
IoDemoDevice* pDevice = new (
DEVICE_NAME,
FILE_DEVICE_UNKNOWN,
DEVICE_NAME,
0,
DO_DIRECT_IO
)
IoDemoDevice();
if (pDevice == NULL)
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
else
{
status = pDevice->ConstructorStatus();
if (!NT_SUCCESS(status))
{
delete pDevice;
}
else
DbgPrint(__FUNCTION__":Created IoDemoDevice!\n");
}
return status;
}
VOID IoDemoDriver::Unload()
{
DbgPrint(__FUNCTION__);
KDriver::Unload();
}
最后可以用我的小工具测试一下,Load驱动后可以点Content按钮编辑数据包,也可以查看驱动返回的数据,显示方式是十六进制,注意根据IO控制代码的定义选择不同的Method,DeviceId和ControlCode组合方式.个人感觉这个小工具还是比较好用的,呵呵!