返回 TI 主页

综述

CVE-2024-30051 漏洞最早由卡巴斯基发现,但是最初卡巴发现的并不是在野样本,而是该漏洞的简单分析报告被传到 vt 上去了,之后我们通过相关报告中漏洞的特征,找到了 QakBot 利用的实际样本(c4dd780560091c8d2da429c7c689f84b),其运行效果如下所示,有意思的是泄露的分析报告和该漏洞样本在 vt 的出现时间分别是 2024/04/01 和 2024/04/18,考虑到样本的实际攻击流程和文档中描述几乎一致,且 18 天时间过于短暂,我们认为该样本不是第三方攻击者在获取该文档后开发,应该就是文档中描述的原版漏洞利用样本。

漏洞的核心问题出在 dwmcore.dll这个dwm 进程的核心 dll 中,具体问题在函数 CCommandBuffer::Initialize 中,可以看到这里 mempy 操作中的长度由攻击者控制 len_control,目标地址的内存空间分配时 size=(len_control/0x90)*0x90,套用以上公式,如当 len_control=0x95,则实际分配的内存只有 0x90,之后 memory 时将导致 0x5 长度的越界写入。


DWM

要理解该漏洞,首先我们需要对Windows中的dwm进程有一个简单了了解,dwm 是 Desktop Window Manager 的缩写,中文译为桌面窗口管理器。它是一个 Windows 系统中的核心进程,负责管理桌面上的窗口,并提供各种视觉效果,用户可以通过 dcomp.dll 中的相关 com 接口调用控制 dwm 的相关行为,但是 dcomp.dll 对 dwm 的调用并不是直接,而是跨内核的,其流程简单来说是用户 ->dcomp.dll\ -> win32kbase(内核)\ -> dwm.exe(用户层)。

而该漏洞主要涉及 DirectComposition 对象,要触发使用 DirectComposition 对象需要使用到以下三个核心函数。

  • NtDCompositionCreateChannel
  • NtDCompositionProcessChannelBatchBuffer
  • NtDCompositionCommitChannel

创建 DirectComposition 对象,需要通过 NtDCompositionCreateChannel 系统调用创建一个通道。

typedef NTSTATUS(*pNtDCompositionCreateChannel)(
OUT PHANDLE hChannel,
IN OUT PSIZE_T pSectionSize,
OUT PVOID* pMappedAddress
);

通道创建完毕后,通过 NtDCompositionProcessChannelBatchBuffer 系统调用发送具体的调用命令,每个命令都有各自的格式,命令保存在 NtDCompositionCreateChannel 返回的 pMappedAddress 中。

typedef NTSTATUS(*pNtDCompositionProcessChannelBatchBuffer)(
IN PHANDLE hChannel,
IN DWORD dwArgStart,
OUT PDWORD pOutArg1,
OUT PDWORD pOutArg2
);

涉及的命令如下所示:

enum DCOMPOSITION_COMMAND_ID
{ 
  ProcessCommandBufferIterator, 
  CreateResource, 
  OpenSharedResource, 
  ReleaseResource, 
  GetAnimationTime, 
  CapturePointer, 
  OpenSharedResourceHandle, 
  SetResourceCallbackId, 
  SetResourceIntegerProperty, 
  SetResourceFloatProperty, 
  SetResourceHandleProperty, 
  SetResourceHandleArrayProperty, 
  SetResourceBufferProperty, 
  SetResourceReferenceProperty, 
  SetResourceReferenceArrayProperty, 
  SetResourceAnimationProperty, 
  SetResourceDeletedNotificationTag, 
  AddVisualChild, 
  RedirectMouseToHwnd, 
  SetVisualInputSink, 
  RemoveVisualChild 
};

NtDCompositionCommitChannel 生成批处理命令 bufer 并发送到 DWM 进程,总结来说:

NtDCompositionCreateChannel 函数创建一个和 dwm 的通信通道,并获取一个用于发送命令的 pMappedAddress,NtDCompositionProcessChannelBatchBuffer 函数通过通信通道,在 pMappedAddress 中设置需要使用的命令,NtDCompositionCommitChannel 触发具体的命令执行并通过 dwm 进程渲染结果。

typedef NTSTATUS(*pNtDCompositionCommitChannel)(
IN HANDLE hChannel,
OUT PDWORD out1,
OUT PDWORD out2,
IN DWORD flag,
IN HANDLE Object
);

漏洞样本分析

样本运行之后首先调用 RtlGetVersion 函数获取当前系统版本,并以此初始化后续利用中的几个核心常量。

动态获取以下函数的 api 地址。

之后通过函数 fun_hookInit/fun_hookSpecialfunInstall 完成对以下四个函数的 hook:

  • NtDCompositionCommitChannel
  • NtDCompositionCreateChannel
  • RtlCreateHeap
  • RtlAllocateHeap

这里的 hook,总结来说就是 fun_hookInit 函数完成 hook 是中间跳转代码的生成,如下所示:

fun_hookSpecialfunInstall 完成目标函数 hook。

详细来看这四个函数 hook 后进行了哪些操作,其中 RtlCreateHeap/RtlAllocateHeap 是对堆创建和分配的操作,当 RtlCreateHeap 的参数 a2 传入堆对象分配时的基址时,hookpro 保存返回的堆对像为 pointer_specialHeapobject,针对 RtlAllocateHeap 函数,当 RtlAllocateHeap 分配堆时,如果对应的堆对象为前面 RtlCreateHeap hook 时保存的 pointer_specialHeapobject 时,返回此时分配的堆地址,总结一下,这里会监控传入堆基址的堆对象的创建过程,并保存该分配的堆地址。

针对函数 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 中的内容,其中重要的位置有三处:

  • 0x0:_fnGESTURE
  • 0x50:LoadLibraryA
  • 0x58:s1.dll path

最终 v35 的内存如下所示:

最后通过函数 NtDCompositionProcessChannelBatchBuffer 再次调用 ReleaseResource,将剩余的资源释放。

通过静态分析可知漏洞本身是一处越界写入,攻击者利用 spray 的方式,疑似通过越界写入来修改 CHolographicInteropTextureMarshaler 资源,但是具体到利用的细节却又不少疑问

  1. 几处 hook 发生的位置及作用是什么?
  2. hook 中的 pointer_specialHeapalloc 扮演了一个什么样的角色,该内存之后 v35 的内存布局意义何在?
  3. 攻击者控制的长度是如何修改并被最终使用导致越界的?
  4. 漏洞是否是通过破坏 CHolographicInteropTextureMarshaler 来实现的代码执行?
  5. 具体触发漏洞写入的位置在哪里,触发代码执行的位置又在何处?

调试分析

带着以上的疑问,我们开始相关的调试,首先是四个 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 场景提交给图形硬件进行渲染的过程中,具体的函数调用流程如下所示。

DirectComposition::CDevice::Commit 中会调用 DirectComposition::CDevice::AllocateSharedMemory 生成一段 ShareMemory,该函数如下位置将触发对应的 RtlCreateHeap/RtlAllocateHeap 的 hook。

DirectComposition::CSharedSection::Create 如下所示,可以看到这里 hook 触发时 RtlCreateHeap 的基址实际上一段通过 MapViewOfFile 映射的内存,其本质上一个 CShareSection 资源,DirectComposition::CSharedSection 是 Windows DirectComposition API 中一个重要的类,它用于在不同的 DirectComposition 视觉对象之间共享内存 CSharedSection,本质上是一个内存块,它被映射到不同的进程空间中,也可用于 dwm 进程和 win32kbase.sys 中用于数据共享。

之后调用 DirectComposition::CSharedSection::Allocate,在这段 CSharedSection 上分配一段长度为 0x228 的内存,该分配的内存地址会被 RtlAllocateHeap hook 捕获并保存为 pointer_specialHeapalloc,DirectComposition::CDevice::AllocateSharedMemory 返回后 pointer_specialHeapalloc+0x74 的位置作为参数传入 DirectComposition::CPrimitiveGroup::WriteCommandBuffer 中用于设置 CommandBuffer,细心的朋友可以发现,漏洞触发核心函数为 CCommandBuffer::Initialize,正是 CCommandBuffer 对象的初始化过程,利用代码中攻击者通过 2d 的绘制图形并关联对应的窗口后渲染 DirectComposition::CDevice::Commit,当 DirectComposition::CDevice::Commit 将当前的 DirectComposition 场景提交给图形硬件进行渲染的过程中 hook 修改对应 CSharedSection 中的 CCommandBuffer 触发漏洞。

DirectComposition::CDevice::Commit 中相关函数调用流程如下所示。

可以看到当 DirectComposition::CDevice::Commit 触发 NtDCompositionCommitChannel hook 时,此时 pointer_specialHeapalloc 这段CSharedSection 内存如下:

  • 0x48处为对应的长度字段,也就是触发漏洞的核心,攻击者通过hook可以控制
  • 0x74的位置开始是CCommandBuffer,其由一个个0x90长度的内存块构成。

NtDCompositionCommitChannel hook 完成之后,对应的 control_len 已经被修改为 0x23f,后续的 CCommandBuffer 内存块被复制为 4 个,因为最终漏洞函数中 control_len/0x90 = 0x23f/0x90 = 4,内存块的数量需要满足。

CSharedSection 中恶意 CCommandBuffer 完成构造后,进入漏洞的 spray 部分,这里通过分配 0x10000 个 CHolographicInteropTextureMarshaler 资源实现 dwm 进程中堆对象的创建,可以看到一个 CHolographicInteropTextureMarshaler 对象的大小是 0x1B0。之后每隔 0x20 个资源编号释放一个 CHolographicInteropTextureMarshaler 对象,这将导致连续的 CHolographicInteropTextureMarshaler 对象堆空间中出现一个个 0x1B0 大小的内存 hole。

这里可以看到生成的 CHolographicInteropTextureMarshaler 对象的堆大小为 0x1B0。

CHolographicInteropTextureMarshaler 对象创建时的堆栈如下:

构造 pointer_specialHeapalloc 中 CommandBuffer 第四个 0x90 内存块,即前面提到的 v35。

通过 fun_gadgetInit 函数构造完成该内存块结构如下所示

当 Showwindow 函数运行,会将 DirectComposition::CDevice::Commit 函数提交给图形硬件进行渲染的效果最终在 test 窗口中展示出来,并触发漏洞函数 CCommandBuffer::Initialize。

漏洞函数 CCommandBuffer::Initialize 中攻击者控制的恶意 buffer 有第二个参数,类型为 ID2D1PrivateCompositorBuffer。

通过堆栈回溯可知该参数在函数 CD2DSharedBuffer::CreateFromSharedSection 中生成,该函数的第一个参数就是利用程序和 dwm 的共享内存,第二个参数则是对应修改的长度 23f。

CD2DSharedBuffer::CreateFromSharedSection 中 ID2D1PrivateCompositorBuffer 生成很简单,该对象长度为 0x28,以共享内存地址 /size 的格式写入。

生成的 ID2D1PrivateCompositorBuffer 如下所示

正式进入漏洞函数 CCommandBuffer::Initialize,此时的堆栈调用如下,当 showwindow 将 DirectComposition::CDevice::Commit 函数提交给图形硬件进行渲染的效果最终在 test 窗口中展示出来时,将触发漏洞函数。

CCommandBuffer::Initialize 中进入漏洞逻辑 exp len_control = 23f,(2f3/90)*0x90 =1b0,最终计算处分配的内存为 0x1b0,可以完美填充分配到前面 spray 的 CHolographicInteropTextureMarshaler 对象 hole 中,而实际需要的长度为 0x23f,这将导致 8f 长度内存的溢出。

分配 0x1b0 大小的内存。

由于前面 spray 的操作该分配的 0x1b0 大小的空间将直接分配到 CHolographicInteropTextureMarshaler 序列内存中的 hole 中,形成以下的内存形式:

CHolographicInteropTextureMarshaleri + hole + CHolographicInteropTextureMarshaleri1

此时一旦完成拷贝,CHolographicInteropTextureMarshaleri1 对象将被破坏,如下所示第一个红框就是我们分配的 0x1b0 的目标拷贝地址,而随后第二个红框中的内容就是 CHolographicInteropTextureMarshaleri+1 对象。

此时拷贝的内容是我们前面通过 hook pointer_specialHeapalloc 构造的恶意 CommandBuffer,其中核心是红框中的两个指针。

这里 00007ffa819ad5f0 就是一处虚指针。

覆盖后的效果如下,这两处指针第一处为 _fnGESTURE,第二处为 LoadLibraryA。

针对第一处的 _fnGESTURE 指针下内存读断点。

可以看到当样本尝试释放剩余所有 CHolographicInteropTextureMarshaler 资源时。

内存读断点断下,此时正是针对 CHolographicInteropTextureMarshaler 资源的释放操作。

这里 dwmcore!CComposition::Channel_DeleteResource 函数中获取对应被破坏 CHolographicInteropTextureMarshaler 对象处的恶意指针并针对该 poi(evilpointer)+0x10 进行调用,攻击者设置的恶意指针将最终通过以上方式寻址找到函数 __fnTOUCHHITTESTING。

__fnTOUCHHITTESTING 函数如下所示,会直接将 rcx+0x50 作为函数地址,rcx+0x28 作为函数第一个参数。

进入到 __fnTOUCHHITTESTING,此时的 rcx 内容如下,可以看到 rcx+0x50 指向了恶意构造的 loadLibrary,rcx+0x28 此时的值为 0x58。

但是当 FixupCallbackPointers 函数执行完毕后,此时 rcx+0x28 就指向了 0x58 处的 dll path。

最终导致任意 dll 加载,实现代码执行。

S1.dll 已经被加载。

现在来整体总结下该漏洞利用的过程:

  1. 通过 D2D1CreateFactory 配合 DirectComposition::CDevice::Commit 在窗口 test 上进行绘制,该调用会导致 DirectComposition::CDevice::Commit 的过程中生成对应的 CommandBuffer,该 CommandBuffer 被利用中的 hook 修改。
  2. 调用 NtDCompositionProcessChannelBatchBuffer 在 dwm 进程中 spray 一系列 CHolographicInteropTextureMarshaler 资源,并每隔 0x20 个释放掉一个该资源,生成一系列 hole。
  3. 通过 showwindow 显示 test 窗口,这将导致 DirectComposition::CDevice::Commit 提交的 CommandBuffer 在 dwm 进程的 CCommandBuffer::Initialize 漏洞函数中被调用,从而占据一个 CHolographicInteropTextureMarshaler 释放的 hole 并实现对紧随其后的 CHolographicInteropTextureMarshaler1 资源的复写。
  4. 调用 NtDCompositionProcessChannelBatchBuffer 释放剩余的 CHolographicInteropTextureMarshaler 资源,这将导致被复写 CHolographicInteropTextureMarshaler1 资源中恶意修改的函数指针被调用,从而通过恶意构造的 __fnTOUCHHITTESTING 以第一个参数可控的方式调用 loadlibrary 加载恶意 dll。

详细分解如下图所示:


利用加载

S1.dll 的代码很简单,通过 RtlDecompressBuffer 释放出一段 shellcode,并通过 CreateThread 调用。

shellcode 会简单解密出一个 s2.exe。

s2.exe 首先同样会通过 RtlDecompressBuffer 释放出一个 s3.dll,之后执行 fun_triggerWinloginload 函数。

fun_triggerWinloginload 中首先通过系统版本初始化两个变量。

之后动态获取一些系统 api 的地址,并通过和之前利用程序中一致的方式 hook 函数 MapViewOfFile。

MapViewOfFile 的 hookpro 函数中首先会执行 MapViewOfFile,并 sleep 一段时间后判断对应返回的映射内存偏移 0x10 的位置是否是 0x1FFEEFFEE。

之后开始对这段 MapViewOfFile 进行赋值操作。

赋值的内容如下所示:

由之前的分析可知,dwm 作为 window 中桌面管理显示的进程,其他进程和 dwm 的通信,例如我们的 exp,都是通过 dcomp 和内核 win32kbase.sys 进行,并在 win32kbase.sys 中生成对应资源的并反馈给 dwm 进程,这就有一个问题,如果对应进程和 dwm 之间通信需要很大的数据,这时再通过内核进行中转将是一件很麻烦的事情,因此诞生了前面提到的资源 CSharedSection,以用于进程和 dwm 之间进行数据的共享,这里的共享其实就是我们前面漏洞利用中提到的 DirectComposition::CDevice::AllocateSharedMemory。

只是前面的利用中是修改了该段 CSharedSection 0x74 之后的 CCommandBuffer,而通过观察可知 CSharedSection 中还保存了通信进程中一些对象的虚表指针,此时 s2.exe 位于dwm的进程,因此可以通过 hook 指定的 MayViewOfFile 获取该段 CSharedSection 并修改其中的虚表指向类似利用中的 _fnGESTURE 来加载任意 dll,从而控制对应的通信进程行为,如果该通信的进程是 system 权限,则可以起到提权的效果,该样本中则是尝试修改的目标进程就是 consent.exe,这里漏洞利用样本最后执行 ShellExecuteA(0i64, "runas", "C:\\Windows\\System32\\cmd.exe", 0i64, 0i64, 1); 来实现 consent.exe 和 dwm 的交互。

实际上当 dwm 进程加载了 s1.dll 的时候,还未完成提权,攻击者绕这么大的弯通过 hook dwm 进程的 MapViewOfFile 来修改 CSharedSection 中 consent.exe 进程对象的虚表指针,以加载 dll 实现提权更多是因为 dwm 本身还存在权限上的限制。可以看到最终 consent.exe 这个 uac 进程加载了 s3.dll,并通过 s3.dll 弹出了一个 root shell。

此处对应的调用堆栈如下所示:

主利用 exp 中调用 ShellExecuteA(0i64, "runas", "C:\\Windows\\System32\\cmd.exe", 0i64, 0i64, 1),这将导致触发一个管理员的 cmd,从而触发 uac,其调用流程是 svchost\ -> consent.exe,此时 consent.exe 将和 dwm 进行一次渲染操作,consent 进程中将调用了函数 dcomp!DirectComposition::CSharedSection::Allocate,用于分配一段 CSharedSection 的共享内存。

这里返回后可以看到函数返回后对应的内存中被放置了两个指针。

两处虚表分别指向以下两个函数:

  • CMILRefCountImpl::AddReference
  • DirectComposition::CCompositorSynchronizedObject::SafeToModify

而同时在我们的 dwm 中,s3 会触发对应的共享内存的映射,从而被设置的 hookpro 捕获,这段内存对应映射在 consent 中的地址为 0x1f16b6c120,而在前面的分析中知道 consent 中分配的地址从 0x1f16b6c720 开始。

因此 hookpro 中的操作就是,构造了一段恶意的指针调用内存,并向后写入 0x1f16b6c720 处的两个指针,将其指向这段共享内存低地址处构造的恶意指针调用内存,如下所示,当这段内存映射回 consent 进程时 0x1f16b6c720 中的指针就已经指向了恶意构造的恶意的指针调用内存。

此时 consent.exe 中的共享内存已经被修改(720 及 740 的位置)。

uac 触发完成时调用 dcomp!DirectComposition::CDelayedDestructionObject::Release,该函数中会使用到前面修改的第一个位置处的指针 CMILRefCountImpl::AddReference。

如下所示,最终 dcomp!DirectComposition::CDelayedDestructionObject::Release 中的调用方式

poi(poi(evilpoint)+0x18) 指向了我们恶意内存中构造的 combase!NdrProxyForwardingFunction3 指针。

combase!NdrProxyForwardingFunction3 函数如下所示,其代码很简单。

总结一下,该代码会以以下伪代码执行 poi(poi(evilpoint+0x20)+0x18)(poi(evilpoint+0x20)),其最终结果就是通过第二个原 DirectComposition::CCompositorSynchronizedObject::SafeToModify 处被替换的指针寻址到恶意内存中构造的 USER32!_fnCOPYDATA 函数,并将该替换地址作为参数。

如下此时进行 USER32!_fnCOPYDATA 的调用,参数就是原第二处替换指针的地址。

USER32!_fnCOPYDATA 和前面的 USER32!__fnTOUCHHITTESTING 类似,即当可以控制第一个参数的情况下,用于实现 loadlibaray 函数任意 dll 加载的小工具函数。

USER32!_fnCOPYDATA 中参数为恶意构造内存中的恶意虚表2处,以该虚表地址依此获取偏移 0x68 处和 0x28 处恶意内存构造好的 loadlibrary 和 s3 路径,从而调用 loadlibrary 加载最终的 s3 代码,该代码实现很简单直接弹出一个 cmd。

自此我们总结以下整个利用的后利用部分:

  1. exp 中通过触发漏洞获取在 dwm 中的代码执行权限,并在成功时 dwm 加载第一阶段的 s1.dll。
  2. s1.dll 释放出一段 shellcode 并执行,该 shellcode 释放 s2.exe 并执行。 3.exp 中执行 ShellExecuteA(0i64, "runas", "C:\\Windows\\System32\\cmd.exe", 0i64, 0i64, 1);来实现consent.exe 和 dwm 的交互,consent.exe 进程中生成对应的 sharedSection。
  3. s2.exe 在 dwm 中执行,会 hook dwm 中 mapviewofile,当监控到指定的内存映射时越界修改 consent.exe 进程中生成对应的 sharedSection 中的两处虚表。
  4. consent.exe 中 sharedSection 中的两处虚表触发执行,进入攻击者设置的恶意虚表指针,通过 NdrProxyForwardingFunction3/__fnCOPYDATA/LoadLibraryA 执行流加载最终 s3.dll 并创建一个 cmd,实现最终的提权。

最终我们将漏洞利用的流程图和后续提权利用的流程合并,整个样本的利用技术流程就可以总结成以下的一张大图。

QakBot,也称为 QBot、QuackBot 和 Pinkslipbot,是一种已经存在十多年的银行木马。它于 2007 年在野外被发现,此后一直得到不断的维护和开发,过去十几年中并没有发现 QakBot 相关攻击活动中有使用 0day 的案例,直到今年上半年出现的 CVE-2024-30051。通过上文的分析,可以看到整个样本的开发者对 Windows 内部的机制研究得非常透彻,分析过程中,笔者也常为其利用中使用得手法而拍案叫绝,作为一款历史悠久的银行木马,QakBot 有着足够的经济实力进行 0day 军备的补充,因此目前我们无法断定该漏洞利用样本是否是 QakBot 内部自研,但是正如 2023 年末奇安信威胁情报中心发布的年度报告中就提到,种种迹象都表明越来越多非传统意义 APT 团伙,且有经济实力的攻击组织,如勒索,再到如今的老牌银行木马都疑似通过网络军火商购入并频繁使用 0day 漏洞,且这样的行为在未来将越发普遍。


参考链接

[1] https://conference.hitb.org/hitbsecconf2023ams/materials/D1T1%20-%20The%20Lost%20World%20of%20DirectComposition-%20Hunting%20Windows%20Desktop%20Window%20Manager%20Bugs%20-%20Zhang%20WangJunjie,%20YiSheng%20He%20&%20Wenyue%20Li.pdf

[2] https://www.zerodayinitiative.com/blog/2021/5/3/cve-2021-26900-privilege-escalation-via-a-use-after-free-vulnerability-in-win32k

[3] https://github.com/progmboy/cansecwest2017

[4] https://blog.talosintelligence.com/snapshot-fuzzing-direct-composition-with-wtf/

[5] https://securelist.com/cve-2024-30051/112618/

[6] https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-30051

漏洞 CVE-2024-30051