菜单

———– Rootkit 宗旨技术之绕过 IopParseDevice() 调用源检查和测试逻辑 —————

2019年3月25日 - www6165com

 

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

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

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

在上一篇文章中,我们曾经见到 IopParseDevice() 如何对传播的 OPEN_PACKET
结构举行认证。假诺 ObReferenceObjectByName()
的调用者没有分配并早先化第7个参数 ParseContext,而仅是总结地传出 “NULL”
,那么当调用链深刻到 IopParseDevice()
内部时,就会因验证战败重临 C0000024(STATUS_OBJECT_TYPE_MISMATCH)。

咱俩依据源码中的暗示来追踪 OPEN_PACKET
结构究竟在哪分配的,如前所述,调用链
NtCreateFile->IoCreateFile()->IopCreateFile() 的末梢,也便是在
IopCreateFile() 内部,实际负担 OPEN_PACKET
的初叶化。上边贴出的代码片段以 NT 5.2 版内核源码为样例:

 

金沙国际 1

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

那边还有2个难题,负责分配该组织体内核内部存款和储蓄器的例程 IopAllocateOpenPacket()
是3个宏,Visual C++ 二零一五 中付出它是用 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
个中1个字段使得编写翻译器为表明式 “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”,Porter率数值越高,响应越快。

把重新编写翻译好的驱动放到虚拟机中,在升级权限后的指令提示符中执行
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

栈的顶层函数 “ReferenceDeviceAndHookISportagePdispatchRoutine+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 字节。

第13个传入的参数 “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_ST本田CR-VING 结构的地方,对应源码定义中的一枚 _UNICODE_ST库罗德ING
指针,该组织中贮存的是我们驱动在注册表中的完整路径:

 

金沙国际 11
——————————————————————————————————————————————————————————————————

简单的讲 ,基于上述理由作者不能继续跟进到 ObpLookupObjectName()
里面查看它是或不是执行了 IopParseDevice()
回调,从而不能够明确究竟怎么后者重返 C0000024。

作者想也许是因为基本源码版本的变通,导致相关例程的判断逻辑也不均等了,无法依据前一版源码的逻辑来编排估摸运营在后一版内核上的驱动。
其实化解方案如故有个别,相比花时间而已,正是选拔 “u” 指令反汇编
ObpLookupObjectName() 初阶处对应的机器指令,再反编写翻译成类似的 C 伪码,与
NT 5.2
版内核源码比较,找出在那之中改动的地方,但那是二个费时费劲的做事,且收入甚微,还不及直接在网络上搜释出的
NT 6.1 版内核源码,大概接近的版本,再思索绕过的法门。

顺带说一下,依照 A 设备名获得 A 设备对象的指针,然后把
rootkit/自身驱动创制的恶意设备 attach 到 A
设备所在的装置栈,从而阻碍检查通过 A 设备的 I奥德赛P
内数据。。。。那种艺术已经相比过时了,因为前些天反病毒软件的根本情势组件也会检讨那么些设备栈,寻找其他匹配特征码的恶心设备,再者,内核调节和测试器的
“!devstack”
命令很简单遍历揭穿出给定设备所在的配备栈内容,被周边用于计算机调查取证中,从
rootkit 的主要目的——完毕隐身——的角度来看, attach
到装备栈就不是二个好规范。

相反,通过 ObReferenceObjectByName()
总是能够得到驱动对象的指针,进而能够 hook 该驱动的 I汉兰达P
分发例程,那种手段隐蔽性极高,而且不不难被检查和测试出来。

接轨的博文将斟酌哪些将那种技能用在 rootkit
中,同时适应当前流行的相得益彰多处理器(SMP)环境。

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

Rootkit 焦点技术之绕过
IopParseDevice() 调用源检查和测试逻辑,
—————————————————————————————————…

金沙国际 12

只顾,NT
6.1 版内核在编写翻译时刻的 OPEN_PACKET 结构明显是未经 “恶意
修改的,所以编写翻译器为其 “sizeof(OPEN_PACKET)” 表明式计算 0x70
的值,而我们在投机的驱动中拿掉了 OPEN_PACKET
个中二个字段使得编写翻译器为表达式 “sizeof(OPEN_PACKET)” 预总括 0x58
的值(后边的调节和测试阶段会注明),那会导致 “Size” 字段不是
IopParseDevice() 内部逻辑预期的 0x70,从而造成重返C0000024(STATUS_OBJECT_TYPE_MISMATCH)。

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

随之调用
ObReferenceObjectByName(),该函数把获得的目的对象地址存款和储蓄到它的尾声2个参数(指针)中,然后重临给调用者。
实战时大家会发觉,引用
_DRIVER_OBJECT 差不离连接成功;而引用
_DEVICE_OBJECT,则不自然会大功告成,重返的 NTSTATUS
状态码一般以三种居多:

 

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

金沙国际 13

金沙国际 14
——————————————————————————————————————————————————————————————————

其实消除方案照旧有的,相比花时间而已,正是运用 “u” 指令反汇编
ObpLookupObjectName() 初阶处对应的机器指令,再反编写翻译成类似的 C 伪码,与
NT 5.2
版内核源码相比较,找出里面改动的地点,但那是二个费时费劲的劳作,且收入甚微,还比不上直接在互连网上搜释出的
NT 6.1 版内核源码,恐怕接近的本子,再思索绕过的主意。

在写
filter driver 或 rootkit 时,日常索要 attach
到装备栈中的指标设备,来堵住途经的 IRP(I/O Request
Packet),完成过滤效果。
首先要得知目的设备向
Windows Object Manager 维护的大局名称空间注册的 _DEVICE_OBJECT
名,此类音信可以经过像是 WinObj.exe 的工具获得。

金沙国际 15

三番五次按下
t” 单步执行,如下图所示,你能够看来,ExAllocatePoolWithTag()
的第四个参数,分配的内核内部存款和储蓄器大小为 0x70
字节,因为自身在宏定义中硬编码了那么些值,而不是用 sizeof(OPEN_PACKET)
表明式让编写翻译器总结;另一方面,图中的 “dt” 命令也表明了它的分寸为
0x70 字节。

 

 

依据上述格局加载时,就会活动触发大家设定好的软件断点,即可在宿主机中反省虚拟机的水源空间。
此外还需注意一点:编写翻译驱动时的
“创设” 环境应该选拔 Check
Build
,那样会一并生成同名称的号子文件,后缀为
.pdb”,从而调节和测试器能够展现我们友好驱动中的函数与变量名称,提升调试效用,如下图:

 ObReferenceObjectByName()
是3个未公开的例程,在 MSDN 中没有文书档案描述,另一方面,包括的 ntddk.h 或
wdm.h 头文件中也尚未有关原型注明;

列出的多寡一定于实践解引操作符 *
后的结果
),第二个参数是 UNICODE_STOdysseyING
结构的地址,对应源码定义中的一枚 _UNICODE_ST昂科拉ING
指针,该协会中蕴藏的是我们驱动在注册表中的完整路径:

金沙国际 16

之所以大家只要在引用指标设备对象前,仿照
IopCreateFile() 的连锁逻辑来分配并初叶化 OPEN_PACKET,并作为
ObReferenceObjectByName()
的参数字传送入,就会绕过
IopParseDevice() 的“调用源检查和测试”逻辑。
这有的
Patch 就留待前面包车型大巴小说再公布。大家当下先要验证“设备”类对象的“ParseProcedure”确实为
IopParseDevice()。。。。。

很不幸的是,小编把编写翻译出来的驱动放到虚拟机(Windows
7,基于 NT 6.1 版内核)里面动态加载测试,依然相当的小概获得到

 

能够看出,在虚拟机中测试时,DbgPrint()
打印重临的状态码为
C0000024(STATUS_OBJECT_TYPE_MISMATCH),也正是目的类型不包容,如下图所示:

简单的说,基于上述理由作者无能为力持续跟进到 ObpLookupObjectName()
里面查看它是否执行了 IopParseDevice()
回调,从而不只怕分明毕竟怎么后者再次回到 C0000024。

金沙国际 17

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

 

自个儿想也许是因为基础源码版本的变化,导致相关例程的判断逻辑也不均等了,不可能依据前一版源码的逻辑来编排揣度运营在后一版内核上的驱动。

今昔你知道为啥
ObReferenceObjectByName()
引用指标设备连接令人那样蛋疼,关键就在急需分配并伊始化那一个 ParseContext。。。

 

反倒,通过 ObReferenceObjectByName()
总是能够收获驱动对象的指针,进而能够 hook 该驱动的 I奥迪Q5P
分发例程,那种手法隐蔽性极高,而且不便于被检查和测试出来。

1 C0000022(STATUS_ACCESS_DENIED)
2 C0000024(STATUS_OBJECT_TYPE_MISMATCH)

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

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

 

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

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

”,宿主机器上运维水源调节和测试器 kd.exe,笔者的开发银行参数像是那样:

上航海用体育场面中有两处关键点:其一是
ObpLookupObjectName() 中,检核对象对象类型的开首化设定(用
_OBJECT_TYPE_INITIALIZER 结构意味着)中,是不是钦赐了
ParseProcedure
例程,对于“设备”类对象,该函数值指针总是为 IopParseDevice() ,最终致使调用
IopParseDevice()

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

把重新编写翻译好的驱动放到虚拟机中,在进步权限后的指令提醒符中执行
bcdedit.exe,启用调试形式,那样重启虚拟机后,就会进去调节和测试格局(无需在运转进度中按下
F8 选拔菜单)。

 

讲一点废话,一般我们在栈回溯中见到的顶层表达行,有三个 “Args to Child” 项目,表示调用者传递给它的参数,可是最多也不得不显示前多少个。

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

金沙国际 18

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

 

金沙国际 19

nt!ExInterlockedPopEntrySList()
调用时,kd.exe
就卡住了,不能持续追踪此后的调用链。从稍早的栈回溯音信来看,与源码令月我们臆想的调用体系大约相符,只是不精通为什么在
nt!ObpAllocateObjectNameBuffer() 中,为了给传入的驱动对象名称
“\Device\QQProtect”
分配内核内部存款和储蓄器,调用 nt!ExInterlockedPopEntrySList(),而后人却力不从心追踪。。。。是虚拟机环境的来头,照旧原子操作类函数的不可分割性质?

 


IopParseDevice()
内部的那段注释,作者不明获得了绕过调用源检查和测试的思绪——那正是跟踪
NtCreateFile() ,看看 OPEN_PACKET 具体是在何地

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

 

正要手边有一份
NT 5.2 版内核的源码,它用来编写翻译 Windows XP/Server 二零零零使用的基本,尽管与自个儿的测试机器的 NT 6.1 版内核有所差异,然而
抑或姑且来看下
ObReferenceObjectByName() 内部毕竟干了些什么。ObReference*()
体系的例程多数坐落内核源码的“obref.c” 与“obdir.c
文本内。通过对相关调用链的剖析,如下图所示:

相关文章

发表评论

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

网站地图xml地图