--------驱动开发之 ObReferenceObjectByName() 故障排查--------
——————————————————————————————————————————————————————
在写 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() 故障排查--------的更多相关文章
- Android驱动开发之Hello实例
Android驱动开发之Hello实例: 驱动部分 modified: kernel/arch/arm/configs/msm8909-1gb_w100_hd720p-perf_defconf ...
- Linux内核驱动开发之KGDB原理介绍及kgdboe方式配置
接博文<Linux内核驱动开发之KGDB单步调试内核(kgdboc方式)>.上篇博文中,仅简单介绍使用串口的Kgbd的流程(kgdboc方式),本文将重点介绍KGDB调试Linux内核的原 ...
- 基于msm8909高通平台Android驱动开发之hello程序
本文转载自:http://www.itwendao.com/article/detail/227839.html Android驱动开发之Hello实例: 驱动部分 modified: ker ...
- 嵌入式linux驱动开发之给linux系统添加温度传感器模块
忙了几天,终于可以让ds18b20在自己的开发板的linux系统上跑了!虽然ds18b20不是什么新鲜玩意,但是想想知己可以给linux系统添加模块了还是有点小鸡冻呢! 虽然说现在硬件的资源非常丰富而 ...
- 嵌入式Linux驱动开发之helloword心得
自从选择了物联网这个专业,智能XX的字样牵动着每一个学习这个专业的孩子. 大家兴致勃勃的来到了学校,结果一切想象和自己的设想并不一样.想象中的各种智能般梦幻的场景变成了真实的高数/电路/模电等等诸如此 ...
- linux驱动开发之GCC问题
最近正在学习驱动开发,进展到字符设备驱动开发阶段. 先不多说,首先把刚看的一篇学习驱动步骤的帖子记录如下: 1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. ...
- linux驱动开发之HelloWorld
最近实习,公司项目搞的是平板开发,而我分配的任务是将驱动加载到内核中. 准备工作,必要知识了解:加载有两种方式,一种是动态加载和卸载即模块加载,另一种是直接编译进入内核:Linux内核把驱动程序划分为 ...
- 【转】Android驱动开发之earlysuspend睡眠模式编程总结
原文网址:http://blog.csdn.net/bigapple88/article/details/8669537 (1)添加头文件: #include <linux/earlysuspe ...
- 嵌入式驱动开发之usb 无线网卡驱动---RT2870STA dm368
RT2870STA 368的无线网卡驱动! http://www.linuxidc.com/Linux/2014-02/96979.htm
随机推荐
- git 设置和取消代理
# 设置ss git config --global http.proxy 'socks5://127.0.0.1:1080' git config --global https.proxy 'soc ...
- Android studio导入eclipse项目(亲测)
之前上网搜索的时候,网上都说先用eclipse导出gradle,之后再用Android Studio的import project导入,但是这个方法使用的过程中会出现许多错误,解决了一个又一个还是不得 ...
- Spring框架中ModelAndView、Model、ModelMap区别
原文地址:http://www.cnblogs.com/google4y/p/3421017.html SPRING框架中ModelAndView.Model.ModelMap区别 注意:如果方法 ...
- 串String(2):串的实现(堆分配存储表示法)
7/27/2017,先占个位,最近事情比较忙,明天敲一波代码,预测在一星期内搞定 9/02/2017,看到这个十分汗颜,八月去美帝学习了,没有抽空补上这一博文,计划这个月开了数据结构课后补上
- iOS知识点、面试题 之二
最近面试,与大家分享一下,分三文给大家: 当然Xcode新版本区别,以及iOS新特性 Xcode8 和iOS 10 在之前文章有发过,感兴趣的可以查阅: http://www.cnblogs.com/ ...
- 转:一篇讲线上优化查 CPU的脚本
原文链接:https://my.oschina.net/leejun2005/blog/1524687 摘要: 本文主要针对 Java 服务而言 0.背景 经常做后端服务开发的同学,或多或少都遇到 ...
- 超级基础的python文件读取
读取文件的两种方式: 1.使用os的open函数: import sys,os r=open("data1.txt","r+") fr=r.readlines( ...
- Nginx (三) 使用Keepalived搭建高可用服务
Nginx可以实现高并发反向代理,实现负载均衡,但是有个问题就是Nginx是单点的.如果Nginx故障,则整个服务将会处于不可用状态.所以我们就需要想办法让nginx高可用,即使一个Nginx宕机,还 ...
- SpringMVC处理multipart请求.
一.简述 multipart格式的数据会将一个表单拆分为多个部分(part),每个部分对应一个输入域.在一般的表单输入域中,它所对应的部分中会放置文本型数据,但是如果上传文件的话,它所对应的部分可以是 ...
- DCL的失效:现实与初衷的背离
最近看了Brian Goetz写的一篇有关DCL的文章:Double-checked locking: Clever, but broken.( 2001年发表于JavaWorld上) 这篇文章讲述了 ...