菜单

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

2019年4月7日 - 皇家赌场系统

 

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

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

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

在上1篇文章中,大家早已观察 IopParseDevice() 怎样对传播的 OPEN_PACKET
结构举行认证。若是 ObReferenceObjectByName()
的调用者未有分配并起首化第4个参数 ParseContext,而仅是简约地流传 “NULL”
,那么当调用链深入到 IopParseDevice()
内部时,就会因验证战败重临 C00000二4(STATUS_OBJECT_TYPE_MISMATCH)。

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

 

系统 1

约等于说,大家一向复制 IopCreateFile() 中的 OPEN_PACKET
结构开首化部分逻辑就行了?

此处还有一个标题,负责分配该协会体内核内部存款和储蓄器的例程 IopAllocateOpenPacket()
是三个宏,Visual C++ 20一五 中付出它是用 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 六.一 版内核在编写翻译时刻的 OPEN_PACKET 结构分明是未经 “恶意
修改的,所以编写翻译器为其 “sizeof(OPEN_PACKET)” 表明式总结 0x70
的值,而小编辈在祥和的驱动中拿掉了 OPEN_PACKET
在那之中二个字段使得编写翻译器为表明式 “sizeof(OPEN_PACKET)” 预总括 0x5八的值(前边的调节和测试阶段会注明),那会招致 “Size” 字段不是
IopParseDevice() 内部逻辑预期的 0x70,从而造成重临C00000二四(STATUS_OBJECT_TYPE_MISMATCH)。

消除办法也很简短,大家的驱动中,不要借助理编辑译时刻的计量,间接把
Size” 字段的值硬编码为 0x70 不就好了?

正如图所示,你还会专注到,作者把 “Type” 字段的常量
“IO_TYPE_OPEN_PACKET” 改成了相应的数值,以管教1旦。

 

系统 4

 

除此以外,由于 IopAllocateOpenPacket() 等价于
ExAllocatePoolWithTag(),而后人经常再次回到泛型指针(“ PVOID ,亦即 
void * ”),
因而作者强制把它转型为与 “openPacket” 壹致的花色。
齐全,“东风” 就在于调用 ObReferenceObjectByName()
时,为第七个参数字传送入“openPacket” 即可,上海体育场合显示的很清楚了。

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

很颓废的是,笔者把编写翻译出来的驱动放到虚拟机(Windows 柒,基于 NT 6.一版内核)里面动态加载测试,照旧不能获取到

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

为了找出故障原因,笔者在分配 OPEN_PACKET
逻辑的前头利用内联汇编添加了1个软中断 “__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,启用调节和测试情势,那样重启虚拟机后,就会跻身调节和测试方式(无需在开发银行进程中按下
F八 采纳菜单)。

笔者把本人的驱动实现成按需加载,也便是应用劳动控制管理器sc.exe)发出命令来动态加载和卸载,完毕此作用的相应批处理公事内容如下图,注意该文件要放在虚拟机中推行,“start=
demand” 申明通过 sc.exe 按需运转
;“binpath”
正是驱动文件存放的磁盘路径
,要是本人的驱动名为hideprocess.sys,执行该批处理职务后,就在有关的注册表地方添加了1项,未来只需在
cmd.exe 中实施 “sc.exe start/stop hideprocess” 就可见动态加卸载。

系统 5

 

根据上述措施加载时,就会活动触发大家设定好的软件断点,即可在宿主机中检查虚拟机的根本空间。
除此以外还需注意一点:编译驱动时的 “营造” 环境应该采纳 Check
Build
,那样会壹并生成同名称的符号文件,后缀为
.pdb”,从而调节和测试器能够展现我们协调驱动中的函数与变量名称,提升调节和测试效用,如下图:

 

系统 6

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

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

 

系统 7

栈的顶层函数 “ReferenceDeviceAndHookI哈弗PdispatchRoutine+0x56
是笔者添加软中断的地点。执行 “r” 命令查看当前的 x八陆通用寄存器状态,EIP 指向地址 0x八f4a31九陆 ,执行 “u
hideprocess!ReferenceDeviceAndHookIRPdispatchRoutine+0x56
L2”,反汇编输出的首先行地址正是 0x八f四a31玖陆,与 EIP
的值相符;第2行是把贰个 16 进制值 “ 704F6F49h” 压栈,实际上它是
ASCII 字符 “pOoI” 的 1陆进制编码,换言之,那是在通过内核栈传递 ExAllocatePoolWithTag()
的第一个参数(从右往左传递,请纪念此前的 IopAllocateOpenPacket()
宏定义那张图)。

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

持续按下 “t
单步执行,如下图所示,你能够看看,ExAllocatePoolWithTag()
的第1个参数,分配的基行业内部部存款和储蓄器大小为 0x70
字节,因为小编在宏定义中硬编码了那几个值,而不是用 sizeof(OPEN_PACKET)
表明式让编译器计算;另一方面,图中的 “dt” 命令也作证了它的轻重为
0x70 字节。

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

系统 8

 

自家不想浪费时间在查看内核内部存款和储蓄器的分红细节上,所以作者按下 “p”,步过
ExAllocatePoolWithTag() 函数调用,接下去的 cmp/jne
汇编系列
对应源码中检查是还是不是中标分配了内部存款和储蓄器并用于 openPacket
指针,实际的实施结果是跳转到地址 0x八f四a3一c陆 ,对应源码中早先化
OPEN_PACKET 结构前七个字段的逻辑:

系统 9

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

那是小编的美美梦想,但实际总是无情的,在自个儿跟踪到原子操作体系函数

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

 

系统 10

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

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

列出的数据一定于实践解引操作符 * 后的结果),第贰个参数是
UNICODE_ST卡宴ING 结构的地址,对应源码定义中的1枚 _UNICODE_STLacrosseING
指针,该协会中存款和储蓄的是大家驱动在注册表中的完整路径:

 

系统 11
系统,——————————————————————————————————————————————————————————————————

简单的讲 ,基于以上理由作者不能持续跟进到 ObpLookupObjectName()
里面查看它是还是不是执行了 IopParseDevice()
回调,从而不可能显明到底怎么后者重返 C000002肆。

本身想可能是因为基本源码版本的更动,导致相关例程的论断逻辑也不一致了,不可能依照前一版源码的逻辑来编排估计运营在后一版内核上的驱动。
其实化解方案大概有个别,相比较花时间而已,正是利用 “u” 指令反汇编
ObpLookupObjectName() 初叶处对应的机器指令,再反编译成类似的 C 伪码,与
NT 五.贰版内核源码相比,找出在那之中改动的地方,但那是三个费时费劲的做事,且收入甚微,还比不上直接在网络上搜释出的
NT 6.1 版内核源码,或然接近的本子,再考虑绕过的艺术。

顺带说一下,依照 A 设备名取得 A 设备对象的指针,然后把
rootkit/自个儿驱动创制的黑心设备 attach 到 A
设备所在的装置栈,从而阻碍检查通过 A 设备的 IKoleosP
内数据。。。。那种措施已经比较过时了,因为今天反病毒软件的内核情势组件也会检讨这几个装备栈,寻找其余相称特征码的恶意设备,再者,内核调节和测试器的
“!devstack”
命令很容易遍历揭穿出给定设备所在的装置栈内容,被广泛用于总括机考察取证中,从
rootkit 的重大指标——实现隐身——的角度来看, attach
到设备栈就不是一个好规范。

相反,通过 ObReferenceObjectByName()
总是能够获得驱动对象的指针,进而能够 hook 该驱动的 ICR-VP
分发例程,那种手法隐蔽性极高,而且不易于被检查测试出来。

持续的博文将琢磨怎么着将那种技能用在 rootkit
中,同时适应现阶段盛行的对称多处理器(SMP)环境。

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

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

密切考查前方的图形可见,从先前时代小编调用
ObReferenceObjectByName() 初阶,就为它的第十二个参数 ParseContext 传入 NULL,而 ParseContext
会在调用链中一路往下传递,最后由
IopParseDevice() 接受并对该参数实行验证,尽管它为空,就回来
STATUS_OBJECT_TYPE_MISMATCH

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

栈的顶层函数
“ReferenceDeviceAndHookI卡宴PdispatchRoutine+0x56
是本身添加软中断的地点。执行 “r” 命令查看当前的 x8陆通用寄存器状态,EIP 指向地址 0x8f四a3196 ,执行 “u
hideprocess!ReferenceDeviceAndHookIRPdispatchRoutine+0x56
L2”,反汇编输出的首先行地址正是 0x八f4a3196,与 EIP
的值相符;第一行是把贰个 1陆 进制值 “ 704F6F49h” 压栈,实际上它是
ASCII 字符 “pOoI” 的 16进制编码,换言之,那是在通过内核栈传递 ExAllocatePoolWithTag()
的第6个参数(从右往左传递,请纪念在此之前的 IopAllocateOpenPacket()
宏定义那张图)。

系统 12

系统 13

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

 

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

系统 14

上海教室中有两处关键点:其1是
ObpLookupObjectName() 中,检核查象对象类型的早先化设定(用
_OBJECT_TYPE_INITIALIZER 结构意味着)中,是还是不是钦赐了
ParseProcedure
例程,对于“设备”类对象,该函数值指针总是为 IopParseDevice() ,最后造成调用
IopParseDevice()

系统 15

系统 16

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

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

第3个传入的参数
NonPagedPool
为不可换页池,其内的数目不恐怕被换出物理内部存款和储蓄器,该常量对应的数值为
“0”:

内需建议,既然经过
ObReferenceObjectByName() 引用绝大多数 _DRIVER_OBJECT
都会成功,而且 _DRIVER_OBJECT.DeviceObject
又针对该驱动创造的设备链中第叁个
_DEVICE_OBJECT,那么那正是最妥当的主意。不过我们仍然要通晓
STATUS_OBJECT_TYPE_MISMATCH 的原因。

在上1篇小说中,我们早就见到
IopParseDevice() 怎样对传播的 OPEN_PACKET 结构实行验证。要是ObReferenceObjectByName() 的调用者未有分配并起始化首个参数
ParseContext,而仅是大致地传来 “NULL” ,那么当调用链深远到
IopParseDevice()
内部时,就会因验证失利再次来到 C00000二四(STATUS_OBJECT_TYPE_MISMATCH)。

很不幸的是,作者把编写翻译出来的驱动放到虚拟机(Windows
7,基于 NT 陆.一 版内核)里面动态加载测试,照旧不能够获得到

 

 

 

分配并开首化的;由于
IopParseDevice() 会检测 POPEN_PACKET 结构实例的壹些字段来担保
ObReferenceObjectByName() 调用
是从
NtCreateFile() 发起的,NtCreateFile() 完毕在 NT 5.2 版内核源码的
creater.c
中,它只是简单地实施调用链
IoCreateFile()->IopCreateFile()(此两例程都完毕在源码的 iosubs.c 中),而具体由
IopCreateFile() 分配并初阶化 OPEN_PACKET 结构。

小心,NT
陆.1 版内核在编写翻译时刻的 OPEN_PACKET 结构显然是未经 “恶意
修改的,所以编写翻译器为其 “sizeof(OPEN_PACKET)” 表达式计算 0x70
的值,而大家在融洽的驱动中拿掉了 OPEN_PACKET
在那之中1个字段使得编写翻译器为表达式 “sizeof(OPEN_PACKET)” 预总结 0x58的值(前面的调剂阶段会评释),那会导致 “Size” 字段不是
IopParseDevice() 内部逻辑预期的 0x70,从而导致再次来到C00000贰4(STATUS_OBJECT_TYPE_MISMATCH)。

此起彼伏的博文将切磋什么将那种技能用在
rootkit 中,同时适应当下盛行的对称多处理器(SMP)环境。

因而大家只要在引用指标设备对象前,仿照
IopCreateFile() 的相关逻辑来分配并起先化 OPEN_PACKET,并作为
ObReferenceObjectByName()
的参数字传送入,就会绕过
IopParseDevice() 的“调用源检验”逻辑。
那有的
Patch 就留待后边的随笔再发布。大家当下先要验证“设备”类对象的“ParseProcedure”确实为
IopParseDevice()。。。。。

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

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

系统 17

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

列出的数码一定于实践解引操作符 *
后的结果
),第二个参数是 UNICODE_ST酷威ING
结构的地址,对应源码定义中的一枚 _UNICODE_ST奥迪Q5ING
指针,该组织中储存的是我们驱动在注册表中的完整路径:


IopParseDevice()
内部的那段注释,俺隐隐获得了绕过调用源检查实验的思路——那正是跟踪
NtCreateFile() ,看看 OPEN_PACKET 具体是在哪个地方

然而,OPEN_PACKET
结构中单单八个字段不是 “原生” 定义的——那正是 “PDUMMY_FILE_OBJECT”
类型,要求包括其余头文件才不造成编写翻译器报错。

 

第两种情形普遍出现在经过
ObReferenceObjectByName() 引用有些 _DEVICE_OBJECT
的风貌中,缘由与 ObReferenceObjectByName()
利用别的执行体组件例程,在大局名称空间中实行的名字查找逻辑缜密相关,前边会分解。

本身把本人的驱动实现成按需加载,也等于运用服务控制管理器sc.exe)发出指令来动态加载和卸载,完结此作用的照应批处理文件内容如下图,注意该公文要放在虚拟机中施行,“start=
demand” 证明通过 sc.exe 按需运维
;“binpath”
便是驱动文件存放的磁盘路径
,若是自身的驱动名称叫hideprocess.sys,执行该批处理职务后,就在有关的注册表地点添加了壹项,以后只需在
cmd.exe 中履行 “sc.exe start/stop hideprocess” 就可见动态加卸载。

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

下卷小说将斟酌哪边绕过
IopParseDevice() 的调用源检查测试,并调试大家的结晶,将其使用于 rootkit
开发技术中。

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

讲壹些废话,壹般大家在栈回溯中看到的顶层表明行,有1个 “Args to Child” 项目,表示调用者传递给它的参数,可是最多也只能展现前三个。

1 C0000022(STATUS_ACCESS_DENIED)
2 C0000024(STATUS_OBJECT_TYPE_MISMATCH)

系统 18

 

 

依据上述方式加载时,就会活动触发我们设定好的软件断点,即可在宿主机中检查虚拟机的根本空间。
除此以外还需注意一点:编写翻译驱动时的
“创设” 环境应该选拔 Check
Build
,那样会一并生成同名称的符号文件,后缀为
.pdb”,从而调试器能够突显我们温馨驱动中的函数与变量名称,升高调节和测试功能,如下图:

nt!ExInterlockedPopEntrySList()
调用时,kd.exe
就卡住了,不可能继续追踪此后的调用链。从稍早的栈回溯信息来看,与源码卯月我们臆度的调用类别大致相符,只是不清楚为何在
nt!ObpAllocateObjectNameBuffer() 中,为了给传入的驱动对象名称
“\Device\QQProtect”
分配内核内部存款和储蓄器,调用 nt!ExInterlockedPopEntrySList(),而后者却无力回天追踪。。。。是虚拟机环境的案由,依然原子操作类函数的不可分割性质?

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

顺便说一下,依照 A 设备名获得 A 设备对象的指针,然后把
rootkit/自身驱动创制的恶意设备 attach 到 A
设备所在的装置栈,从而阻碍检查通过 A 设备的 I大切诺基P
内数据。。。。那种艺术已经比较过时了,因为未来反病毒软件的根本形式组件也会检讨那些装备栈,寻找别的匹配特征码的恶心设备,再者,内核调节和测试器的
“!devstack”
命令很简单遍历揭破出给定设备所在的配备栈内容,被普遍用于总结机调查取证中,从
rootkit 的第2目的——完毕隐身——的角度来看, attach
到装备栈就不是三个好规范。

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

 

系统 19

附带说一下,依据 A 设备名获得 A 设备对象的指针,然后把
rootkit/本身驱动创制的恶心设备 attach 到 A
设备所在的设施栈,从而阻碍检查通过 A 设备的 I昂CoraP
内数据。。。。那种方法已经相比较过时了,因为今后反病毒软件的水源情势组件也会检查这一个设施栈,寻找别的相配特征码的黑心设备,再者,内核调试器的
“!devstack”
命令很简单遍历揭破出给定设备所在的设施栈内容,被广大用于总结机调查取证中,从
rootkit 的关键目的——达成隐身——的角度来看, attach
到装备栈就不是三个好规范。

 

相关文章

发表评论

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

网站地图xml地图