转自:http://www.ccgcn.com/bbs/redirect.php?tid=974&goto=lastpost
FileDisk.h
FileDisk 是把一个镜像文件Mount成一个卷
fileDisk.h中包含以下内容
1.OPEN_FILE_INFORMATION结构
2.定义了几个宏:
fileDisk.h中包含以下内容
1.OPEN_FILE_INFORMATION结构
复制内容到剪贴板
这个结构表明了镜像文件的几个必要信息。(查询信息操作就是返回这个结构.)代码:
typedef struct _OPEN_FILE_INFORMATION {
LARGE_INTEGER FileSize; //镜像文件的大小,并不一定是到文件尾。
BOOLEAN ReadOnly; //是否只读
USHORT FileNameLength; //文件名长度
UCHAR FileName[1]; //文件名
} OPEN_FILE_INFORMATION, *POPEN_FILE_INFORMATION;
2.定义了几个宏:
复制内容到剪贴板
实际上就是:
代码:
#define DEVICE_BASE_NAME _T("//FileDisk")
#define DEVICE_DIR_NAME _T("//Device") DEVICE_BASE_NAME
#define DEVICE_NAME_PREFIX DEVICE_DIR_NAME DEVICE_BASE_NAME
复制内容到剪贴板
3.3个IO控制码.
代码:
#define DEVICE_BASE_NAME _T("//FileDisk")
#define DEVICE_DIR_NAME _T("//Device//FileDisk")
#define DEVICE_NAME_PREFIX _T("//Device//FileDisk//FileDisk")
复制内容到剪贴板
其中CTL_CODE就又是一个宏:
代码:
#define FILE_DEVICE_FILE_DISK 0x8000
#define IOCTL_FILE_DISK_OPEN_FILE CTL_CODE(FILE_DEVICE_FILE_DISK, 0x800, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define IOCTL_FILE_DISK_CLOSE_FILE CTL_CODE(FILE_DEVICE_FILE_DISK, 0x801, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define IOCTL_FILE_DISK_QUERY_FILE CTL_CODE(FILE_DEVICE_FILE_DISK, 0x802, METHOD_BUFFERED, FILE_READ_ACCESS)
复制内容到剪贴板
通过这个CTL_CODE得到的一是一个32位的IO控制码。这个码其实包含了IO操作的几个属性。代码:
#define CTL_CODE( DeviceType, Function, Method, Access ) ( ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) )
应用层filedisk.c
1.FileDiskSyntax(void)函数表明了我们应该如何使用FileDisk./mount /umount /status 三个参数分别代表用户可能用到的三个功能.这从字面意思就可以理解。
2.main(int argc, char* argv[])函数所做的事情:
从命令行取出以下参数。
A.(每次运行时,)初始化一个OpenFileInformation结构的各字段,在计算FileSize字段时考虑得比较周到,支持"G/M/k"等容量单位.这里我们可以对代码作相关修改,以让程序能适应与大小无关的参数.
B.CdImage、DriveLetter变量分别表示是否作为一个光盘挂载,和挂载之后期望的盘符。
C.DeviceNumber我暂时的理解是设备的编号.
如果参数无误,便调用:
FileDiskMount(DeviceNumber, OpenFileInformation, DriveLetter, CdImage);
FileDiskUmount(DriveLetter);
FileDiskStatus(DriveLetter);
反之,如参数有误,便显示FileDisk的用法.
3.FileDiskMount(DeviceNumber, OpenFileInformation, DriveLetter, CdImage);
从函数名字上看,这个函数在做什么事,就很明显,细看一下发现:
以 OPEN_EXISTING 标记打开盘符对应的卷,如果打开了,说明已经盘符已经被占用,就显示出错信息并退出.
如果该盘符未被占用,就创建一个符号连接,并再次打开卷,然后向该卷设备发送 IOCTL_FILE_DISK_OPEN_FILE IO控制码,同时传递 OpenFileInformation 结构给设备.
这里我觉得值得一提的是删除符号连接的方法:
DefineDosDevice(DDD_REMOVE_DEFINITION, &VolumeName[4], NULL);
DefineDosDevice这个函数还可以删除符号连接!(DefineDosDevice这个函数是供用户模式程序调用的,在内核模式则应该用IoCreateSymbolicLink函数.)
4.FileDiskUmount(char DriveLetter);
发现主要有以下动作:
打开盘符所对应的卷设备
向卷设备发 FSCTL_LOCK_VOLUME 命令,锁定该卷(可能是让该卷设备不再接受其余的命令)。
向卷设备发 IOCTL_FILE_DISK_CLOSE_FILE 命令。
向卷设备发 FSCTL_DISMOUNT_VOLUME 命令。
向卷设备发 FSCTL_UNLOCK_VOLUME 命令.
DefineDosDevice (带参数DDD_REMOVE_DEFINITION) 删除对应的符号链接。
5.FileDiskStatus(char DriveLetter);
就是向卷设备发 IOCTL_FILE_DISK_QUERY_FILE 命令,驱动应返回一个 OPEN_FILE_INFORMATION 结构.把这里的结构里的信息打印出来就OK了.
2.main(int argc, char* argv[])函数所做的事情:
从命令行取出以下参数。
A.(每次运行时,)初始化一个OpenFileInformation结构的各字段,在计算FileSize字段时考虑得比较周到,支持"G/M/k"等容量单位.这里我们可以对代码作相关修改,以让程序能适应与大小无关的参数.
B.CdImage、DriveLetter变量分别表示是否作为一个光盘挂载,和挂载之后期望的盘符。
C.DeviceNumber我暂时的理解是设备的编号.
如果参数无误,便调用:
FileDiskMount(DeviceNumber, OpenFileInformation, DriveLetter, CdImage);
FileDiskUmount(DriveLetter);
FileDiskStatus(DriveLetter);
反之,如参数有误,便显示FileDisk的用法.
3.FileDiskMount(DeviceNumber, OpenFileInformation, DriveLetter, CdImage);
从函数名字上看,这个函数在做什么事,就很明显,细看一下发现:
以 OPEN_EXISTING 标记打开盘符对应的卷,如果打开了,说明已经盘符已经被占用,就显示出错信息并退出.
如果该盘符未被占用,就创建一个符号连接,并再次打开卷,然后向该卷设备发送 IOCTL_FILE_DISK_OPEN_FILE IO控制码,同时传递 OpenFileInformation 结构给设备.
这里我觉得值得一提的是删除符号连接的方法:
DefineDosDevice(DDD_REMOVE_DEFINITION, &VolumeName[4], NULL);
DefineDosDevice这个函数还可以删除符号连接!(DefineDosDevice这个函数是供用户模式程序调用的,在内核模式则应该用IoCreateSymbolicLink函数.)
4.FileDiskUmount(char DriveLetter);
发现主要有以下动作:
打开盘符所对应的卷设备
向卷设备发 FSCTL_LOCK_VOLUME 命令,锁定该卷(可能是让该卷设备不再接受其余的命令)。
向卷设备发 IOCTL_FILE_DISK_CLOSE_FILE 命令。
向卷设备发 FSCTL_DISMOUNT_VOLUME 命令。
向卷设备发 FSCTL_UNLOCK_VOLUME 命令.
DefineDosDevice (带参数DDD_REMOVE_DEFINITION) 删除对应的符号链接。
5.FileDiskStatus(char DriveLetter);
就是向卷设备发 IOCTL_FILE_DISK_QUERY_FILE 命令,驱动应返回一个 OPEN_FILE_INFORMATION 结构.把这里的结构里的信息打印出来就OK了.
内核层filedisk.c
1.NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)函数
从注册表中得到 设备的数量 并放到n_devices中.
因为期望的设备名是_T("//Device//FileDisk//FileDiskCdX")或_T("//Device//FileDisk//FileDiskX").
当设备目录_T("//Device//FileDisk")不存在的时候,创建_T("//Device//FileDisk//FileDiskCdX")设备会失败.所以应该先创建这个"对象目录".
创建对象目录对象,然后调用 FileDiskCreateDevice 创建 n_devices 个FILE_DEVICE_DISK 和 n_devices 个FILE_DEVICE_CDROM.显然 FileDiskCreateDevice 很值得一看.
最后,指定几个派谴例程和DriverUnload例程.
2.NTSTATUS FileDiskCreateDevice (IN PDRIVER_OBJECT DriverObject,IN ULONG Number,IN DEVICE_TYPE DeviceType)函数
调用IoCreateDevice创建一个设备名为device_name[_T("//Device//FileDisk//FileDiskCdX")或_T("//Device//FileDisk//FileDiskX")],设备类型为FILE_DEVICE_DISK/FILE_DEVICE_CD_ROM的设备.
指定device_object所指设备对象的Flags字段(一组标志位)值为DO_DIRECT_IO,即读写操作使用直接方式(内存描述符表)访问用户模式数据.
指定device_object设备扩展的media_in_device字段为FALSE,即媒体文件(即前面所说的镜像文件)还没有打开.
如果设备类型是FILE_DEVICE_CD_ROM就再设定Characteristics标志位(描述设备的可选特征)为只读设备,并设定设备扩展中的相应字段(read_only)的值为TRUE.
初始化链表头(设备扩展中的).并调用KeInitializeSpinLock初始化一个自旋锁.(方便以后,当代码运行在低于或等于DISPATCH_LEVEL级上时获取这个锁,并执行需要保护的代码,最后释放自旋锁.)在这里这个链表是用于存放IRP列表.
初始化一个内核事件为非信号态.(后面创建的系统线程运行的第一件事就是等这个内核事件变成信号态).
初始化设备扩展中的terminate_thread字段值为FALSE,(后面创建的系统线程运行时将检查这个字段,以决定是否退出线程).
调用PsCreateSystemThread创建系统线程FileDiskThread(device_object);
调用用ObReferenceObjectByHandle就是在权限可以的情况下,由系统线程对象句柄得到系统线程对象的指针.
如果不能得到系统线程指针将让其退出.并删除创建的设备.
关闭系统线程句柄.并不是终止系统线程的运行.
3.FileDiskUnload (IN PDRIVER_OBJECT DriverObject)调用FileDiskDeleteDevice(device_object)删除所有设备对象.
4.PDEVICE_OBJECT FileDiskDeleteDevice (IN PDEVICE_OBJECT DeviceObject);
让当前的设备对象对应的系统线程退出,等待线程关闭.
删除设备扩展对象中的对应的线程引用.
如果device_extension->security_client_context不为空,减少引用计数,并释放内存.
删除当前的设备对象.
返回链表中的下一个设备对象.
从注册表中得到 设备的数量 并放到n_devices中.
因为期望的设备名是_T("//Device//FileDisk//FileDiskCdX")或_T("//Device//FileDisk//FileDiskX").
当设备目录_T("//Device//FileDisk")不存在的时候,创建_T("//Device//FileDisk//FileDiskCdX")设备会失败.所以应该先创建这个"对象目录".
创建对象目录对象,然后调用 FileDiskCreateDevice 创建 n_devices 个FILE_DEVICE_DISK 和 n_devices 个FILE_DEVICE_CDROM.显然 FileDiskCreateDevice 很值得一看.
最后,指定几个派谴例程和DriverUnload例程.
2.NTSTATUS FileDiskCreateDevice (IN PDRIVER_OBJECT DriverObject,IN ULONG Number,IN DEVICE_TYPE DeviceType)函数
调用IoCreateDevice创建一个设备名为device_name[_T("//Device//FileDisk//FileDiskCdX")或_T("//Device//FileDisk//FileDiskX")],设备类型为FILE_DEVICE_DISK/FILE_DEVICE_CD_ROM的设备.
指定device_object所指设备对象的Flags字段(一组标志位)值为DO_DIRECT_IO,即读写操作使用直接方式(内存描述符表)访问用户模式数据.
指定device_object设备扩展的media_in_device字段为FALSE,即媒体文件(即前面所说的镜像文件)还没有打开.
如果设备类型是FILE_DEVICE_CD_ROM就再设定Characteristics标志位(描述设备的可选特征)为只读设备,并设定设备扩展中的相应字段(read_only)的值为TRUE.
初始化链表头(设备扩展中的).并调用KeInitializeSpinLock初始化一个自旋锁.(方便以后,当代码运行在低于或等于DISPATCH_LEVEL级上时获取这个锁,并执行需要保护的代码,最后释放自旋锁.)在这里这个链表是用于存放IRP列表.
初始化一个内核事件为非信号态.(后面创建的系统线程运行的第一件事就是等这个内核事件变成信号态).
初始化设备扩展中的terminate_thread字段值为FALSE,(后面创建的系统线程运行时将检查这个字段,以决定是否退出线程).
调用PsCreateSystemThread创建系统线程FileDiskThread(device_object);
调用用ObReferenceObjectByHandle就是在权限可以的情况下,由系统线程对象句柄得到系统线程对象的指针.
如果不能得到系统线程指针将让其退出.并删除创建的设备.
关闭系统线程句柄.并不是终止系统线程的运行.
3.FileDiskUnload (IN PDRIVER_OBJECT DriverObject)调用FileDiskDeleteDevice(device_object)删除所有设备对象.
4.PDEVICE_OBJECT FileDiskDeleteDevice (IN PDEVICE_OBJECT DeviceObject);
让当前的设备对象对应的系统线程退出,等待线程关闭.
删除设备扩展对象中的对应的线程引用.
如果device_extension->security_client_context不为空,减少引用计数,并释放内存.
删除当前的设备对象.
返回链表中的下一个设备对象.
5.FileDiskCreateClose (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
设定IoStatus的值,并完成该IRP
6.FileDiskReadWrite ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
如果没有媒体文件就完成该IRP,并返回STATUS_NO_MEDIA_IN_DEVICE.
如果读0字节的数据,就完成该IRP.
否则,将该IRP插入队列,并返回STATUS_PENDING.
7.FileDiskDeviceControl (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
如果还没有打开文件,并且IoControlCode不为 IOCTL_FILE_DISK_OPEN_FILE
完成该Irp 并且 返回状态 STATUS_NO_MEDIA_IN_DEVICE。
否则IoControlCode的处理情况就是:
A.IOCTL_FILE_DISK_OPEN_FILE: //打开文件 应用 /mount 命令发送这个Irp
如果媒体文件已经打开了,status 赋值为 STATUS_INVALID_DEVICE_REQUEST.
检验是否传进来的缓冲区大小,如果太少了,status 就赋值为 STATUS_INVALID_PARAMETER.
然后模拟一个客户环境,并放到device_extension中.
把IRP放入队列,status 赋值为 STATUS_PENDING.(交给所创建的系统线程处理.)
B.IOCTL_FILE_DISK_CLOSE_FILE: //关闭文件
把IRP放入队列,status 赋值为 STATUS_PENDING.(交给所创建的系统线程处理.)
C.以下这几个IoControlCode的处理情况是根据device_extension的信息返回相应状态:
IOCTL_DISK_CHECK_VERIFY
IOCTL_CDROM_CHECK_VERIFY
IOCTL_STORAGE_CHECK_VERIFY
IOCTL_STORAGE_CHECK_VERIFY2
IOCTL_DISK_IS_WRITABLE
IOCTL_DISK_MEDIA_REMOVAL
IOCTL_STORAGE_MEDIA_REMOVAL
IOCTL_DISK_SET_PARTITION_INFO
status值都不是STATUS_PENDING.
D.在返回的缓冲区容量大小足够的情况下,以下这几个IoControlCode的处理是:
IOCTL_FILE_DISK_QUERY_FILE //查询状态
IOCTL_DISK_GET_DRIVE_GEOMETRY
IOCTL_CDROM_GET_DRIVE_GEOMETRY //得到盘的容量大小
IOCTL_DISK_GET_LENGTH_INFO
IOCTL_DISK_GET_PARTITION_INFO
IOCTL_DISK_GET_PARTITION_INFO_EX
IOCTL_CDROM_READ_TOC
IOCTL_DISK_VERIFY
Irp->AssociatedIrp.SystemBuffer指向要返回结构。并为 status 赋值 STATUS_SUCCESS;
Irp->IoStatus.Information 赋值为返回结构的大小.
E.如果是其他的IoControlCode就返回STATUS_INVALID_DEVICE_REQUEST;
F.如果在以上处理时status不是STATUS_PENDING,就完成该IRP.
设定IoStatus的值,并完成该IRP
6.FileDiskReadWrite ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
如果没有媒体文件就完成该IRP,并返回STATUS_NO_MEDIA_IN_DEVICE.
如果读0字节的数据,就完成该IRP.
否则,将该IRP插入队列,并返回STATUS_PENDING.
7.FileDiskDeviceControl (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
如果还没有打开文件,并且IoControlCode不为 IOCTL_FILE_DISK_OPEN_FILE
完成该Irp 并且 返回状态 STATUS_NO_MEDIA_IN_DEVICE。
否则IoControlCode的处理情况就是:
A.IOCTL_FILE_DISK_OPEN_FILE: //打开文件 应用 /mount 命令发送这个Irp
如果媒体文件已经打开了,status 赋值为 STATUS_INVALID_DEVICE_REQUEST.
检验是否传进来的缓冲区大小,如果太少了,status 就赋值为 STATUS_INVALID_PARAMETER.
然后模拟一个客户环境,并放到device_extension中.
把IRP放入队列,status 赋值为 STATUS_PENDING.(交给所创建的系统线程处理.)
B.IOCTL_FILE_DISK_CLOSE_FILE: //关闭文件
把IRP放入队列,status 赋值为 STATUS_PENDING.(交给所创建的系统线程处理.)
C.以下这几个IoControlCode的处理情况是根据device_extension的信息返回相应状态:
IOCTL_DISK_CHECK_VERIFY
IOCTL_CDROM_CHECK_VERIFY
IOCTL_STORAGE_CHECK_VERIFY
IOCTL_STORAGE_CHECK_VERIFY2
IOCTL_DISK_IS_WRITABLE
IOCTL_DISK_MEDIA_REMOVAL
IOCTL_STORAGE_MEDIA_REMOVAL
IOCTL_DISK_SET_PARTITION_INFO
status值都不是STATUS_PENDING.
D.在返回的缓冲区容量大小足够的情况下,以下这几个IoControlCode的处理是:
IOCTL_FILE_DISK_QUERY_FILE //查询状态
IOCTL_DISK_GET_DRIVE_GEOMETRY
IOCTL_CDROM_GET_DRIVE_GEOMETRY //得到盘的容量大小
IOCTL_DISK_GET_LENGTH_INFO
IOCTL_DISK_GET_PARTITION_INFO
IOCTL_DISK_GET_PARTITION_INFO_EX
IOCTL_CDROM_READ_TOC
IOCTL_DISK_VERIFY
Irp->AssociatedIrp.SystemBuffer指向要返回结构。并为 status 赋值 STATUS_SUCCESS;
Irp->IoStatus.Information 赋值为返回结构的大小.
E.如果是其他的IoControlCode就返回STATUS_INVALID_DEVICE_REQUEST;
F.如果在以上处理时status不是STATUS_PENDING,就完成该IRP.
//创建的系统线程.
8.VOID FileDiskThread (IN PVOID Context)
将本线程的运行时优先级改为LOW_REALTIME_PRIORITY.
KeSetPriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY);
循环开始.
//等待这个事件&device_extension->request_event变成信号态。
//如果系统线程应该终止.就终止本线程。
从IRP队列中取出IRP,判断IRP的主功能号进行处理:
IRP_MJ_READ://IRP_MJ_READ读操作.
用MmGetSystemAddressForMdlSafe宏得到irp->MdlAddress所描述的非分页系统空间虚拟地址.(即为system_buffer赋值)
申请指定大小的分页内存,用ZwReadFile读取相应文件内容,复制所读取的内容到system_buffer. 释放刚才申请的分页内存.
IRP_MJ_WRITE:
判断参数正确性.
将irp->MdlAddress内存用ZwWriteFile写入device_extension->file_handle所代表的文件中.
IRP_MJ_DEVICE_CONTROL:
IOCTL_FILE_DISK_OPEN_FILE:
模仿用户安全环境,交给函数FileDiskOpenFile做具体的操作.
IOCTL_FILE_DISK_CLOSE_FILE:
交给函数FileDiskCloseFile做具体的操作.
完成该IRP.
从IRP队列中取出下一个IRP继续.
所有IRP都处理完之后,返回循环开始处.
//关闭文件操作.
9.NTSTATUS FileDiskCloseFile (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp )
取得设备扩展device_extension,关闭打开句柄,释放申请的内容.设定标志位device_extension->media_in_device.设定返回状态为STATUS_SUCCESS.
//打开文件,并获取文件的信息放入 DeviceObject 及设备扩展结构中.
10.NTSTATUS FileDiskOpenFile ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
//根据Irp->AssociatedIrp.SystemBuffer所指OPEN_FILE_INFORMATION结构的值
//设定DeviceObject->DeviceExtension各字段的值.
//打开文件保存句柄到device_extension->file_handle.
//如果文件不存在就创建文件.
8.VOID FileDiskThread (IN PVOID Context)
将本线程的运行时优先级改为LOW_REALTIME_PRIORITY.
KeSetPriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY);
循环开始.
//等待这个事件&device_extension->request_event变成信号态。
//如果系统线程应该终止.就终止本线程。
从IRP队列中取出IRP,判断IRP的主功能号进行处理:
IRP_MJ_READ://IRP_MJ_READ读操作.
用MmGetSystemAddressForMdlSafe宏得到irp->MdlAddress所描述的非分页系统空间虚拟地址.(即为system_buffer赋值)
申请指定大小的分页内存,用ZwReadFile读取相应文件内容,复制所读取的内容到system_buffer. 释放刚才申请的分页内存.
IRP_MJ_WRITE:
判断参数正确性.
将irp->MdlAddress内存用ZwWriteFile写入device_extension->file_handle所代表的文件中.
IRP_MJ_DEVICE_CONTROL:
IOCTL_FILE_DISK_OPEN_FILE:
模仿用户安全环境,交给函数FileDiskOpenFile做具体的操作.
IOCTL_FILE_DISK_CLOSE_FILE:
交给函数FileDiskCloseFile做具体的操作.
完成该IRP.
从IRP队列中取出下一个IRP继续.
所有IRP都处理完之后,返回循环开始处.
//关闭文件操作.
9.NTSTATUS FileDiskCloseFile (IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp )
取得设备扩展device_extension,关闭打开句柄,释放申请的内容.设定标志位device_extension->media_in_device.设定返回状态为STATUS_SUCCESS.
//打开文件,并获取文件的信息放入 DeviceObject 及设备扩展结构中.
10.NTSTATUS FileDiskOpenFile ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
//根据Irp->AssociatedIrp.SystemBuffer所指OPEN_FILE_INFORMATION结构的值
//设定DeviceObject->DeviceExtension各字段的值.
//打开文件保存句柄到device_extension->file_handle.
//如果文件不存在就创建文件.