自2022年2月起,有报道称出现了一种新的勒索软件,该软件似乎利用了Windows 0-day漏洞,这是根据Trend Micro的研究得出的。
关于这种勒索软件的更多信息可以在这个链接中找到。
根据卡巴斯基的分析,Nokoyawa勒索软件组织自2022年6月以来,一直使用其他针对通用日志文件系统(CLFS)驱动程序的漏洞,这些漏洞具有相似但不同的特征,全部与同一个漏洞开发者有关。
2023年4月,当微软发布补丁时,分配了CVE-2023-28252。
此前,在2022年,我们研究了同一组件中的一个类似漏洞,并记录在这篇博客文章中。
为了进行分析,有必要了解***.blf文件格式,该格式由易受攻击的通用日志文件系统*驱动程序CLFS.sys**处理,该驱动程序位于system32中的驱动程序文件夹内。
有关此文件类型的更多信息,请参见以下链接:
https://github.com/ionescu007/clfs-docs/blob/main/README.md
https://www.coresecurity.com/core-labs/articles/understanding-cve-2022-37969-windows-clfs-lpe
本分析针对Windows 11 21H2,clfs.sys版本10.0.22000.1574,尽管它也适用于Windows 10 21H2、Windows 10 22H2、Windows 11 22H2和Windows Server 2022。
在之前的Windows版本中,需要调整一些值,否则会导致蓝屏死机(BSOD)。
您可以像这样检查驱动程序版本
当漏洞公布时,2023年4月我开始与Esteban Kazimirow一起对CLFS.sys驱动程序进行逆向工程,尽管在这种情况下,仅仅分析补丁很难推断出漏洞在哪里以及如何触发它,因为利用非常复杂。
后来,出现了一篇博客文章,其作者从一个恶意软件样本中展示了HexRays反编译的一些代码片段,以及一些指导如何应对利用的信息。
显然,提供的信息并不完整,但如果没有这些帮助,就很难构建出PoC,进而构建出可用的利用程序。
为了更容易理解,我们将首先解释如何构建PoC,然后进行漏洞分析。
这篇博客文章包含两个部分:
构建PoC:
1-获取我们利用所需的内核地址
2-准备创建.blf文件的路径:
3-使用CreateLogFile()函数创建“触发器blf”文件
4-制作“触发器blf”文件
5-获取触发器blf的BASE BLOCK的内核地址
6-使用触发器blf的句柄调用AddLogContainer
7-准备喷洒blf文件
8-准备执行喷洒的内存
9-触发漏洞
调试:
1-检查内存喷洒
2-查看触发器blf的RecordOffset[12]
3-查看喷洒blf文件中的iFlushBlock值
4-为什么它从BLOCK 1 SHADOW读取,而不是BLOCK 0 CONTROL?
5-为什么在blf喷洒文件中校验和等于零?
6-完成利用。
7-真正的补丁
我将创建一个名为InitEnvironment的函数来获取一些必要的内核地址。
获取我的进程的EPROCESS地址并将其存储在g_EProcessAddress变量中,然后获取SYSTEM进程的EPROCESS地址并存储在system_EPROCESS中,接着获取我的进程的主线程的EHTREAD地址并存储在g_EThreadAddress中,最后获取PREVIOUS MODE的地址,在该PoC版本中不会使用。

这种方法众所周知,GetObjectKernelAddress函数调用NtQuerySystemInformation两次,第一个参数为SystemExtendedHandleInformation,第一次调用时传递了一个错误的大小并返回错误,但同时也返回了正确的尺寸,用于第二次调用,从而获取所有句柄的信息,然后在循环中遍历每个句柄的信息,在正确的handleinfo的Object字段中获取内核中搜索的地址。

我还需要由CLFS.sys导出的以下函数的内核地址:
• ClfsEarlierLsn
• ClfsMgmtDeregisterManagedClient
以及NTOSKRNL.exe导出的函数:
• RtlClearBit/PoFxProcessorNotification
• SeSetAccessStateGenericMapping
为了获取这些地址,使用类似于获取两个模块内核基址的方法,通过两次调用NtQuerySystemInformation,但这次第一个参数将是SYSTEM_INFORMATION_CLASS(在PoC中,我们使用FindKernelModulesBase函数来实现此目的)。
然后,它将CLFS.sys和NTOSKRNL.exe作为用户模式下的普通模块加载,调用LoadLibrary,使用GetProcAddress获取用户模式下的地址,然后从每个地址中减去镜像基址,从而获得函数的偏移量,最后将每个偏移量添加到相应的内核基址,从而获得所有必要函数的内核地址。

我创建一个名为createInitialTriggerBlfFile的函数,它将生成并写入一个**.blf文件**。
在CreateLogFile中用作参数的路径与普通路径不同,例如,要打开位于C:\Users\Public文件夹中的文件1280.blf,我们必须设置路径LOG:C:\Users\Public\1280.。这将被保存在stored_name_CreateLog变量中。
我使用wsprintfW() 来实现这一点,因为stored_env存储了路径C:\Users\Public,该路径之前从环境变量中获取。我将在此字符串前面加上字符串LOG:,并在末尾加上一个随机名称,不带.blf扩展名。

这将是我想称为“触发器blf”的初始文件的路径。当然,我还必须保存同一文件的普通路径,不带前面的LOG:,并带有BLF扩展名,以便使用CreateFile()、WriteFIle() 像任何其他文件一样打开和修改它,这个路径将是,例如:C:\Users\Public\1280.blf,并将存储在stored_name_fopen变量中**。**

当然,这两个路径对应的是同一个文件,我必须根据情况使用其中之一。
CreateLogFile函数的功能与CreateFile() 非常相似(创建新文件或打开现有文件并获取其句柄),甚至一些参数也类似,但CreateLogFile() 仅适用于blf文件。
此外,当打开现有文件时,它会验证格式是否正确,即使每个块都有一个校验和,如果校验和不正确,它将返回错误。
我将创建两种类型的BLF文件:
Trigger blf(触发器blf)
Spray blf(喷洒blf)
两者都是blf文件,但以不同的方式修改。
通过这种方式,PoC首先使用CreateLogFile创建“触发器blf”文件,使用的路径例如:LOG:C:\Users\Public\1280,我之前已设置好该路径,并存储在stored_name_CreateLog变量中。
第五个参数fCreateDisposition,与CreateFileA() 一样,可以取以下值:

在这种情况下,我将使用OPEN_ALWAYS参数,因此如果文件不存在则创建,如果存在则打开。由于该文件尚不存在,它将使用随机名称创建。
logFile = CreateLogFile(stored_name_CreateLog, GENERIC_READ | GENERIC_WRITE, 1, 0, 4, 0);
CreateLogFile() 将创建我们的“触发器blf”文件,包含其6个块及其相应的校验和,并返回句柄,该句柄将存储在logFile变量中。

每个块将从左侧列显示的偏移量开始,有一个大小为0x70字节的头部。
因此,例如,CONTROL BLOCK的头部从偏移量0x0到0x70。

所有块的头部都具有相同的结构,称为**_CLFS_LOG_BLOCK_HEADER**。
这是头部结构:

在头部的偏移量0xC处,我可以找到checksum,因此由于CONTROL BLOCK从偏移量0开始,校验和将在文件的偏移量0xC处,并且每个块的校验和将位于其块起始偏移量0xC处。

要修改触发器blf文件,我必须将其作为普通文件打开,可以使用CreateFileA或fopen,然后分别使用WriteFile或fwrite修改它,我在PoC的fun_prepare函数开头执行此操作。
请记住,普通路径存储在stored_name_fopen变量中,因此我使用它通过wfopen_s(它是支持Unicode字符串的fopen变体)打开文件。
在从fun_prepare调用的craftTriggerBlfFile函数中修改该文件。