核心技术之绕过,调用源检测逻辑

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

———– 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++ 二零一四 中提交它是用 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()
时,为第3个参数传入“openPacket” 就能够,上海教室显示的很理解了。

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

很倒霉的是,我把编写翻译出来的驱动放到虚构机(Windows 7,基于 NT 6.1
版内核)里面动态加载测验,依旧不能获得到

“DeviceQQProtect” 相应的设备对象指针,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=\.pipecom_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

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

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

图片 8

 

自己不想浪费时间在查阅内核内存的分红细节上,所以本人按下 “p”,步过
ExAllocatePoolWithTag() 函数调用,接下去的 cmp/jne
汇编系列
对应源码中反省是或不是中标分配了内部存款和储蓄器并用于 openPacket
指针,实际的实践结果是跳转到地址 0x8f4a31c6 ,对应源码中最早化
OPEN_PACKET 结构前八个字段的逻辑:

图片 9

接下来直白单步实施到调用 ObReferenceObjectByName()
前夕,在此处我们要 “步入” 它的中间,进行故障排查,所以按下 “t
跟进,这里有八个小技术,我们早已深入分析过 ObReferenceObjectByName()
的源码,知道它会调用相当多函数,何况大约精晓难题出现在
ObpLookupObjectName() 里面,所以指令
tc”可以追踪到各样函数调用处甘休,再由顾客决定是还是不是跟进该函数内部。

那是自身的美好梦想,但具中华全国体育总会是暴虐的,在本身追踪到原子操作种类函数

nt!ExInterlockedPopEntrySList() 调用时,kd.exe
就卡住了,无法持续追踪此后的调用链。从稍早的栈回溯新闻来看,与源码花月咱们估量的调用系列大概相符,只是不领会为什么在
nt!ObpAllocateObjectNameBuffer() 中,为了给传入的驱动对象名称
“DeviceQQProtect”
分配内核内部存款和储蓄器,调用 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君越ING 结构的地点,对应源码定义中的一枚 _UNICODE_STPRADOING
指针,该组织中存款和储蓄的是大家驱动在注册表中的完整路径:

 

图片 11
——————————————————————————————————————————————————————————————————

一言以蔽之 ,基于上述理由小编敬谢不敏持续跟进到 ObpLookupObjectName()
里面查看它是还是不是试行了 IopParseDevice()
回调,进而不可能明确到底为啥后面一个重临 C0000024。

自身想恐怕是因为基本源码版本的变通,导致相关例程的论断逻辑也分歧了,不可能依照前一版源码的逻辑来编排估算运营在后一版内核上的驱动。
其实应用方案照旧有个别,相比花时间而已,正是运用 “u” 指令反汇编
ObpLookupObjectName() 起头处对应的机器指令,再反编写翻译成类似的 C 伪码,与
NT 5.2
版内核源码相比,搜索个中更改的地点,但这是三个费时费事的干活,且收入甚微,还不及直接在网络络搜释出的
NT 6.1 版内核源码,也许接近的本子,再考虑绕过的法子。

顺带说一下,依据 A 设备名得到 A 设备对象的指针,然后把
rootkit/本身驱动创设的恶心设备 attach 到 A
设备所在的设施栈,进而阻碍检查通过 A 设备的 IRP
内数据。。。。这种办法已经比较过时了,因为以往反病毒软件的水源形式组件也会检查这么些设备栈,搜索别的相配特征码的恶心设备,再者,内核调节和测验器的
“!devstack”
命令很轻巧遍历揭露出给定设备所在的装置栈内容,被大规模用于Computer考查取证中,从
rootkit 的根本指标——实现隐身——的角度来看, attach
到装备栈就不是一个好标准。

相反,通过 ObReferenceObjectByName()
总是能够赢得驱动对象的指针,进而可以 hook 该驱动的 IRP
分发例程,这种手腕掩瞒性极高,何况不易于被检查测量试验出来。

持续的博文将研讨哪边将这种技术用在 rootkit
中,同期适应现阶段风靡的相得益彰多管理器(SMP)意况。

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

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++ 二〇一五 中提交它是用 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 版内核)里面动态加载测量检验,还是不恐怕获得到

“DeviceQQProtect”
相应的设施对象指针,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=\.pipecom_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

发表评论

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