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

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

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

 C0000022(STATUS_ACCESS_DENIED)
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),所以相关的声明是必须的,如下图所示:

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

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

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

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

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

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

上图中有两处关键点:其一是 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 而言是如此,之后的版本由于没测试过,就不清楚了!)

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()。。。。。

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

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

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

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

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

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

--------驱动开发之 ObReferenceObjectByName() 故障排查--------的更多相关文章

  1. Android驱动开发之Hello实例

    Android驱动开发之Hello实例:   驱动部分 modified:   kernel/arch/arm/configs/msm8909-1gb_w100_hd720p-perf_defconf ...

  2. Linux内核驱动开发之KGDB原理介绍及kgdboe方式配置

    接博文<Linux内核驱动开发之KGDB单步调试内核(kgdboc方式)>.上篇博文中,仅简单介绍使用串口的Kgbd的流程(kgdboc方式),本文将重点介绍KGDB调试Linux内核的原 ...

  3. 基于msm8909高通平台Android驱动开发之hello程序

    本文转载自:http://www.itwendao.com/article/detail/227839.html Android驱动开发之Hello实例:   驱动部分 modified:   ker ...

  4. 嵌入式linux驱动开发之给linux系统添加温度传感器模块

    忙了几天,终于可以让ds18b20在自己的开发板的linux系统上跑了!虽然ds18b20不是什么新鲜玩意,但是想想知己可以给linux系统添加模块了还是有点小鸡冻呢! 虽然说现在硬件的资源非常丰富而 ...

  5. 嵌入式Linux驱动开发之helloword心得

    自从选择了物联网这个专业,智能XX的字样牵动着每一个学习这个专业的孩子. 大家兴致勃勃的来到了学校,结果一切想象和自己的设想并不一样.想象中的各种智能般梦幻的场景变成了真实的高数/电路/模电等等诸如此 ...

  6. linux驱动开发之GCC问题

    最近正在学习驱动开发,进展到字符设备驱动开发阶段. 先不多说,首先把刚看的一篇学习驱动步骤的帖子记录如下: 1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. ...

  7. linux驱动开发之HelloWorld

    最近实习,公司项目搞的是平板开发,而我分配的任务是将驱动加载到内核中. 准备工作,必要知识了解:加载有两种方式,一种是动态加载和卸载即模块加载,另一种是直接编译进入内核:Linux内核把驱动程序划分为 ...

  8. 【转】Android驱动开发之earlysuspend睡眠模式编程总结

    原文网址:http://blog.csdn.net/bigapple88/article/details/8669537 (1)添加头文件: #include <linux/earlysuspe ...

  9. 嵌入式驱动开发之usb 无线网卡驱动---RT2870STA dm368

    RT2870STA 368的无线网卡驱动! http://www.linuxidc.com/Linux/2014-02/96979.htm

随机推荐

  1. spring boot + druid + 封装JdbcTemplate

    本源码内容如下: spring boot项目 用的druid连接池 druid监控页面配置 数据操作用spring jdbctemplate 进一步封装spring jdbctemplate支持用对象 ...

  2. Mixed Reality-宁波市VR/AR技术应用高研班总结

    年,全球AR与VR市场规模将达到1500亿美元,而根据市场研究机构BI Intelligence的统计,2020年仅头戴式VR硬件市场规模将达到28亿美元,未来5年复合增长率超过100%.本次培训从V ...

  3. 2. whoami,常用包,调优selinux,七种启动模式,系统开机服务

    1   whoami  查看当前登录用户      useradd zhang   增加用户      passwd  zhang       su - zhang     切换用户        e ...

  4. IOC容器在web容器中初始化过程——(二)深入理解Listener方式装载IOC容器方式

    先来看一下ContextServletListener的代码 public class ContextLoaderListener extends ContextLoader implements S ...

  5. jquery如此强大,为什么还要写原生呢?

    这是一个伪标题,其实是一篇年终总结. 在这家公司一年多,蛮多收获的.大部分来自自己,小部分来自公司. 做前端开发到现在,我觉得可以分为两部分. 前半部分做项目用原生js,jquery以及各种基于jq的 ...

  6. js验证input输入框(字母,数字,符号,中文)

    [javascript]代码库 <h1>js验证输入框内容</h1> <br /> <br /> 只能输入英文 <input type=" ...

  7. 实现WebSocket和WAMP协议的开源库WampSharp

    Websocket Application Messaging Protocol 协议:https://github.com/wamp-proto/wamp-proto 1. 基础档案 引入: WAM ...

  8. [置顶] android ListView包含Checkbox滑动时状态改变

    题外话: 在xamarin android的开发中基本上所有人都会遇到这个小小的坎,的确有点麻烦,当时我也折腾了好一半天,如果你能看到这篇博客,说明你和我当初也是一样的焦灼,如果你想解决掉这个小小的坎 ...

  9. JS画几何图形之五【过圆外一点作切线】

    样例:http://www.zhaojz.com.cn/demo/draw9.html 依赖:[点].[直线].[圆] //画切线 //point 圆外的一点 //dot 圆心 //r 半径 func ...

  10. PHP随机函数【上】

    随机函数应用的场景很多,比如验证码,token,订单号等.由浅入深了解常用随机函数 1.rand 常用的随机数字函数,默认生成[0,getrandmax()]之间的随机数(包括边界值),因性能问题已被 ...