故障排查永利集团304com:

——————————————————————————————————————————————————————

 

在写
filter driver 或 rootkit 时,经常需要 attach
到设备栈中的目标设备,来拦截途经的 IRP(I/O Request
Packet),实现过滤功能。
首先要获悉目标设备向
Windows Object Manager 维护的全局名称空间注册的 _DEVICE_OBJECT
名,此类信息可以通过像是 WinObj.exe 的工具获取。

接着调用
ObReferenceObjectByName(),该函数把获取的目标对象地址存储到它的最后一个参数(指针)中,然后返回给调用者。
实战时我们会发现,引用
_DRIVER_OBJECT 几乎总是成功;而引用
_DEVICE_OBJECT,则不一定会成功,返回的 NTSTATUS
状态码一般以两种居多:

1 C0000022(STATUS_ACCESS_DENIED)
2 C0000024(STATUS_OBJECT_TYPE_MISMATCH)

 

第一种情况通常是由于创建目标
_DEVICE_OBJECT 时指定的 session id 与当前的 session id
不一致,或者目标对象持有特殊的安全访问令牌/安全属性,所以我们无法以常规方式获取,而且这种错误频繁出现在
IoGetDeviceObjectPointer() 调用时,偏偏多数讲过滤驱动和 rootkit
的书籍都用 IoGetDeviceObjectPointer()
作为示例代码的一部分,真是有点误人子弟的意味。

第二种情况普遍出现在通过
ObReferenceObjectByName() 引用某些 _DEVICE_OBJECT
的场景中,缘由与 ObReferenceObjectByName()
利用其它执行体组件例程,在全局名称空间中执行的名字查找逻辑密切相关,后面会解释。

需要指出,既然通过
ObReferenceObjectByName() 引用绝大多数 _DRIVER_OBJECT
都会成功,而且 _DRIVER_OBJECT.DeviceObject
又指向该驱动创建的设备链中第一个
_DEVICE_OBJECT,那么这就是最稳当的方法。不过我们还是要知道
STATUS_OBJECT_TYPE_MISMATCH 的原因。

 ObReferenceObjectByName()
是一个未公开的例程,在 MSDN 中没有文档描述,另一方面,包含的 ntddk.h 或
wdm.h 头文件中也没有相关原型声明;

但是内核映像
ntoskrnl.exe
和其它的版本,的确导出了它的符号,换言之,我们只需要告诉链接器把这个函数名作为外部符号来解析即可。
此外,ObReferenceObjectByName()
的第五个参数也是一个未文档化的数据类型(POBJECT_TYPE),所以相关的声明是必须的,如下图所示:

 

永利集团304com 1

—————————————————————————————————————————————————————————————

请注意,我们声明了一个指向类型“POBJECT_TYPE”的指针——IoDeviceObjectType——而“POBJECT_TYPE”自身又是指向类型“OBJECT_TYPE”的指针,所以在传入第五个参数时,一定要谨慎,使用操作符
*” 解引
IoDeviceObjectType,才会与它的形参类型(POBJECT_TYPE)匹配,否则会导致
ObReferenceObjectByName() 失败,干扰我们对返回的 NTSTATUS
原因判断!

 

假设我们自己的驱动要获得“DeviceQQProtect”对应的
_DEVICE_OBJECT 指针,然后检查返回的 NTSTATUS
状态码,如下图所示:

(“DeviceQQProtect”是与即时通信软件
QQ 一同安装的两个过滤驱动之一:QQProtect.sys
创建的设备对象名,
它也是我们稍后的
IRP Dispatch Routine Hook 实验目标!)

 

永利集团304com 2

 

可以看到,在虚拟机中测试时,DbgPrint()
打印返回的状态码为
C0000024(STATUS_OBJECT_TYPE_MISMATCH),也就是对象类型不匹配,如下图所示:

 

永利集团304com 3

 

刚好手边有一份
NT 5.2 版内核的源码,它用来编译 Windows XP/Server 2003
使用的内核,尽管与我的测试机器的 NT 6.1 版内核有所差异,不过
还是姑且来看下
ObReferenceObjectByName() 内部究竟干了些什么。ObReference*()
系列的例程多数放在内核源码的“obref.c” 与“obdir.c
文件内。通过对相关调用链的分析,如下图所示:

永利集团304com 4

上图中有两处关键点:其一是
ObpLookupObjectName() 中,检查目标对象类型的初始化设定(用
_OBJECT_TYPE_INITIALIZER 结构表示)中,是否指定了
ParseProcedure
例程,对于“设备”类对象,该函数值指针总是为 IopParseDevice() ,最终导致调用
IopParseDevice()

仔细观察前面的图片可知,从最初我调用
ObReferenceObjectByName() 开始,就为它的第七个参数 ParseContext 传入 NULL,而 ParseContext
会在调用链中一路往下传递,最终由
IopParseDevice() 接受并对该参数进行验证,如果它为空,就返回
STATUS_OBJECT_TYPE_MISMATCH

现在你知道为啥
ObReferenceObjectByName()
引用目标设备总是让人如此蛋疼,关键就在需要分配并初始化那个 ParseContext。。。

 ———————————————————————————————————————————

我在源码中提取了相关代码片段,如下面这些图所示,最好能把它与上面的流程图对比加深理解,
后面我会拿虚拟机上的
Windows 7(基于 NT 6.1
版内核)调试,你会惊讶地发现,追踪栈回溯信息时,
竟然与
NT 5.2
版内核源码中的调用链非常相似,这说明版本之间的迁移并没有让对象名查找和验证逻辑改动太大。
(至少从
Windows XP 到 7
而言是如此,之后的版本由于没测试过,就不清楚了!)

 

永利集团304com 5

 

永利集团304com 6

永利集团304com 7

 

 

 


IopParseDevice()
内部的那段注释,我依稀得到了绕过调用源检测的思路——那就是跟踪
NtCreateFile() ,看看 OPEN_PACKET 具体是在哪里

分配并初始化的;由于
IopParseDevice() 会检测 POPEN_PACKET 结构实例的一些字段来保证
ObReferenceObjectByName() 调用
是从
NtCreateFile() 发起的,NtCreateFile() 实现在 NT 5.2 版内核源码的
creater.c
中,它只是简单地执行调用链
IoCreateFile()->IopCreateFile()(此两例程都实现在源码的 iosubs.c 中),而具体由
IopCreateFile() 分配并初始化 OPEN_PACKET 结构。

所以我们只要在引用目标设备对象前,仿照
IopCreateFile() 的相关逻辑来分配并初始化 OPEN_PACKET,并作为
ObReferenceObjectByName()
的参数传入,就会绕过
IopParseDevice() 的“调用源检测”逻辑。
这部分
Patch 就留待后面的随笔再发表。我们当前先要验证“设备”类对象的“ParseProcedure”确实为
IopParseDevice()。。。。。

——————————————————————————————————————————————————

在双击内核调试环境中,首先通过设备名称“DeviceQQProtect”取得相应对象的信息:

永利集团304com 8

得到对象头地址后,格式化并转储其中的字段,我们感兴趣的是“TypeIndex”字段,它用来索引“对象类型表”中的相应“对象类型”:

 

永利集团304com 9

WInodws
内核使用一个数据结构——ObTypeIndexTable
存放有关各种“对象类型”的信息,本质上 ObTypeIndexTable
是一个指针数组,在 32 位体系结构上,每个指针大小
4 字节,而我们得到的索引号为(下标**
0
开始**)19,下图中的两条表达式据此计算出该“对象类型”的地址:

永利集团304com 10

由此可知,相应“对象类型”结构的地址为
0x855cef78——Windows 内核用数据结构 _OBJECT_TYPE
来表示“对象类型”的概念,所以再次
格式化并转储其中的字段,我们感兴趣的字段为“TypeInfo”,如前所述,它是一个“对象类型初始化设定”结构,内核用
_OBJECT_TYPE_INITIALIZER
来表示“对象类型初始化设定”的概念。需要注意,TypeInfo
偏移它的母结构起始地址 0x28
字节,所以要加上这个
offset
再查看,如你所见,其中的“ParseProcedure”为
IopParseDevice()

 

永利集团304com 11

 

下篇文章将讨论如何绕过
IopParseDevice() 的调用源检测,并调试我们的成果,将其应用于 rootkit
开发技术中。

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注