Windows内核安全与驱动开发_应用与内核通信_内核方面的编程
- 作者: 刘杰
- 来源: 技术那些事
- 阅读:180
- 发布: 2025-10-30 10:59
- 最后更新: 2025-10-30 10:59
5.1.1 生成控制设备
如果一个驱动需要和应用程序通信,那么首先要生成一个设备对象(DeviceObject)。在Windows驱动开发体系中,设备对象是非常重要的元素。设备对象和分发函数构成了整个内核体系的基本框架。设备对象可以在内核中暴露出来给应用层,应用层可以像操作文件一样操作它。这些细节将会在后面阐述。一般而言,用于和应用程序通信的设备往往用来“控制”这个内核驱动(这里所谓的控制就是配置、开启或者关闭某些功能,建议参考本章开头图5-1的例子),所以往往称之为“控制设备对象”(ControlDevice Object,CDO)。下面请读者先看生成控制设备的例子。
生成设备可以使用函数IoCreateDevice。这个函数的原型如下:NTSTATUS
c
IoCreateDevice(
IN PDRIVER_OBJECT DriverObject
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceName OPTIONAL,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics
IN BOOLEAN xclusive,
OUT PDEVICE_OBJECT*Device0bject
);
第一个参数是 DriverObject,它可以直接从DriverEntry的参数中获得。第二个参数表示设备扩展的大小,在后面的“应用设备扩展”中会专门讲述。DeviceName是设备名,可以为空作为控制设备,一般要提供一个设备名,以便应用程序容易打开它。DeviceType是设备类型。Windows 已经规定了一系列设备类型。DeviceCharacteristics是一组设备属性。如何填写读者会在后面看到例子。Exclusive表示是否是一个独占设备。设置为独占设备的话,这个设备将在同一时刻只能被打开一个句柄。一般驱动都不会设置为独占设备。不过有时候这会有好处。作为控制设备,可能不希望别的进程能够打开这个设备,而只允许自己的进程打开(比如安全软件显然不希望被病毒所控制),那么设置为独占设备并由某个进程打开着永不关闭,应该可以提供一定程度的安全性。不过,这点笔者并未自己尝试过,读者有兴趣的话可以试试。最后一个参数 DeviceObject是用来返回结果的。如果函数执行成功(返回值为STATUS SUCCESS),那么*DeviceObject 就是生成的设备对象的指针。
使用loCreateDevice来生成控制设备对初学者来说会碰到一个潜在的问题,那就是这个函数生成的设备具有默认的安全属性,其结果就是必须要具有管理员权限的进程才能打开它。这可能会让某些读者在用自己的应用程序与之通信时遇到麻烦:如果当前用户只是一个普通用户,就会发现无法打开设备。为此,笔者使用另一个函数来强迫生成一个任何用户都可以打开的设备。当然,作为商业软件而言,这显然是不安全的。这样做的目的仅仅是为了读者使用程序方便。
c
NTSTATUS IoCreateDeviceSecure(
IN PDRIVER_OBECT DriverObject,
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceNameOPTIONAL,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
IN PCUNICODE_STRING DefaultSDDLstring,
IN LPCGUID DeviceclassGuid,
OUT PDEVICE_OBJECT *Device0bject,
)
这个函数的大多数参数和上面的loCreateDevice 函数是一样的,只是增加了两个参数,其中一个是 DefaultSDDLString,这个特殊格式的字符串能表示这个设备对象的安全设置;另一个参数是 DeviceClassGuid,它是这个设备的GUID,是所谓的全球唯一标识符。 对于 DefaultSDDLString,笔者并未深究,只是从WDK的帮助中拷贝了一个自称支持任何用户直接打开设备的字符串(读者可以在下面的例子中见到)。至于DeviceClassGuid,理论上需要用微软提供的函数CoCreateGuid来生成(注意是指调用这个函数一次,得到一个设备的GUID,以后就一直使用这个GUID,而不是每次执行驱动都生成一个)。所以,请读者在开发自己的驱动时,切记不要拷贝笔者例子中的GUID,以免同一个GUID被用到多个设备中造成潜在的冲突。 最终生成控制设备的代码如下:
cpp
// 在DriverEntry之外定义一个全局变量
PDEVICE OBJECT gCdO = NULL;
NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg_path)
{
// 生成一个控制设备,然后生成符号链接
UNICODE STRING Sdd1 = RTL_CONSTANT_STRING(L"D:P(A; ;GA; ; ;WD)" );
UNICODE STRING cdo name= RTL_CONSTANT_STRING(L"\\Device\\slbk 3948d33e");
// 生成一个控制设备对象
status =IoCreateDeviceSecure(
driver,
0,
&cdo_name,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&sddl,
(LPCGUID)&SLBKGUID_CLASS_MYCDO,
&g_cdo
);
if(INT SUCCESS(status))
return status;
...
}
上面的字符串"D:P(A;;GA;;;WD)"就是允许任何用户访问该设备的万能安全设置字符串(当然所谓的万能就是完全不安全)。另外一个字符串cdoname是设备的名字,这在下面的小节中会详细介绍。 另外值得注意的是上面的gcdo,这是一个全局变量。一般而言,控制设备生成之后都保存在全局变量中。这是因为一个驱动程序只有一个控制设备,简单地保存在全局变量里容易在其他函数中(比如卸载或者分发函数中)识别。
5.1.2 控制设备的名字和符号链接
设备对象是可以没有名字的。但是控制设备需要有一个名字,这样它才会被暴露出来,供其他程序打开与之通信。设备的名字可以在调用IoCreateDevice或IoCreateDeviceSecure 时指定。此外,应用层是无法直接通过设备的名字来打开对象的,为此必须要建立一个暴露给应用层的符号链接。符号链接就是记录一个字符串对应到另一个字符串的一种简单结构。生成符号链接的函数是:
c
NTSTATUS IoCreateSymbolicLink(
IN PUNICODE_STRING SymbolicLinkName,
IN PUNICODE_STRING DeviceName
)
可以看到这个函数非常简单,只有两个参数:一个是符号链接名,一个是设备名。般而言,这个函数都会成功。不过,如果一个符号链接的名字已经在系统里存在了(注意,符号链接是在 Windows中全局存在的),那么这个函数会返回失败。 所以,用符号链接是不太稳妥的方式,因为符号链接就是一个字符串,就算字符串再长,也不可能完全避免某个驱动生成的符号链接的名字与另一个厂商的符号链接的名字发生冲突。所以,最稳妥的是使用GUID的方式来访问设备。不过,这对本书来说并非一个关键问题。要详细了解设备对象的访问方式,请参考专业的硬件驱动开发书籍。 在这方面笔者的方式比较粗暴,就是先尝试删除该符号链接,然后再生成。这样万一有冲也被消弭于无形了。当然,这只是表面上的。如果另一个驱动真的需要用到同名的符号链接,那么应该会因为把请求发给错误的设备对象而崩溃。代码如下:
c
#define CWK_CDO_SYB_NAME L"\\??\\slbkcdo 3948d33e"
// ...此处省略部分代码
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
//...
UNICODE_STRING cdo_name = RTL_CONSTANT_STRING(L"\\Device\\cwk 3948d33e");
UNICODE_STRING cdo_syb= RTL_CONSTANT_STRING(CWK CDO SYB NAME);
//...
// 生成符号链接
IoDeleteSymbolicLink(&cdo_syb);
status = IoCreateSymbolicLink(&cdo_syb,&cdo_name);
if(INT_SUCCESS(status))
{
//一旦失败了,要记得之前已经生成过设备对象,所以删除IoDeleteDevice(g cdo);
return status;
}
}
注意这个设备名是cdo_name,而符号链接名是cdo_syb。这两个字符串有一定的讲究。Windows的设备都像文件一样位于一个对象树的管理之下。一般而言,设备都位于\Devicel这个路径下(但这不是绝对的),而生成的符号链接则一般位于\??\这个路径下(这的确是两个问号,并非乱码)。这两个字符串其实是路径名。
笔者故意把这两个字符串弄得很古怪,后面加了很长的数字,就是为了避免和别的驱动程序冲突。当然,如果最终有多位读者直接拷贝了这些代码并用到了商业的软件中,而且最后产生了冲突,笔者也不会吃惊。
如何在应用程序中打开这个设备,将会在后面的内容中介绍。
5.1.3 控制设备的删除
待续
5.1.4 分发函数
待续
5.1.5 请求的处理
待续