上帝保佑 - God4

God Bless

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

现象:

  最近在编写WinXP SP2系统下的虚拟磁盘驱动,通过DefineDosDevice将虚拟盘挂载到计算机上,显示出盘符。测试的时候,把一些exe文件放进去,看能不能正常执行,大部分exe都执行良好,但在执行devicetree.exe的时候出现了问题,弹出对话框显示objinfo驱动无法启动,然后退出。

分析:

  devicetree.exe本身只有一个文件就能运行,没有发现过需要什么驱动,莫非是在其自身里面包含了?于是通过OllyDbg反汇编查找具体出错的位置。果然,devicetree.exe启动时要在其自身目录下创建并启动objinfo.sys,但是在StartService的时候就会出错。如果把程序放在别的实际盘中运行,则一切正常。打开注册表,观察这两种情况下服务的配置信息,除了盘符不同之外,其他信息完全相同,由此看来问题确实出在虚拟盘上。

  用DefineDosDevice定义的盘符毕竟是在用户态下创建的,对于服务和驱动可能会有影响。用WinObj查看内核中的符号连接,在Global中居然看不到虚拟盘符对应的项,那么内核在启动驱动时肯定无法正常定位到真正路径。突然想起Windows映射的网络驱动器,也是如此情况,那也肯定会存在这种问题,经测试发现果然如此。

  在内核下用IoCreateSymblicLink创建的符号连接没有问题,所以初步怀疑是和用户态、内核态有关系,于是编写代码从用户态发送DeviceControl请求,由驱动来创建符号连接。结果出乎意料,仍然不行。但是测试中却发现,如果是由System下的进程发送的请求,创建出来的符号连接就没有问题。看来问题出在上下文(Session)上,只要是用户态程序,其调用的驱动也在用户的上下文(Session)里。

原因:

  从网上搜索来搜索去,终于明白,比较高级的Windows系统(哪个版本的操作系统很难讲,需要判断系统所支持的WDM版本号)符号链接带有用户相关性。用户上下文创建的符号连接不是在Global下,而是在Session下。

解决:

  方案一

  任何用户都可以生成全局符号链接,只要在创建符号连接的时候把“\DosDevices\SymbolicLinkName”改为“\DosDevices\Global\SymbolicLinkName”即可。但是在不支持符号链接用户相关性的系统上,生成这样的符号链接是一种错误。为此必须先判断一下:

 

    UNICODE_STRING device_name;
    UNICODE_STRING symbl_name;

    
if( IoIsWdmVersionAvailable(10x10) )           // 如果支持符号链接用户相关性
        RtlInitUnicodeString(&symbl_name, L"\\DosDevices\\Global\\SymbolicLinkName");

    
else                                             // 如果不支持,则用“\DosDevices”
        RtlInitUnicodeString(&symbl_name, L"\\DosDevices\\SymbolicLinkName");

    RtlInitUnicodeString(
&device_name, L"\\Device\\DeviceName");
    IoCreateSymbolicLink(
&symbl_name, &device_name); // 生成符号链接

    ......

    IoDeleteSymbolicLink(
&symbl_name);               // 删除符号链接

 

  配合用户态下的DefineDosDevice,可以使用户看到所加载的盘符,同时内核态也不会出现问题。

 

    DefineDosDevice(DDD_RAW_TARGET_PATH, DriveName, DEVICE_NAME);

    ......

    DefineDosDevice(DDD_REMOVE_DEFINITION, DriveName, NULL);

 

  方案二

  TrueCrypt实现的虚拟盘不会出现上述问题,调试其源代码发现它在加载的过程中,通过MountManagerMount函数向MountPointManager设备发送IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION和IOCTL_MOUNTMGR_CREATE_POINT请求来实现盘符的加载,发送IOCTL_MOUNTMGR_DELETE_POINTS请求来实现盘符的卸载。两个主要函数如下:

 

NTSTATUS MountManagerMount (MOUNT_STRUCT *mount)
{
    NTSTATUS ntStatus; 
    WCHAR arrVolume[
64];
    
char buf[200];
    PMOUNTMGR_TARGET_NAME 
in = (PMOUNTMGR_TARGET_NAME) buf;
    PMOUNTMGR_CREATE_POINT_INPUT point 
= (PMOUNTMGR_CREATE_POINT_INPUT) buf;

    TCGetNTNameFromNumber (arrVolume, mount
->nDosDriveNo);
    
in->DeviceNameLength = (USHORT) wcslen (arrVolume) * 2;
    wcscpy(
in->DeviceName, arrVolume);

    ntStatus 
= TCDeviceIoControl (MOUNTMGR_DEVICE_NAME, IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION,
        
insizeof (in->DeviceNameLength) + wcslen (arrVolume) * 200);

    memset (buf, 
0sizeof buf);
    TCGetDosNameFromNumber ((PWSTR) 
&point[1], mount->nDosDriveNo);

    point
->SymbolicLinkNameOffset = sizeof (MOUNTMGR_CREATE_POINT_INPUT);
    point
->SymbolicLinkNameLength = (USHORT) wcslen ((PWSTR) &point[1]) * 2;

    point
->DeviceNameOffset = point->SymbolicLinkNameOffset + point->SymbolicLinkNameLength;
    TCGetNTNameFromNumber ((PWSTR) (buf 
+ point->DeviceNameOffset), mount->nDosDriveNo);
    point
->DeviceNameLength = (USHORT) wcslen ((PWSTR) (buf + point->DeviceNameOffset)) * 2;

    ntStatus 
= TCDeviceIoControl (MOUNTMGR_DEVICE_NAME, IOCTL_MOUNTMGR_CREATE_POINT, point,
        point
->DeviceNameOffset + point->DeviceNameLength, 00);

    
return ntStatus;
}

NTSTATUS MountManagerUnmount (
int nDosDriveNo)
{
    NTSTATUS ntStatus; 
    WCHAR drive[] 
= {(WCHAR) nDosDriveNo + 'A'0};
    
char buf[64], out[300];
    PMOUNTMGR_MOUNT_POINT 
in = (PMOUNTMGR_MOUNT_POINT) buf;

    memset (buf, 
0sizeof buf);

    TCGetDosNameFromNumber ((PWSTR) 
&in[1], nDosDriveNo);

    
in->SymbolicLinkNameOffset = sizeof (MOUNTMGR_MOUNT_POINT);
    
in->SymbolicLinkNameLength = (USHORT) wcslen ((PWCHAR) &in[1]) * 2;

    ntStatus 
= TCDeviceIoControl (MOUNTMGR_DEVICE_NAME, IOCTL_MOUNTMGR_DELETE_POINTS,
        
insizeof(MOUNTMGR_MOUNT_POINT) + in->SymbolicLinkNameLength, outsizeof out);

    Dump (
"IOCTL_MOUNTMGR_DELETE_POINTS returned 0x%08x\n", ntStatus);

    
return ntStatus;
}

 

  需要说明的是,MountPointManager在加载盘符前,会自动向目标设备发送IOCTL_MOUNTDEV_QUERY_DEVICE_NAME和IOCTL_MOUNTDEV_QUERY_UNIQUE_ID请求来获取信息,也就就是说,要想通过这种方式加载成功,目标设备必须正确处理这两个请求。

  加载或卸载成功后,在用户态下需要广播设备变更消息来显示盘符,具体内容可以参考TrueCrypt的BroadcastDeviceChange函数。

备注:

  TrueCrypt
  http://www.truecrypt.com/

  Windows driver examples
  https://www.acc.umu.se/~bosse/

  在应用层用DefineDosDevice创建的符号链接,用winobj为何看不到?
  http://bbs.driverdevelop.com/read.php?tid-107056.html

  小议Windows装载管理器(Mount Manager)
  http://hi.baidu.com/%B3%FE%D0%F9%B1%F9%C0%B6/blog/item/2cf3958354fe49b46c811984.html

posted on 2010-08-31 21:43  God4  阅读(4859)  评论(5编辑  收藏  举报