正文
针对函数 fun_hookProNtDCompositionCreateChannel 的 hook,其会监控第一次 NtDCompositionCreateChannel 的创建,并获取该 Channel 对应的 pMappedAddress,保存为 pointer_pMappedAddress。
针对函数 NtDCompositionCommitChannel 的 hook 就比较复杂了,通过前文我们知道 NtDCompositionCommitChannel 用于生成批处理命令 bufer 并发送到 DWM 进程,这里同样hook的时候会确保是第一次 NtDCompositionCommitChannel 的调用,并在 pointer_specialHeapalloc 这段前面堆分配中获取的内存中搜索 0x120 这个字段,找到将该字段修改为 var_contrulLen,不同环境下的利用有所区别,这里是 0x23f(var_contrulLen(0x1B0)+0x8f),之后 0x23f/0x90 = 4,看到这里我们基本可以猜测,这个位置的 0x23f 就是漏洞中攻击者控制的 len_control。
根据 0x23f/0x90 = 4 进行 4 次拷贝,拷贝的内容为 len_control+0x2c 处开始长度为 0x90 的内存,向后依次保存,此外拷贝的内容中会有两处地址的值被设置为 0。
之后调用四次 NtDCompositionProcessChannelBatchBuffer,这里使用的命令是 SetResourceIntegerProperty,资源号从 1-4,资源对应的 Propertyid 4 被设置为值 1000。
hook 完成后,通过 fun_trigger 函数触发漏洞完成提权操作。
fun_trigger 中首先注册了一个窗口类,调用函数 fun_windowsInit 完成相关窗口的后续定义工作,然后调用函数 DirectComposition::CDevice::Commit。
首先来看 fun_windowsInit,根据前面注册的窗口类,创建了一个名为 test 的窗口。
之后的代码比较复杂,总结起来就是通过 D2D1CreateFactory 创建了一个 2d 绘图的工厂接口,使用工厂接口创建了一个 surface,一个 visual,这里 surface 可以简单理解为一张画布,visual 则可以理解为一个画框,在画布上完成绘制后,放入该 visual 画框,并和前面的 test 窗口关联起来,并在 fun_windowsInit 返回后通过函数 DirectComposition::CDevice::Commit 将当前的 DirectComposition 场景提交给图形硬件进行渲染。
完成 test 窗口图形硬件绘制后,释放出后续的 s1.dll。
根据前面探测的版本进入指定版本的利用流程,这里我们进入 var_osVersiontype=2 的利用类型,函数 NtDCompositionCreateChannel 开启一个 Channel,通过该 Channel 调用 NtDCompositionProcessChannelBatchBuffer,该函数调用 0x10000 次,每次调用传入的指令是 CreateResource,对应创建的资源 id从0x14 到 0x10014,资源类型为 CHolographicInteropTextureMarshaler。
之后同样通过 NtDCompositionProcessChannelBatchBuffer 调用 ReleaseResource 命令释放前面创建的资源,被释放目标资源的 id 从 0x14-0x7000,以 0x20 作为间隔,可以看到这里是一个明显的 spray操作,该步骤结束之后,这片连续的 CHolographicInteropTextureMarshaler 内存中将每隔 0x20 个 CHolographicInteropTextureMarshaler 对象出现一个 hole。
获取前面 hook 中分配的内存指针 pointer_specialHeapalloc 并初始化其指定偏移 v35 之后的内存,将 v35 作为参数传递给函数 fun_gadgetInit,之后调用函数 ShowWindow 将前面通过 DirectComposition::CDevice::Commit 绘制好的图像通过配置好的窗口test显示。
fun_gadgetInit 函数的内容很简单,用于配置前面传入的 v35 中的内容,其中重要的位置有三处:
最后通过函数 NtDCompositionProcessChannelBatchBuffer 再次调用 ReleaseResource,将剩余的资源释放。
通过静态分析可知漏洞本身是一处越界写入,攻击者利用 spray 的方式,疑似通过越界写入来修改 CHolographicInteropTextureMarshaler 资源,但是具体到利用的细节却又不少疑问:
-
-
hook 中的 pointer_specialHeapalloc 扮演了一个什么样的角色,该内存之后 v35 的内存布局意义何在?
-
攻击者控制的长度是如何修改并被最终使用导致越界的?
-
漏洞是否是通过破坏 CHolographicInteropTextureMarshaler 来实现的代码执行?
-
具体触发漏洞写入的位置在哪里,触发代码执行的位置又在何处?
带着以上的疑问,我们开始相关的调试,首先是四个 hook 函数的部分,这里以函数 NtDCompositionCommitChannel 为例,NtDCompositionCommitChannel 函数调用如下所示,通过 syscall 进入后续的内核部分。
hook 之后可以看到对应的部分被修改为一个 jmp 跳转,最终指向 hookpro 函数。
而这里返回地址中则保存了前面 hook 时被 jmp 覆盖的两条指令,最终指回之前的 syscall 代码部分,从而完成 hook 返回。
分别对这四个 hookpro 函数下断点,来看看具体利用样本中是在哪些关键的地方触发了 hook,首先是针对 NtDCompositionCreateChannel 函数的 hook,该 hook 指会在第一次的调用时触发,通过堆栈回溯可以看到触发第一次 NtDCompositionCreateChannel hook 的位置在 fun_windowsInit 的 DCompositionCreateDevice 位置处。
而触发另外三个函数 hook 的皆在 DirectComposition::CDevice::Commit 将当前的 DirectComposition 场景提交给图形硬件进行渲染的过程中,具体的函数调用流程如下所示。