使得开发之,调用源检测逻辑

by admin on 2019年2月19日

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

———– Rootkit 大旨技术之绕过 IopParseDevice() 调用源检测逻辑,

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

在上一篇小说中,大家早已看到 IopParseDevice() 怎样对传播的 OPEN_PACKET
结构进行认证。若是 ObReferenceObjectByName()
的调用者没有分配并先河化第几个参数 ParseContext,而仅是差不多地传来 “NULL”
,那么当调用链深刻到 IopParseDevice()
内部时,就会因验证失败再次来到 C0000024(STATUS_OBJECT_TYPE_MISMATCH)。

咱俩依照源码中的暗示来追踪 OPEN_PACKET
结构毕竟在哪分配的,如前所述,调用链
NtCreateFile->IoCreateFile()->IopCreateFile() 的结尾,约等于在
IopCreateFile() 内部,实际负责 OPEN_PACKET
的起首化。上面贴出的代码片段以 NT 5.2 版内核源码为样例:

 

home88一必发 1

相当于说,大家一直复制 IopCreateFile() 中的 OPEN_PACKET
结构初叶化部分逻辑就行了?

那里还有三个题材,负责分配该协会体内核内存的例程 IopAllocateOpenPacket()
是贰个宏,Visual C++ 二〇一六 中提交它是用 ExAllocatePoolWithTag()
定义的。那就好办了,在我们温馨的驱动源码中,添加相应定义即可,如下图:

 

home88一必发 2

 

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

因为 OPEN_PACKET
结构同样没有公开的文档来叙述,所以依然在大家的驱动源码中用 
#include
蕴涵定义它的头文件,要么间接复制定义的那有个别黏贴进来。很精晓,后者相比轻松——OPEN_PACKET
在根本源码的 “iomgr.h
中定义,而该头文件又嵌套包括了一堆杂七杂八的内核头文件,要理清这几个嵌套包涵关系很麻烦,而且最要紧的是,其间一些头文件定义的数据类型会与驱动开发中用的 “ntddk.h”
和“wdm.h”重复,引起编译器的抱怨。
故而一直在 “iomgr.h” 中搜索字串
“typedef struct _OPEN_PACKET”,把找到的定义块拷贝进来即可。

然而,OPEN_PACKET 结构中单独3个字段不是 “原生” 定义的——那就是
“PDUMMY_FILE_OBJECT” 类型,必要包蕴其余头文件才不造成编译器报错。

本人的消除方案是,直接把该字段的评释所在行注释掉,下图展现了该字段具体的岗位(在
iomgr.h” 中的行号),方便各位快速搜索:

 

home88一必发 3

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

小心,NT 6.1 版内核在编译时刻的 OPEN_PACKET 结构鲜明是未经 “恶意
修改的,所以编译器为其 “sizeof(OPEN_PACKET)” 表达式统计 0x70
的值,而小编辈在团结的驱动中拿掉了 OPEN_PACKET
其中几个字段使得编译器为说明式 “sizeof(OPEN_PACKET)” 预总结 0x58
的值(后边的调节阶段会注脚),那会招致 “Size” 字段不是
IopParseDevice() 内部逻辑预期的 0x70,从而造成重回C0000024(STATUS_OBJECT_TYPE_MISMATCH)。

化解办法也很简短,大家的驱动中,不要借助编译时刻的持筹握算,间接把
Size” 字段的值硬编码为 0x70 不就好了?

正如图所示,你还会专注到,小编把 “Type” 字段的常量
“IO_TYPE_OPEN_PACKET” 改成了相应的数值,以有限支撑一旦。

 

home88一必发 4

 

其余,由于 IopAllocateOpenPacket() 等价于
ExAllocatePoolWithTag(),而后人平时重临泛型指针(“ PVOID ,亦即 
void * ”),
之所以我强制把它转型为与 “openPacket” 一致的类型。
万事俱备,“东风” 就在于调用 ObReferenceObjectByName()
时,为首个参数传入“openPacket” 即可,上图展示的很精通了。

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

很不佳的是,作者把编译出来的驱动放到虚拟机(Windows 7,基于 NT 6.1
版内核)里面动态加载测试,依旧无法赢拿到

“\Device\QQProtect” 相应的装置对象指针,ObReferenceObjectByName() 重返C0000024。

为了找出故障原因,小编在分配 OPEN_PACKET
逻辑的前边利用内联汇编添加了三个软中断 “__asm{ int 3; } 
”,宿主机器上运维水源调试器 kd.exe,作者的起步参数像是那样:

kd.exe -n -v -logo d:\virtual_使得开发之,调用源检测逻辑。machine_debugging.txt -y
SRV*C:\Symbols* -k
com:pipe,port=\\.\pipe\com_1,baud=115200,reconnect

 

参数 “logo” 内定要把方方面面调试进度的出口新闻写入日志;

“-y”
指定符号文件的职位(机器指令中尚无内核函数与变量的标志,所以调试器必要查找额外的记号以向用户浮现人类可读的名目);
“-k” 参数内定调试类型为
命名管道模拟串口1”,Porter率数值越高,响应越快。

把重新编译好的驱动放到虚拟机中,在升级权限后的吩咐提醒符中执行
bcdedit.exe,启用调试格局,那样重启虚拟机后,就会进来调试方式(无需在开行进度中按下
F8 采取菜单)。

作者把团结的驱动完毕成按需加载,约等于使用劳动控制管理器sc.exe)发出指令来动态加载和卸载,完成此成效的附和批处理公事内容如下图,注意该文件要放在虚拟机中履行,“start=
demand” 申明通过 sc.exe 按需运营
;“binpath”
就是驱动文件存放的磁盘路径
,借使自身的驱动名为
hideprocess.sys,执行该批处理职分后,就在连带的注册表地方添加了一项,今后只需在
cmd.exe 中推行 “sc.exe start/stop hideprocess” 就可以动态加卸载。

home88一必发 5

 

遵守上述方法加载时,就会活动触发我们设定好的软件断点,即可在宿主机中检查虚拟机的基础空间。
除此以外还需注意一点:编译驱动时的 “打造” 环境应该接纳 Check
Build
,那样会一并生成同名称的号子文件,后缀为
.pdb”,从而调试器可以显得大家自个儿驱动中的函数与变量名称,升高调试功效,如下图:

 

home88一必发 6

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

接触软件断点后,大家一般会用 “kv
命令查看栈回溯新闻,它揭发出大家的驱动入口点 DriverEntry() 是由 I/O
管理器的 IopLoadDriver() 调用的:

 

home88一必发 7

栈的顶层函数 “ReferenceDeviceAndHookIPRADOPdispatchRoutine+0x56
是自个儿添加软中断的地点。执行 “r” 命令查看当前的 x86
通用寄存器状态,EIP 指向地址 0x8f4a3196 ,执行 “u
hideprocess使得开发之,调用源检测逻辑。!ReferenceDeviceAndHookIRPdispatchRoutine+0x56
L2”,反汇编输出的首先行地址就是 0x8f4a3196,与 EIP
的值相符;第1行是把多个 16 进制值 “ 704F6F49h” 压栈,实际上它是
ASCII 字符 “pOoI” 的 16
进制编码,换言之,那是在经过内核栈传递 ExAllocatePoolWithTag()
的第⑨个参数(从右往左传递,请回想以前的 IopAllocateOpenPacket()
宏定义那张图)。

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

一而再按下 “t
单步执行,如下图所示,你可以看到,ExAllocatePoolWithTag()
的第二个参数,分配的根本内存大小为 0x70
字节,因为本身在宏定义中硬编码了这些值,而不是用 sizeof(OPEN_PACKET)
表达式让编译器总括;另一方面,图中的 “dt” 命令也作证了它的分寸为
0x70 字节。

第二个传入的参数 “NonPagedPool
为不可换页池,其内的多少不能够被换出物理内存,该常量对应的数值为 “0”:

home88一必发 8

 

自家不想浪费时间在翻看内核内存的分红细节上,所以小编按下 “p”,步过
ExAllocatePoolWithTag() 函数调用,接下去的 cmp/jne
汇编体系
对应源码中反省是不是中标分配了内存并用于 openPacket
指针,实际的实施结果是跳转到地址 0x8f4a31c6 ,对应源码中早先化
OPEN_PACKET 结构前五个字段的逻辑:

home88一必发 9

接下来直接单步执行到调用 ObReferenceObjectByName()
前夕,在那边大家要 “步入” 它的内部,举行故障排查,所以按下 “t
跟进,这里有三个小技巧,大家已经分析过 ObReferenceObjectByName()
的源码,知道它会调用很多函数,而且大致驾驭难题出现在
ObpLookupObjectName() 里面,所以指令
tc”可以跟踪到各类函数调用处截至,再由用户决定是不是跟进该函数内部。

那是本人的美美好的梦想,但实际总是狂暴的,在本身跟踪到原子操作种类函数

nt!ExInterlockedPopEntrySList() 调用时,kd.exe
就卡住了,无法持续追踪此后的调用链。从稍早的栈回溯信息来看,与源码中和我们揣度的调用系列大概相符,只是不了然为什么在
nt!ObpAllocateObjectNameBuffer() 中,为了给传入的驱动对象名称
“\Device\QQProtect”
分配内核内存,调用 nt!ExInterlockedPopEntrySList(),而后者却一筹莫展追踪。。。。是虚拟机环境的来由,依然原子操作类函数的不可分割性质?

 

home88一必发 10

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

讲一点废话,一般大家在栈回溯中看出的顶层表明行,有3个 “Args to Child”
项目,表示调用者传递给它的参数,不过最多也只能突显前多个。
以下图为例子吗,传递给 nt!ExAllocatePoolWithTag()
的多少个参数(从左到右)就是
00000000(NonPagedPool),00000070(作者硬编码的值),704f6f49(ASCII
字符串“pOoI”)
,同理,传递给 hideprocess!DriverEntry() 的率先个参数
867c3550 是 _DRIVER_OBJECT 结构的地址,由I/O
管理器加载它时为它分配(注意与源码中 DriverEntry() 定义的一枚
_DRIVER_OBJECT 指针差距,“Args to Child”

列出的多少一定于实践解引操作符 * 后的结果),第3个参数是
UNICODE_STLX570ING 结构的地址,对应源码定义中的一枚 _UNICODE_ST福睿斯ING
指针,该协会中贮存的是大家驱动在注册表中的完整路径:

 

home88一必发 11
——————————————————————————————————————————————————————————————————

一句话来说 ,基于上述理由作者无能为力继续跟进到 ObpLookupObjectName()
里面查看它是不是举办了 IopParseDevice()
回调,从而不能分明毕竟怎么后者重临 C0000024。

小编想大概是因为根本源码版本的转变,导致相关例程的论断逻辑也不雷同了,不能依照前一版源码的逻辑来编排臆度运行在后一版内核上的驱动。
其实化解方案或然有的,比较花时间而已,就是使用 “u” 指令反汇编
ObpLookupObjectName() 早先处对应的机器指令,再反编译成类似的 C 伪码,与
NT 5.2
版内核源码比较,找出里面改动的地点,但那是1个费时费劲的行事,且收入甚微,还不如直接在网络上搜释出的
NT 6.1 版内核源码,大概接近的本子,再想想绕过的主意。

顺带说一下,依照 A 设备名取得 A 设备对象的指针,然后把
rootkit/自个儿驱动创立的恶意设备 attach 到 A
设备所在的装置栈,从而阻碍检查通过 A 设备的 ITiguanP
内数据。。。。那种艺术已经相比较过时了,因为今后反病毒软件的基本格局组件也会检查这几个装备栈,寻找其余匹配特征码的恶意设备,再者,内核调试器的
“!devstack”
命令很简单遍历揭露出给定设备所在的设施栈内容,被大规模用于总计机调查取证中,从
rootkit 的紧要性目的——达成隐身——的角度来看, attach
到装备栈就不是2个好规范。

相反,通过 ObReferenceObjectByName()
总是可以得到驱动对象的指针,进而可以 hook 该驱动的 I科雷傲P
分发例程,那种手段隐蔽性极高,而且不易于被检测出来。

两次三番的博文将讨论怎么样将这种技术用在 rootkit
中,同时适应现阶段风靡的相反相成多处理器(SMP)环境。

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

Rootkit 大旨技术之绕过
IopParseDevice() 调用源检测逻辑,
—————————————————————————————————…

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

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

在上一篇小说中,大家早已见到
IopParseDevice() 如何对传播的 OPEN_PACKET 结构举行认证。假若ObReferenceObjectByName() 的调用者没有分配并开头化第⑧个参数
ParseContext,而仅是简简单单地传颂 “NULL” ,那么当调用链长远到
IopParseDevice()
内部时,就会因验证战败重回 C0000024(STATUS_OBJECT_TYPE_MISMATCH)。

在上一篇作品中,我们早就观察IopParseDevice() 怎么样对传播的 OPEN_PACKET 结构进行表达。假若ObReferenceObjectByName() 的调用者没有分配并先河化第多少个参数
ParseContext,而仅是简约地流传 “NULL” ,那么当调用链深刻到
IopParseDevice()
内部时,就会因验证失败重回 C0000024(STATUS_OBJECT_TYPE_MISMATCH)。

 

大家依据源码中的暗示来追踪
OPEN_PACKET 结构终究在哪分配的,如前所述,调用链
NtCreateFile->IoCreateFile()->IopCreateFile() 的结尾,约等于在
IopCreateFile() 内部,实际负责 OPEN_PACKET
的起始化。上边贴出的代码片段以 NT 5.2 版内核源码为样例:

我们依照源码中的暗示来追踪
OPEN_PACKET 结构究竟在哪分配的,如前所述,调用链
NtCreateFile->IoCreateFile()->IopCreateFile() 的终极,相当于在
IopCreateFile() 内部,实际负担 OPEN_PACKET
的发轫化。上边贴出的代码片段以 NT 5.2 版内核源码为样例:

在写
filter driver 或 rootkit 时,平日须要 attach
到装备栈中的对象设备,来阻拦途经的 IHighlanderP(I/O Request
Packet),达成过滤效果。
先是要查出目标设备向
Windows Object Manager 维护的全局名称空间注册的 _DEVICE_OBJECT
名,此类新闻方可因此像是 WinObj.exe 的工具得到。

 

 

接着调用
ObReferenceObjectByName(),该函数把收获的靶子对象地址存储到它的终极3个参数(指针)中,然后回来给调用者。
实战时大家会发觉,引用
_DRIVER_OBJECT 几乎总是成功;而引用
_DEVICE_OBJECT,则不自然会中标,再次回到的 NTSTATUS
状态码一般以二种居多:

home88一必发 12

home88一必发 13

1 C0000022(STATUS_ACCESS_DENIED)
2 C0000024(STATUS_OBJECT_TYPE_MISMATCH)

也等于说,我们直接复制
IopCreateFile() 中的 OPEN_PACKET 结构起初化部分逻辑就行了?

相当于说,大家一贯复制
IopCreateFile() 中的 OPEN_PACKET 结构伊始化部分逻辑就行了?

 

那里还有1个题材,负责分配该协会体内核内存的例程 IopAllocateOpenPacket()
是贰个宏,Visual C++ 二零一四 中提交它是用 ExAllocatePoolWithTag()
定义的。那就好办了,在大家友好的驱动源码中,添加相应定义即可,如下图:

那里还有多个标题,负责分配该社团体内核内存的例程 IopAllocateOpenPacket()
是3个宏,Visual C++ 二零一六 中提交它是用 ExAllocatePoolWithTag()
定义的。那就好办了,在大家自个儿的驱动源码中,添加相应定义即可,如下图:

首先种状态普通是出于创造目的
_DEVICE_OBJECT 时内定的 session id 与近年来的 session id
不一致等,大概目标对象拥有特殊的安全访问令牌/安全品质,所以我们鞭长莫及以常规格局拿到,而且这种错误频仍出未来
IoGetDeviceObjectPointer() 调用时,偏偏多数讲过滤驱动和 rootkit
的书本都用 IoGetDeviceObjectPointer()
作为示范代码的一片段,真是有点误人子弟的意味。

 

 

第2种情况普遍出现在经过
ObReferenceObjectByName() 引用某个 _DEVICE_OBJECT
的情景中,缘由与 ObReferenceObjectByName()
利用此外执行体组件例程,在全局名称空间中实践的名字查找逻辑缜密相关,前面会解释。

home88一必发 14

home88一必发 15

急需提议,既然经过
ObReferenceObjectByName() 引用绝超过半数 _DRIVER_OBJECT
都会成功,而且 _DRIVER_OBJECT.DeviceObject
又针对该驱动创立的设施链中第一个
_DEVICE_OBJECT,那么那就是最稳妥的法子。但是我们照旧要驾驭
STATUS_OBJECT_TYPE_MISMATCH 的原因。

 

 

 ObReferenceObjectByName()
是一个未公开的例程,在 MSDN 中从未文档描述,另一方面,包罗的 ntddk.h 或
wdm.h 头文件中也未曾有关原型评释;

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

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

只是内核印象ntoskrnl.exe
和其他的本子,的确导出了它的标记,换言之,我们只要求报告链接器把那些函数名作为外部符号来分析即可。
此外,ObReferenceObjectByName()
的第8个参数也是3个未文档化的数据类型(POBJECT_TYPE),所以相关的扬言是必须的,如下图所示:

因为
OPEN_PACKET 结构同样没有当面的文档来描述,所以依然在大家的驱动源码中用 
#include
包涵定义它的头文件,要么直接复制定义的那部分黏贴进来。很强烈,后者比较轻松——OPEN_PACKET
在基础源码的 “iomgr.h
中定义,而该头文件又嵌套包罗了一堆杂七杂八的内核头文件,要理清这一个嵌套包蕴关系很麻烦,而且最根本的是,里面一部分头文件定义的数据类型会与驱动开发中用的 “ntddk.h”
和“wdm.h”重复,引起编译器的埋怨。
由此直接在 “iomgr.h
中搜索字串 “typedef struct
_OPEN_PACKET”,把找到的概念块拷贝进来即可。

因为
OPEN_PACKET 结构同样没有当面的文档来描述,所以照旧在我们的驱动源码中用 
#include
包蕴定义它的头文件,要么直接复制定义的那部分黏贴进来。很明显,后者相比轻松——OPEN_PACKET
在基础源码的 “iomgr.h
中定义,而该头文件又嵌套包含了一堆杂七杂八的内核头文件,要清理那些嵌套包涵关系很劳碌,而且最重大的是,个中一部分头文件定义的数据类型会与驱动开发中用的 “ntddk.h”
和“wdm.h”重复,引起编译器的抱怨。
所以平素在 “iomgr.h
中搜索字串 “typedef struct
_OPEN_PACKET”,把找到的概念块拷贝进来即可。

 

然而,OPEN_PACKET
结构中只有二个字段不是 “原生” 定义的——那就是 “PDUMMY_FILE_OBJECT”
类型,须求包罗其他头文件才不造成编译器报错。

然而,OPEN_PACKET
结构中唯有一个字段不是 “原生” 定义的——那就是 “PDUMMY_FILE_OBJECT”
类型,须求包蕴别的头文件才不造成编译器报错。

home88一必发 16

自小编的缓解方案是,直接把该字段的宣示所在行注释掉,下图彰显了该字段具体的任务(在
iomgr.h” 中的行号),方便各位赶快搜索:

本身的解决方案是,直接把该字段的扬言所在行注释掉,下图彰显了该字段具体的职责(在
iomgr.h” 中的行号),方便各位连忙搜索:

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

 

 

请留心,大家注明了二个针对性类型“POBJECT_TYPE”的指针——IoDeviceObjectType——而“POBJECT_TYPE”自个儿又是指向品种“OBJECT_TYPE”的指针,所以在扩散第多少个参数时,一定要战战兢兢,使用操作符
*” 解引
IoDeviceObjectType,才会与它的形参类型(POBJECT_TYPE)匹配,否则会导致
ObReferenceObjectByName() 失败,干扰大家对回到的 NTSTATUS
原因判断!

home88一必发 17

home88一必发 18

 

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

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

假定我们友好的驱动要收获“\Device\QQProtect”对应的
_DEVICE_OBJECT 指针,然后检查再次回到的 NTSTATUS
状态码,如下图所示:

只顾,NT
6.1 版内核在编译时刻的 OPEN_PACKET 结构鲜明是未经 “恶意
修改的,所以编译器为其 “sizeof(OPEN_PACKET)” 表明式统计 0x70
的值,而大家在友好的驱动中拿掉了 OPEN_PACKET
其中八个字段使得编译器为表明式 “sizeof(OPEN_PACKET)” 预统计 0x58
的值(前面的调剂阶段会申明),那会招致 “Size” 字段不是
IopParseDevice() 内部逻辑预期的 0x70,从而导致返回C0000024(STATUS_OBJECT_TYPE_MISMATCH)。

注意,NT
6.1 版内核在编译时刻的 OPEN_PACKET 结构分明是未经 “恶意
修改的,所以编译器为其 “sizeof(OPEN_PACKET)” 表明式统计 0x70
的值,而小编辈在大团结的驱动中拿掉了 OPEN_PACKET
其中2个字段使得编译器为发挥式 “sizeof(OPEN_PACKET)” 预统计 0x58
的值(前边的调剂阶段会讲明),那会招致 “Size” 字段不是
IopParseDevice() 内部逻辑预期的 0x70,从而导致重临C0000024(STATUS_OBJECT_TYPE_MISMATCH)。

(“\Device\QQProtect”是与当时通讯软件
QQ 一同安装的七个过滤驱动之一:QQProtect.sys
创制的装置对象名,
它也是大家稍后的
I智跑P Dispatch Routine Hook 实验目的!)

化解办法也很不难,我们的驱动中,不要借助编译时刻的一个钱打二十七个结,直接把
Size” 字段的值硬编码为 0x70 不就好了?

解决办法也很简短,大家的驱动中,不要借助编译时刻的一个钱打二拾伍个结,直接把
Size” 字段的值硬编码为 0x70 不就好了?

 

一般来说图所示,你还会专注到,小编把
“Type” 字段的常量 “IO_TYPE_OPEN_PACKET”
改成了对应的数值,以管教一旦。

正如图所示,你还会注意到,小编把
“Type” 字段的常量 “IO_TYPE_OPEN_PACKET”
改成了相应的数值,以保险一旦。

home88一必发 19

 

 

 

home88一必发 20

home88一必发 21

可以看到,在虚拟机中测试时,DbgPrint()
打印再次来到的状态码为
C0000024(STATUS_OBJECT_TYPE_MISMATCH),相当于目的类型不包容,如下图所示:

 

 

 

除此以外,由于
IopAllocateOpenPacket() 等价于
ExAllocatePoolWithTag(),而后人平日重返泛型指针(“ PVOID ,亦即 void
”),
故而本身强制把它转型为与
“openPacket” 一致的项目。
万事俱备,“东风”
就在于调用 ObReferenceObjectByName() 时,为第多少个参数传入“openPacket”
即可,上图显示的很明亮了。

除此以外,由于
IopAllocateOpenPacket() 等价于
ExAllocatePoolWithTag(),而后者寻常再次来到泛型指针(“ PVOID ,亦即 void
”),
之所以本人强制把它转型为与
“openPacket” 一致的体系。
万事俱备,“东风”
就在于调用 ObReferenceObjectByName() 时,为第⑩个参数传入“openPacket”
即可,上图显示的很明白了。

home88一必发 22

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

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

 

很懊丧的是,我把编译出来的驱动放到虚拟机(Windows
7,基于 NT 6.1 版内核)里面动态加载测试,依然无法取得到

很懊丧的是,小编把编译出来的驱动放到虚拟机(Windows
7,基于 NT 6.1 版内核)里面动态加载测试,仍然无法得到到

刚好手边有一份
NT 5.2 版内核的源码,它用来编译 Windows XP/Server 二〇〇四使用的基础,就算与自个儿的测试机器的 NT 6.1 版内核有所差距,然而
抑或姑且来看下
ObReferenceObjectByName() 内部终究干了些什么。ObReference*()
种类的例程多数位于内核源码的“obref.c” 与“obdir.c
文本内。通过对有关调用链的辨析,如下图所示:

“\Device\QQProtect”
相应的装置对象指针,ObReferenceObjectByName() 再次回到 C0000024。

“\Device\QQProtect”
相应的装置对象指针,ObReferenceObjectByName() 重回 C0000024。

home88一必发 23

为了找出故障原因,作者在分配
OPEN_PACKET 逻辑的前方利用内联汇编添加了1个软中断 “__asm{ int
3; } 

”,宿主机器上运转水源调试器 kd.exe,作者的起步参数像是那样:

为了找出故障原因,作者在分配
OPEN_PACKET 逻辑的前方利用内联汇编添加了1个软中断 “__asm{ int
3; } 

”,宿主机器上运转水源调试器 kd.exe,我的起步参数像是这样:

上图中有两处关键点:其一是
ObpLookupObjectName() 中,检核对象对象类型的初始化设定(用
_OBJECT_TYPE_INITIALIZER 结构意味着)中,是不是指定了
ParseProcedure
例程,对于“设备”类对象,该函数值指针总是为 IopParseDevice() ,最后致使调用
IopParseDevice()

kd.exe
-n -v -logo d:\virtual_machine_debugging.txt -y
SRV*C:\Symbols* -k
com:pipe,port=\\.\pipe\com_1,baud=115200,reconnect

kd.exe
-n -v -logo d:\virtual_machine_debugging.txt -y
SRV*C:\Symbols* -k
com:pipe,port=\\.\pipe\com_1,baud=115200,reconnect

有心人考察前方的图纸可见,从初期自身调用
ObReferenceObjectByName() 开头,就为它的第多少个参数 ParseContext 传入 NULL,而 ParseContext
会在调用链中一路往下传递,末了由
IopParseDevice() 接受并对该参数举行验证,如果它为空,就赶回
STATUS_OBJECT_TYPE_MISMATCH

 

 

近来你通晓怎么
ObReferenceObjectByName()
引用目的设备连接令人这么蛋疼,关键就在需求分配并起始化那些 ParseContext。。。

参数
“logo” 指定要把全部调试进程的输出音信写入日志;

参数
“logo” 内定要把方方面面调试进度的输出消息写入日志;

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

“-y”
钦点符号文件的地方(机器指令中从未内核函数与变量的标记,所以调试器需求查找额外的标志以向用户浮现人类可读的名称);
“-k”
参数指定调试类型为
命名管道模拟串口1”,Porter率数值越高,响应越快。

“-y”
内定符号文件的职责(机器指令中从不内核函数与变量的号子,所以调试器须要查找额外的标记以向用户浮现人类可读的名号);
“-k”
参数钦定调试类型为
命名管道模拟串口1”,Porter率数值越高,响应越快。

作者在源码中领到了相关代码片段,如下边那几个图所示,最好能把它与地点的流程图比较加深通晓,
末端作者会拿虚拟机上的
Windows 7(基于 NT 6.1
版内核)调试,你会惊奇地觉察,追踪栈回溯音信时,
竟然与
NT 5.2
版内核源码中的调用链相当相像,这表达版本之间的搬迁并没有让对象名查找和认证逻辑改动太大。
(至少从
Windows XP 到 7
而言是那般,之后的本子由于没测试过,就不亮堂了!)

把重新编译好的驱动放到虚拟机中,在升级权限后的命令提醒符中执行
bcdedit.exe,启用调试形式,这样重启虚拟机后,就会进入调试情势(无需在开行进度中按下
F8 接纳菜单)。

把重新编译好的驱动放到虚拟机中,在升级权限后的授命提醒符中执行
bcdedit.exe,启用调试方式,那样重启虚拟机后,就会进来调试方式(无需在开行进度中按下
F8 采取菜单)。

 

自笔者把团结的驱动达成成按需加载,约等于行使劳动控制管理器sc.exe)发出指令来动态加载和卸载,达成此效用的对应批处理文件内容如下图,注意该公文要放在虚拟机中实践,“start=
demand” 表明通过 sc.exe 按需运维
;“binpath”
就是驱动文件存放的磁盘路径
,如果自身的驱动名为
hideprocess.sys,执行该批处理职务后,就在有关的注册表地方添加了一项,将来只需在
cmd.exe 中施行 “sc.exe start/stop hideprocess” 就可见动态加卸载。

自家把本身的驱动达成成按需加载,相当于利用服务控制管理器sc.exe)发出命令来动态加载和卸载,达成此成效的附和批处理文件内容如下图,注意该公文要放在虚拟机中执行,“start=
demand” 注明通过 sc.exe 按需运营
;“binpath”
就是驱动文件存放的磁盘路径
,如若本人的驱动名为
hideprocess.sys,执行该批处理职务后,就在连带的注册表地点添加了一项,今后只需在
cmd.exe 中推行 “sc.exe start/stop hideprocess” 就可以动态加卸载。

home88一必发 24

home88一必发 25

home88一必发 26

 

 

 

home88一必发 27

遵从上述方法加载时,就会自动触发大家设定好的软件断点,即可在宿主机中检查虚拟机的水源空间。
除此以外还需注意一点:编译驱动时的
“营造” 环境应该选用 Check
Build
,那样会一并生成同名称的标记文件,后缀为
.pdb”,从而调试器可以显示大家温馨驱动中的函数与变量名称,升高调试效用,如下图:

依照上述办法加载时,就会自动触发大家设定好的软件断点,即可在宿主机中检查虚拟机的根本空间。
除此以外还需注意一点:编译驱动时的
“营造” 环境应该选取 Check
Build
,那样会一并生成同名称的符号文件,后缀为
.pdb”,从而调试器可以显得我们和好驱动中的函数与变量名称,升高调试效能,如下图:

home88一必发 28

 

 

 

home88一必发 29

home88一必发 30

 

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

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

 

接触软件断点后,大家一般会用
kv” 命令查看栈回溯新闻,它揭表露大家的驱动入口点 DriverEntry() 是由
I/O 管理器的 IopLoadDriver() 调用的:

接触软件断点后,大家一般会用
kv” 命令查看栈回溯音信,它表披露大家的驱动入口点 DriverEntry() 是由
I/O 管理器的 IopLoadDriver() 调用的:


IopParseDevice()
内部的那段注释,小编不明得到了绕过调用源检测的思路——这就是跟踪
NtCreateFile() ,看看 OPEN_PACKET 具体是在哪儿

 

 

分配并早先化的;由于
IopParseDevice() 会检测 POPEN_PACKET 结构实例的有个别字段来确保
ObReferenceObjectByName() 调用
是从
NtCreateFile() 发起的,NtCreateFile() 实未来 NT 5.2 版内核源码的
creater.c
中,它只是不难地举行调用链
IoCreateFile()->IopCreateFile()(此两例程都落实在源码的 iosubs.c 中),而具体由
IopCreateFile() 分配并初步化 OPEN_PACKET 结构。

home88一必发 31

home88一必发 32

因而大家只要在引用目的设备对象前,仿照
IopCreateFile() 的有关逻辑来分配并初叶化 OPEN_PACKET,并作为
ObReferenceObjectByName()
的参数传入,就会绕过
IopParseDevice() 的“调用源检测”逻辑。
那有些Patch 就留待后边的小说再公布。大家日前先要验证“设备”类对象的“ParseProcedure”确实为
IopParseDevice()。。。。。

栈的顶层函数
“ReferenceDeviceAndHookIPAJEROPdispatchRoutine+0x56
是自家添加软中断的地点。执行 “r” 命令查看当前的 x86
通用寄存器状态,EIP 指向地址 0x8f4a3196 ,执行 “u
hideprocess!ReferenceDeviceAndHookIRPdispatchRoutine+0x56
L2”,反汇编输出的首先行地址就是 0x8f4a3196,与 EIP
的值相符;第叁行是把1个 16 进制值 “ 704F6F49h” 压栈,实际上它是
ASCII 字符 “pOoI” 的 16
进制编码,换言之,那是在经过内核栈传递 ExAllocatePoolWithTag()
的第多个参数(从右往左传递,请纪念以前的 IopAllocateOpenPacket()
宏定义这张图)。

栈的顶层函数
“ReferenceDeviceAndHookICRUISERPdispatchRoutine+0x56
是自己添加软中断的地方。执行 “r” 命令查看当前的 x86
通用寄存器状态,EIP 指向地址 0x8f4a3196 ,执行 “u
hideprocess!ReferenceDeviceAndHookIRPdispatchRoutine+0x56
L2”,反汇编输出的率先行地址就是 0x8f4a3196,与 EIP
的值相符;第叁行是把3个 16 进制值 “ 704F6F49h” 压栈,实际上它是
ASCII 字符 “pOoI” 的 16
进制编码,换言之,那是在经过内核栈传递 ExAllocatePoolWithTag()
的第多少个参数(从右往左传递,请纪念此前的 IopAllocateOpenPacket()
宏定义那张图)。

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

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

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

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

延续按下
t” 单步执行,如下图所示,你可以看看,ExAllocatePoolWithTag()
的首个参数,分配的内核内存大小为 0x70
字节,因为自身在宏定义中硬编码了那几个值,而不是用 sizeof(OPEN_home88一必发 ,PACKET)
表明式让编译器统计;另一方面,图中的 “dt” 命令也验证了它的分寸为
0x70 字节。

接二连三按下
t” 单步执行,如下图所示,你可以看出,ExAllocatePoolWithTag()
的首个参数,分配的根本内存大小为 0x70
字节,因为作者在宏定义中硬编码了这几个值,而不是用 sizeof(OPEN_PACKET)
表明式让编译器总括;另一方面,图中的 “dt” 命令也表明了它的尺寸为
0x70 字节。

home88一必发 33

首个传入的参数
NonPagedPool
为不可换页池,其内的多少不能被换出物理内存,该常量对应的数值为
“0”:

第一个传入的参数
NonPagedPool
为不可换页池,其内的多少无法被换出物理内存,该常量对应的数值为
“0”:

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

home88一必发 34

home88一必发 35

 

 

 

home88一必发 36

自己不想浪费时间在查阅内核内存的分配细节上,所以本人按下
p”,步过 ExAllocatePoolWithTag() 函数调用,接下去的 cmp/jne
汇编连串
对应源码中检查是否中标分配了内存并用于 openPacket
指针,实际的进行结果是跳转到地址 0x8f4a31c6 ,对应源码中开端化
OPEN_PACKET 结构前三个字段的逻辑:

本身不想浪费时间在查看内核内存的分红细节上,所以小编按下
p”,步过 ExAllocatePoolWithTag() 函数调用,接下去的 cmp/jne
汇编体系
对应源码中检查是否成功分配了内存并用于 openPacket
指针,实际的实施结果是跳转到地址 0x8f4a31c6 ,对应源码中初阶化
OPEN_PACKET 结构前三个字段的逻辑:

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

home88一必发 37

home88一必发 38

home88一必发 39

接下来间接单步执行到调用
ObReferenceObjectByName() 前夕,在此间大家要 “步入
它的内部,举办故障排查,所以按下 “t
跟进,那里有贰个小技巧,大家已经分析过 ObReferenceObjectByName()
的源码,知道它会调用很多函数,而且几乎掌握难点应运而生在
ObpLookupObjectName() 里面,所以指令
tc”可以跟踪到每一个函数调用处甘休,再由用户决定是不是跟进该函数内部。

接下来向来单步执行到调用
ObReferenceObjectByName() 前夕,在那里大家要 “步入
它的中间,举办故障排查,所以按下 “t
跟进,那里有三个小技巧,大家早已分析过 ObReferenceObjectByName()
的源码,知道它会调用很多函数,而且大概精晓难点应运而生在
ObpLookupObjectName() 里面,所以指令
tc”可以跟踪到每种函数调用处甘休,再由用户决定是不是跟进该函数内部。

因而可见,相应“对象类型”结构的地方为
0x855cef78——Windows 内核用数据结构 _OBJECT_TYPE
来表示“对象类型”的定义,所以重复
格式化并转储其中的字段,我们感兴趣的字段为“TypeInfo”,如前所述,它是多个“对象类型初阶化设定”结构,内核用
_OBJECT_TYPE_INITIALIZER
来代表“对象类型开始化设定”的定义。要求专注,TypeInfo
偏移它的母结构开端地址 0x28
字节,所以要增加那一个
offset
再查看,如您所见,其中的“ParseProcedure”为
IopParseDevice()

那是自身的光明梦想,但实际总是残忍的,在自个儿跟踪到原子操作连串函数

那是自作者的光明期待,但具体总是残忍的,在自小编跟踪到原子操作种类函数

 

nt!ExInterlockedPopEntrySList()
调用时,kd.exe
就卡住了,不大概继续追踪此后的调用链。从稍早的栈回溯音信来看,与源码中和大家预测的调用种类几乎相符,只是不精晓为何在
nt!ObpAllocateObjectNameBuffer() 中,为了给传入的驱动对象名称
“\Device\QQProtect”
分配内核内存,调用 nt!ExInterlockedPopEntrySList(),而后者却一筹莫展追踪。。。。是虚拟机环境的原因,照旧原子操作类函数的不可分割性质?

nt!ExInterlockedPopEntrySList()
调用时,kd.exe
就卡住了,不可以继续追踪此后的调用链。从稍早的栈回溯信息来看,与源码中和我们推测的调用系列大约相符,只是不明了为什么在
nt!ObpAllocateObjectNameBuffer() 中,为了给传入的驱动对象名称
“\Device\QQProtect”
分配内核内存,调用 nt!ExInterlockedPopEntrySList(),而后人却力不从心追踪。。。。是虚拟机环境的由来,如故原子操作类函数的不可分割性质?

home88一必发 40

 

 

 

home88一必发 41

home88一必发 42

下篇小说将商讨如何绕过
IopParseDevice() 的调用源检测,并调节我们的结晶,将其拔取于 rootkit
开发技术中。

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

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

 

讲一点废话,一般大家在栈回溯中看出的顶层表达行,有一个 “Args to Child” 项目,表示调用者传递给它的参数,不过最多也只可以呈现前七个。

讲一些废话,一般大家在栈回溯中来看的顶层表明行,有1个 “Args to Child” 项目,表示调用者传递给它的参数,然而最多也不得不显示前多少个。

以下图为例子吗,传递给 nt!ExAllocatePoolWithTag()
的多个参数(从左到右)就是
00000000(NonPagedPool),00000070(作者硬编码的值),704f6f49(ASCII
字符串“pOoI”)
,同理,传递给 hideprocess!DriverEntry() 的率先个参数
867c3550 是 _DRIVER_OBJECT 结构的地点,由I/O
管理器加载它时为它分配(注意与源码中 DriverEntry() 定义的一枚
_DRIVER_OBJECT 指针不相同,“Args to
Child”

以下图为例子吗,传递给 nt!ExAllocatePoolWithTag()
的四个参数(从左到右)就是
00000000(NonPagedPool),00000070(小编硬编码的值),704f6f49(ASCII
字符串“pOoI”)
,同理,传递给 hideprocess!DriverEntry() 的首先个参数
867c3550 是 _DRIVER_OBJECT 结构的地点,由I/O
管理器加载它时为它分配(注意与源码中 DriverEntry() 定义的一枚
_DRIVER_OBJECT 指针差别,“Args to
Child”

列出的数码一定于实践解引操作符 *
后的结果
),第二个参数是 UNICODE_ST库罗德ING
结构的地址,对应源码定义中的一枚 _UNICODE_ST昂科拉ING
指针,该社团中蕴藏的是大家驱动在注册表中的完整路径:

列出的数码一定于实践解引操作符 *
后的结果
),首个参数是 UNICODE_STMuranoING
结构的地方,对应源码定义中的一枚 _UNICODE_ST君越ING
指针,该协会中贮存的是我们驱动在注册表中的完整路径:

 

 

home88一必发 43
——————————————————————————————————————————————————————————————————

home88一必发 44
——————————————————————————————————————————————————————————————————

简单来讲,基于上述理由我不可以继续跟进到 ObpLookupObjectName()
里面查看它是不是实施了 IopParseDevice()
回调,从而无法显然到底为啥后者再次回到 C0000024。

简而言之,基于以上理由我无能为力继续跟进到 ObpLookupObjectName()
里面查看它是或不是执行了 IopParseDevice()
回调,从而不或然鲜明到底怎么后者重返 C0000024。

自己想恐怕是因为基本源码版本的转变,导致相关例程的论断逻辑也不一样了,不可以依照前一版源码的逻辑来编排猜想运转在后一版内核上的驱动。

本人想或者是因为基本源码版本的转移,导致相关例程的论断逻辑也不一样了,不能根据前一版源码的逻辑来编排臆度运营在后一版内核上的驱动。

实质上化解方案恐怕有个别,相比较花时间而已,就是利用 “u” 指令反汇编
ObpLookupObjectName() 开首处对应的机器指令,再反编译成类似的 C 伪码,与
NT 5.2
版内核源码比较,找出里面改动的地方,但那是三个费时费劲的工作,且收入甚微,还不如直接在网络上搜释出的
NT 6.1 版内核源码,恐怕接近的本子,再思考绕过的艺术。

事实上解决方案只怕有些,相比较花时间而已,就是采用 “u” 指令反汇编
ObpLookupObjectName() 最先处对应的机器指令,再反编译成类似的 C 伪码,与
NT 5.2
版内核源码相比,找出其中改动的地方,但那是二个费时费劲的干活,且收入甚微,还不如直接在网络上搜释出的
NT 6.1 版内核源码,大概接近的版本,再想想绕过的点子。

顺手说一下,依照 A 设备名得到 A 设备对象的指针,然后把
rootkit/本身驱动创设的恶心设备 attach 到 A
设备所在的配备栈,从而阻碍检查通过 A 设备的 I福睿斯P
内数据。。。。那种形式已经相比过时了,因为明日反病毒软件的基业方式组件也会检讨那些设备栈,寻找其余匹配特征码的恶心设备,再者,内核调试器的
“!devstack”
命令很简单遍历揭穿出给定设备所在的设备栈内容,被周边用于统计机调查取证中,从
rootkit 的首要目的——完成隐身——的角度来看, attach
到设备栈就不是3个好规范。

顺手说一下,根据 A 设备名取得 A 设备对象的指针,然后把
rootkit/本人驱动创立的恶心设备 attach 到 A
设备所在的设施栈,从而阻碍检查通过 A 设备的 ITiguanP
内数据。。。。那种办法已经相比较过时了,因为以后反病毒软件的内核格局组件也会检查那一个装备栈,寻找其他匹配特征码的恶心设备,再者,内核调试器的
“!devstack”
命令很简单遍历揭露出给定设备所在的配备栈内容,被广泛用于总结机调查取证中,从
rootkit 的根本目的——完成隐身——的角度来看, attach
到装备栈就不是多少个好规范。

反倒,通过 ObReferenceObjectByName()
总是可以拿到驱动对象的指针,进而能够 hook 该驱动的 IHavalP
分发例程,这种手法隐蔽性极高,而且不易于被检测出来。

相反,通过 ObReferenceObjectByName()
总是可以赢得驱动对象的指针,进而可以 hook 该驱动的 I本田CR-VP
分发例程,那种手段隐蔽性极高,而且不易于被检测出来。

连续的博文将探究哪边将那种技术用在
rootkit 中,同时适应当前风靡的相辅相成多处理器(SMP)环境。

接轨的博文将商讨什么将那种技术用在
rootkit 中,同时适应当下盛行的对称多处理器(SMP)环境。

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

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

发表评论

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

网站地图xml地图