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

在写 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. 日期函数ADD_MONTHS,MONTHS_BETWEEN,LAST_DAY,NEXT_DAY

  2. JMeter脚本java代码String数组要写成String[] args,不能写成String args[],否则报错。

    JMeter脚本java代码String数组中括号要写在类型关键字后面,不能写在变量名后面.

  3. 【批处理】shift用法举例

    @echo off set sum=0 call :sub sum 1 2 3 4 echo sum=%sum% pause :sub set /a %1=%1+%2 shift /2 if not ...

  4. Winccflexable触摸屏的报警

    1.报警的分类 2.自定义报警分类 3.报警组成 4.Winccflexable中预定义的报警类别 5.报警的确认 6.WinccFlexable报警的显示 1)报警视图 2)报警窗口 3).报警指示 ...

  5. 西门子flexable创建画面

    一.wincc flexable 创建画面包括以下四点 二.具体操作 1.组态画面模板 1)使用该模板的画面包括该模板的所有组件,一个模板也是一个画面 2)给模板上添加一个文本域如下图,则画面1也会显 ...

  6. Vuejs环境安装与工程建立【小白Windows向】

    不知道为什么CDN的方式就是困难...大佬说SPA必须配置本地开发环境,那就配咯. 所以就准备了以下的工具进行安装本地开发环境: 1. 代码编辑器×1[本人使用VSCode 1.11] 2. Node ...

  7. Quartz.Net 使用

    Quartz.NET  是一套很好的任务调度框架. 下面介绍如何使用: 在项目Nuget包管理器中搜索:quartz 安装后会添加如下dll: <packages> <package ...

  8. CentOS7 nginx安装

    1 nginx安装环境 nginx是C语言开发,建议在linux上运行,本教程使用Centos6.5作为安装环境. n gcc 安装nginx需要先将官网下载的源码进行编译,编译依赖gcc环境,如果没 ...

  9. nginx+apache前后台搭配使用

    nginx apache都是web服务器 但是nginx更轻型对静态处理强大,而且nginx也是反向代理服务器,可以作转发 apache比较重型,非常稳定,处理动态WEB程序非常好,但是对静态处理就比 ...

  10. jquery获取select选中的值

    http://blog.csdn.net/renzhenhuai/article/details/19569593 误区: 一直以为jquery获取select中option被选中的文本值,是这样写的 ...