异想天开

What's the true meaning of light, Could you tell me why

windows NT驱动demo

日期:2017-12-16 18:08:36
  
最后更新日期:2017-12-17 08:46:38
捣鼓一个虚拟盘功能,类似普通的磁盘那样挂载在电脑,对用户透明为一个可见盘符,该虚拟盘只是一个文件或以后其他设备。查询资料得知,可以用一个windows内核驱动实现该伪装,挂载为盘符,优点在于透明性好,缺点在于内核驱动,程序没写好的话,就容易蓝屏。本文为对内核驱动初学的笔记。
主要实现一个类似echo的功能demo,用户态程序打开设备,读设备,不论读任何内容,设备返回10个大写A的字符串。
1. 驱动开发的入口函数为DriverEntry。该函数类似c语言开发的main函数一样。定义如下:
[code lang="cpp"]
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegistryPath)
[/code]
第一个参数为驱动对象指针,还有一个设备对象。开发的驱动加载到内核中,只有一个驱动对象,但可以打开若干设备对象。比如硬盘驱动,可以挂载多块硬盘设备。
驱动程序运行在内核态,平常普通的程序是运行在用户态,用户态与内核态度交互的媒介为IRP(io request package)。操作系统把驱动需要用到的io,抽象为几种操作:
IRP_MJ_CREATE
IRP_MJ_CLOSE
IRP_MJ_WRITE
IRP_MJ_READ
还有其他IRP_MJ_DEVICE_CONTROL等。这里没有全部列举出来所有windows驱动支持注册的操作,仅列出一些名字比较常见的。而这些操作的参数,就封装在这个irp这个结构体中。DriverEntry函数里面的基本套路就是创建设备对象,驱动对象由操作系统创建了,注册一些执行这些操作的分发函数。还有一个注册一个驱动卸载的函数来关闭打开的设备对象等。驱动对象结构体有一个MajorFunction数组,里面就是各自类型的分发函数。如下可以注册一个设备创建时的分发函数。
[code lang="cpp"]
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchRoutine;
[/code]
一个简单的例子:
[code lang="cpp"]
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegistryPath)//驱动入口
{
NTSTATUS status;
DbgPrint("DriverEntry: %s \r\n", pRegistryPath);
pDriverObject->DriverUnload = DriverUnload;//注册卸载函数
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchRoutine;//注册派遣函数
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchRoutine;
pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRoutine;

status = CreateDevice(pDriverObject);
return status;
}
[/code]
2. 创建设备对象
驱动开发人员可以自定义设备对象的扩展,来保存一些值。本例定义了三个变量记录设备对象指针,设备名和符号链接名。
[code lang="cpp"]
typedef struct _DEVICE_EXTENSION {
PDEVICE_OBJECT pDevice;
UNICODE_STRING ustrDeviceName; //设备名称
UNICODE_STRING ustrSymLinkName; //符号链接名
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
[/code]
这里有一点需要注意的就是设备名和符号链接名。初始化unicodestring。unicodestring是一个结构体,不是wchar_t。故需要调用RtlInitUnicodeString函数初始化。
[code lang="cpp"]
UNICODE_STRING devName;
RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
[/code]
符号链接名字可以根据这个例子来推断:
驱动中的符号链接名 L"\\??\\HelloDDK", 用户态用"\\\\.\\HelloDDK"来访问。前面的"\\\\.\\"部分进入驱动会被替换为L"\\??\\"。

创建设备时,这里需要选择FILE_DEVICE_UNKNOWN,选FILE_DEVICE_DISK的套路可能复杂点,就先注释掉了。
[code lang="cpp"]
status = IoCreateDevice( pDriverObject,
sizeof(DEVICE_EXTENSION),
&devName,
FILE_DEVICE_UNKNOWN,//此种设备为独占设备FILE_DEVICE_DISK
FILE_DEVICE_SECURE_OPEN | FILE_READ_ONLY_DEVICE , TRUE,
&pDevObj );
[/code]
我们在DispatchRoutine分发函数,当识别MajorFunction为IRP_MJ_READ直接返回10个字节全是A的字符串。
[code lang="cpp"]
case IRP_MJ_READ:
{
strcpy(majorfun, "IRP_MJ_READ");
ulReadLength = irpSp->Parameters.Read.Length;

//完成IRP
pIrp->IoStatus.Status = status;

//设置IRP操作了多少字节
pIrp->IoStatus.Information = ulReadLength;

//设置内核模式下的缓冲区
memset(pIrp->AssociatedIrp.SystemBuffer,0xAA,ulReadLength);

//处理IRP
break;
}
[/code]
3. 用户态程序。该用户态程序主要打开设备,然后ReadFile,相当于是发起一个IRP_MJ_READ的IRP。
[code lang="cpp"]
HANDLE hFILE = CreateFile("\\\\.\\HelloDDK",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hFILE==INVALID_HANDLE_VALUE)
{
printf("CreateFile error: %d \n", GetLastError());
return 0;
}

UCHAR buffer[10];
ULONG ulRead;

//对设备读写
BOOL bRet = ReadFile(hFILE,buffer,10,&ulRead,NULL);

if(bRet)
{
printf("Read %d bytes:",ulRead);
for(int i=0;i<(int)ulRead;i++)
{
printf("%02X ",buffer[i]);
}
}
[/code]

4.测试时建议下载驱动开发套装。
debugview - 查看驱动日志
drivermonitor - 加载驱动
winobj - 查看设备对象
驱动开发工具合集: https://pan.baidu.com/s/1nvwx03v

完整代码github地址:
https://github.com/acjog/nt-drive