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

———– 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 版内核源码为样例:

 

澳门金沙国际 1

也就是说,大家一直复制 IopCreateFile() 中的 OPEN_PACKET
结构起首化部分逻辑就行了?

此地还有一个问题,负责分配该协会体内核内存的例程 IopAllocateOpenPacket()
是一个宏,Visual C++ 2015 中付出它是用 ExAllocatePoolWithTag()
定义的。那就好办了,在大家温馨的驱动源码中,添加相应定义即可,如下图:

 

澳门金沙国际 2

 

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

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

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

自己的解决方案是,直接把该字段的扬言所在行注释掉,下图呈现了该字段具体的职责(在
iomgr.h” 中的行号),方便各位快速搜索:

 

澳门金沙国际 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” 改成了对应的数值,以有限扶助一旦。

 

澳门金沙国际 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”,波特率数值越高,响应越快。

把重新编译好的驱动放到虚拟机中,在升高权限后的指令提醒符中执行
bcdedit.exe,启用调试形式,那样重启虚拟机后,就会进入调试方式(无需在开行进程中按下
F8 选用菜单)。

自我把团结的驱动完成成按需加载,也就是行使劳动控制管理器sc.exe)发出指令来动态加载和卸载,落成此功能的呼应批处理公事内容如下图,注意该公文要放在虚拟机中实施,“start=
demand” 申明通过 sc.exe 按需启动
;“binpath”
就是驱动文件存放的磁盘路径
,假使自己的驱动名为
hideprocess.sys,执行该批处理职责后,就在连带的注册表地方添加了一项,将来只需在
cmd.exe 中履行 “sc.exe start/stop hideprocess” 就可见动态加卸载。

澳门金沙国际 5

 

按照上述方式加载时,就会自行触发大家设定好的软件断点,即可在宿主机中检查虚拟机的木本空间。
其它还需注意一点:编译驱动时的 “构建” 环境应该选择 Check
Build
,那样会一并生成同名称的符号文件,后缀为
.pdb”,从而调试器可以显得我们团结驱动中的函数与变量名称,提升调试功能,如下图:

 

澳门金沙国际 6

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

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

 

澳门金沙国际 7

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

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

继续按下 “t
单步执行,如下图所示,你可以见见,ExAllocatePoolWithTag()
的第四个参数,分配的木本内存大小为 0x70
字节,因为我在宏定义中硬编码了那些值,而不是用 sizeof(OPEN_PACKET)
表明式让编译器总结;另一方面,图中的 “dt” 命令也证实了它的大小为
0x70 字节。

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

澳门金沙国际 8

 

自身不想浪费时间在查阅内核内存的分红细节上,所以我按下 “p”,步过
ExAllocatePoolWithTag() 函数调用,接下去的 cmp/jne
汇编种类
对应源码中检查是还是不是成功分配了内存并用于 openPacket
指针,实际的实施结果是跳转到地址 0x8f4a31c6 ,对应源码中起始化
OPEN_PACKET 结构前多少个字段的逻辑:

澳门金沙国际 9

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

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

nt!ExInterlockedPopEntrySList() 调用时,kd.exe
就卡住了,不能继续追踪此后的调用链。从稍早的栈回溯新闻来看,与源码中和大家预测的调用种类大约相符,只是不亮堂为什么在
nt!ObpAllocateObjectNameBuffer() 中,为了给传入的驱动对象名称
“\Device\QQProtect”
分配内核内存,调用 nt!ExInterlockedPopEntrySList(),而后者却无所适从追踪。。。。是虚拟机环境的来头,依旧原子操作类函数的不可分割性质?

 

澳门金沙国际 10

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

讲一点废话,一般大家在栈回溯中观看的顶层表明行,有一个 “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_STRING 结构的地址,对应源码定义中的一枚 _UNICODE_STRING
指针,该社团中蕴藏的是我们驱动在注册表中的完整路径:

 

澳门金沙国际 11
——————————————————————————————————————————————————————————————————

简单的讲 ,基于上述理由我无能为力持续跟进到 ObpLookupObjectName()
里面查看它是还是不是举行了 IopParseDevice()
回调,从而不能确定到底怎么后者重返 C0000024。

自己想可能是因为根本源码版本的变化,导致相关例程的判定逻辑也不均等了,不可以按照前一版源码的逻辑来编排估算运行在后一版内核上的驱动。
其实解决方案或者有些,比较花时间而已,就是运用 “u” 指令反汇编
ObpLookupObjectName() 起首处对应的机器指令,再反编译成类似的 C 伪码,与
NT 5.2
版内核源码比较,找出里面改动的地方,但那是一个费时费劲的行事,且收入甚微,还不如直接在互联网上搜释出的
NT 6.1 版内核源码,或者接近的版本,再想想绕过的章程。

顺带说一下,按照 A 设备名获得 A 设备对象的指针,然后把
rootkit/自己驱动创立的恶心设备 attach 到 A
设备所在的配备栈,从而阻碍检查通过 A 设备的 IRP
内数据。。。。这种艺术已经相比较过时了,因为现在反病毒软件的基业格局组件也会检查那一个设施栈,寻找其他匹配特征码的黑心设备,再者,内核调试器的
“!devstack”
命令很简单遍历揭穿出给定设备所在的装备栈内容,被广大用于总结机调查取证中,从
rootkit 的重中之重目的——完毕隐身——的角度来看, attach
到装备栈就不是一个好规范。

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

继续的博文将钻探什么将那种技能用在 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
到装备栈中的靶子设备,来堵住途经的 IRP(I/O Request
Packet),完结过滤效果。
先是要查出目的设备向
Windows Object Manager 维护的大局名称空间注册的 _DEVICE_OBJECT
名,此类音信可以由此像是 WinObj.exe 的工具获得。

 

 

随即调用
ObReferenceObjectByName(),该函数把得到的目标对象地址存储到它的终极一个参数(指针)中,然后重回给调用者。
实战时大家会发现,引用
_大旨技术之绕过,驱动开发之。DRIVER_OBJECT 大概连接成功;而引用
_DEVICE_OBJECT,则不肯定会中标,重临的 NTSTATUS
状态码一般以两种居多:

澳门金沙国际 12

澳门金沙国际 13

1 C0000022(STATUS_ACCESS_DENIED)
2 C0000024(STATUS_OBJECT_TYPE_MISMATCH)

也就是说,我们直接复制
IopCreateFile() 中的 OPEN_PACKET 结构先河化部分逻辑就行了?

也就是说,大家间接复制
IopCreateFile() 中的 OPEN_PACKET 结构先导化部分逻辑就行了?

 

此处还有一个题目,负责分配该社团体内核内存的例程 IopAllocateOpenPacket()
是一个宏,Visual C++ 2015 中付出它是用 ExAllocatePoolWithTag()
定义的。那就好办了,在大家团结一心的驱动源码中,添加相应定义即可,如下图:

此处还有一个题材,负责分配该社团体内核内存的例程 IopAllocateOpenPacket()
是一个宏,Visual C++ 2015 中提交它是用 ExAllocatePoolWithTag()
定义的。那就好办了,在我们团结的驱动源码中,添加相应定义即可,如下图:

先是种情状普通是由于创制目的
_DEVICE_OBJECT 时指定的 session id 与方今的 session id
不雷同,或者目的对象具备特殊的白山访问令牌/安全性能,所以我们无法以健康办法拿到,而且那种不当频仍出现在
IoGetDeviceObjectPointer() 调用时,偏偏多数讲过滤驱动和 rootkit
的书籍都用 IoGetDeviceObjectPointer()
作为示范代码的一片段,真是有点误人子弟的表示。

 

 

其次种状态普遍出现在通过
ObReferenceObjectByName() 引用某些 _DEVICE_OBJECT
的场景中,缘由与 ObReferenceObjectByName()
利用其余执行体组件例程,在全局名称空间中施行的名字查找逻辑缜密相关,前面会分解。

澳门金沙国际 14

澳门金沙国际 15

须求提出,既然经过
ObReferenceObjectByName() 引用绝半数以上 _DRIVER_OBJECT
都会马到功成,而且 _DRIVER_OBJECT.DeviceObject
又针对该驱动创制的配备链中第四个
_DEVICE_OBJECT,那么那就是最稳妥的不二法门。不过咱们如故要精通
STATUS_OBJECT_TYPE_MISMATCH 的原因。

 

 

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

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

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

不过内核印象ntoskrnl.exe
和其余的本子,的确导出了它的号子,换言之,我们只需要告诉链接器把这么些函数名作为外部符号来分析即可。
澳门金沙国际,此外,ObReferenceObjectByName()
的第五个参数也是一个未文档化的数据类型(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”
类型,需求包涵其余头文件才不造成编译器报错。

澳门金沙国际 16

我的化解方案是,直接把该字段的评释所在行注释掉,下图体现了该字段具体的职位(在
iomgr.h” 中的行号),方便各位神速搜索:

自家的化解方案是,直接把该字段的表明所在行注释掉,下图显示了该字段具体的职位(在
iomgr.h” 中的行号),方便各位快捷搜索:

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

 

 

请留意,大家评释了一个对准类型“POBJECT_TYPE”的指针——IoDeviceObjectType——而“POBJECT_TYPE”自身又是指向项目“OBJECT_TYPE”的指针,所以在传播第二个参数时,一定要审慎,使用操作符
*” 解引
IoDeviceObjectType,才会与它的形参类型(POBJECT_TYPE)匹配,否则会导致
ObReferenceObjectByName() 战败,干扰我们对回到的 NTSTATUS
原因判断!

澳门金沙国际 17

澳门金沙国际 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
其中一个字段使得编译器为表达式 “sizeof(OPEN_PACKET)” 揣摸算 0x58
的值(前边的调剂阶段会注解),那会促成 “Size” 字段不是
IopParseDevice() 内部逻辑预期的 0x70,从而导致重回C0000024(STATUS_OBJECT_TYPE_MISMATCH)。

(“\Device\QQProtect”是与当时通讯软件
QQ 一同安装的多少个过滤驱动之一:QQProtect.sys
创立的配备对象名,
它也是大家稍后的
IRP Dispatch Routine Hook 实验目标!)

解决办法也很粗略,大家的驱动中,不要借助编译时刻的测算,直接把
Size” 字段的值硬编码为 0x70 不就好了?

解决办法也很粗略,我们的驱动中,不要借助编译时刻的测算,直接把
Size” 字段的值硬编码为 0x70 不就好了?

 

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

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

澳门金沙国际 19

 

 

 

澳门金沙国际 20

澳门金沙国际 21

可以看到,在虚拟机中测试时,DbgPrint()
打印重临的状态码为
C0000024(STATUS_OBJECT_TYPE_MISMATCH),也就是目标类型不同盟,如下图所示:

 

 

 

别的,由于
IopAllocateOpenPacket() 等价于
ExAllocatePoolWithTag(),而后人常常重返泛型指针(“ PVOID ,亦即 void
”),
于是我强制把它转型为与
“openPacket” 一致的品种。
齐全,“东风”
就在于调用 ObReferenceObjectByName() 时,为首个参数传入“openPacket”
即可,上图展现的很精通了。

其它,由于
IopAllocateOpenPacket() 等价于
ExAllocatePoolWithTag(),而后者日常再次来到泛型指针(“ PVOID ,亦即 void
”),
所以自己强制把它转型为与
“openPacket” 一致的类型。
万事俱备,“东风”
就在于调用 ObReferenceObjectByName() 时,为第五个参数传入“openPacket”
即可,上图体现的很掌握了。

澳门金沙国际 22

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

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

 

很不佳的是,我把编译出来的驱动放到虚拟机(Windows
7,基于 NT 6.1 版内核)里面动态加载测试,仍旧不可能取得到

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

刚好手边有一份
NT 5.2 版内核的源码,它用来编译 Windows XP/Server 2003
使用的根本,即便与自身的测试机器的 NT 6.1 版内核有所差距,可是
或者姑且来看下
ObReferenceObjectByName() 内部究竟干了些什么。ObReference*()
体系的例程多数身处内核源码的“obref.c” 与“obdir.c
文本内。通过对有关调用链的解析,如下图所示:

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

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

澳门金沙国际 23

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

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

为了找出故障原因,我在分配
OPEN_PACKET 逻辑的前面利用内联汇编添加了一个软中断 “__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”,波特率数值越高,响应越快。

“-y”
指定符号文件的岗位(机器指令中一直不内核函数与变量的记号,所以调试器必要查找额外的符号以向用户显示人类可读的名称);
“-k”
参数指定调试类型为
命名管道模拟串口1”,波特率数值越高,响应越快。

自身在源码中领取了有关代码片段,如上边那些图所示,最好能把它与地点的流程图相比加深明白,
前面我会拿虚拟机上的
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” 就可见动态加卸载。

澳门金沙国际 24

澳门金沙国际 25

澳门金沙国际 26

 

 

 

澳门金沙国际 27

循规蹈矩上述措施加载时,就会自动触发大家设定好的软件断点,即可在宿主机中检查虚拟机的根本空间。
其它还需注意一点:编译驱动时的
“构建” 环境应当选用 Check
Build
,那样会一并生成同名称的标记文件,后缀为
.pdb”,从而调试器可以体现我们团结一心驱动中的函数与变量名称,提升调试效用,如下图:

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

澳门金沙国际 28

 

 

 

澳门金沙国际 29

澳门金沙国际 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 结构。

澳门金沙国际 31

澳门金沙国际 32

就此大家如若在引用目的设备对象前,仿照
IopCreateFile() 的相干逻辑来分配并开始化 OPEN_PACKET,并作为
ObReferenceObjectByName()
的参数传入,就会绕过
IopParseDevice() 的“调用源检测”逻辑。
那有些
Patch 就留待前边的小说再发表。我们脚下先要验证“设备”类对象的“ParseProcedure”确实为
IopParseDevice()。。。。。

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

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

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

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

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

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

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

一连按下
t” 单步执行,如下图所示,你可以寓目,ExAllocatePoolWithTag()
的第四个参数,分配的内核内存大小为 0x70
字节,因为我在宏定义中硬编码了这一个值,而不是用 sizeof(OPEN_PACKET)
表明式让编译器计算;另一方面,图中的 “dt” 命令也验证了它的大大小小为
0x70 字节。

澳门金沙国际 33

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

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

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

澳门金沙国际 34

澳门金沙国际 35

 

 

 

澳门金沙国际 36

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

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

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

澳门金沙国际 37

澳门金沙国际 38

澳门金沙国际 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(),而后者却无计可施追踪。。。。是虚拟机环境的原委,依旧原子操作类函数的不可分割性质?

澳门金沙国际 40

 

 

 

澳门金沙国际 41

澳门金沙国际 42

下卷文章将琢磨如何绕过
IopParseDevice() 的调用源检测,并调节大家的收获,将其拔取于 rootkit
开发技术中。

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

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

 

讲一些废话,一般我们在栈回溯中看出的顶层表明行,有一个 “Args to Child” 项目,表示调用者传递给它的参数,但是最多也只能突显前七个。

讲一点废话,一般大家在栈回溯中见到的顶层表达行,有一个 “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_STRING
结构的地方,对应源码定义中的一枚 _UNICODE_STRING
指针,该社团中贮存的是咱们驱动在注册表中的完整路径:

列出的数量一定于履行解引操作符 *
后的结果
),第四个参数是 UNICODE_STRING
结构的地方,对应源码定义中的一枚 _UNICODE_STRING
指针,该协会中贮存的是大家驱动在注册表中的完整路径:

 

 

澳门金沙国际 43
——————————————————————————————————————————————————————————————————

澳门金沙国际 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 设备的 IRP
内数据。。。。那种方式已经相比过时了,因为前些天反病毒软件的水源方式组件也会检查那个设备栈,寻找其他匹配特征码的恶意设备,再者,内核调试器的
“!devstack”
命令很不难遍历揭破出给定设备所在的设备栈内容,被大面积用于总计机调查取证中,从
rootkit 的重中之重目的——完成隐身——的角度来看, attach
到装备栈就不是一个好规范。

顺手说一下,按照 A 设备名得到 A 设备对象的指针,然后把
rootkit/自己驱动创造的恶意设备 attach 到 A
设备所在的装备栈,从而阻碍检查通过 A 设备的 IRP
内数据。。。。那种方法已经相比较过时了,因为后天反病毒软件的基本方式组件也会检讨这么些设施栈,寻找其他匹配特征码的黑心设备,再者,内核调试器的
“!devstack”
命令很简单遍历揭露出给定设备所在的装置栈内容,被广泛用于总结机调查取证中,从
rootkit 的紧要性目标——达成隐身——的角度来看, attach
到设备栈就不是一个好规范。

相反,通过 ObReferenceObjectByName()
总是能够收获驱动对象的指针,进而可以 hook 该驱动的 IRP
分发例程,那种手法隐蔽性极高,而且不不难被检测出来。

反倒,通过 ObReferenceObjectByName()
总是可以收获驱动对象的指针,进而可以 hook 该驱动的 IRP
分发例程,那种手法隐蔽性极高,而且不便于被检测出来。

三番五次的博文将商讨哪边将那种技术用在
rootkit 中,同时适应现阶段风靡的对称多处理器(SMP)环境。

接轨的博文将研讨什么将这种技术用在
rootkit 中,同时适应现阶段风行的对称多处理器(SMP)环境。

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

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

相关文章