[原创]CVE-2023-28252 CLFS 提权漏洞分析
2023-8-2 10:36:18 Author: bbs.pediy.com(查看原文) 阅读量:34 收藏

红雨滴团队的《CVE-2023-28252在野提权漏洞样本分析》文章详细的分析了恶意文件并定位到漏洞触发点,但由于文章的主要目的是分析恶意文件而非讲解漏洞成因,因此漏洞成因方面的内容偏少。该文的主要内容是分析clfs.sys内的函数,以了解漏洞触发过程。由于笔者刚入门,如果文章出现错误,请多多包涵。

  • Windows 10 22H2
  • IDA
    image.png

简述

通用日志文件系统(Common Log File System,缩写CLFS)是一个通用目的的日志文件系统,它可以从内核模式或用户模式的应用程序访问,用以构建一个高性能的事务日志。

文件格式

文件格式的类型定义可以参考ionescu007的文章,下面进行简单介绍。
元数据块类型为6种,其中Shadow块为备份块,当主块存在异常时,会使用Shadow块。类型定义如下:

1

2

3

4

5

6

7

8

9

typedef enum _CLFS_METADATA_BLOCK_TYPE

{

    ClfsMetaBlockControl,

    ClfsMetaBlockControlShadow,

    ClfsMetaBlockGeneral,

    ClfsMetaBlockGeneralShadow,

    ClfsMetaBlockScratch,

    ClfsMetaBlockScratchShadow

} CLFS_METADATA_BLOCK_TYPE, *PCLFS_METADATA_BLOCK_TYPE;

每个元数据块都以CLFS_LOG_BLOCK_HEADER结构起始,后面跟着元数据块特有的结构与数据,简单示意图如下:
图片描述
该漏洞涉及ClfsMetaBlockControl和ClfsMetaBlockGeneral块,其涉及的主要类型定义如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

typedef struct _CLFS_LOG_BLOCK_HEADER

{

    UCHAR MajorVersion;

    UCHAR MinorVersion;

    UCHAR Usn;

    CLFS_CLIENT_ID ClientId;

    USHORT TotalSectorCount;

    USHORT ValidSectorCount;

    ULONG Padding;

    ULONG Checksum;

    ULONG Flags;

    CLFS_LSN CurrentLsn;

    CLFS_LSN NextLsn;

    ULONG RecordOffsets[16];

    ULONG SignaturesOffset;

} CLFS_LOG_BLOCK_HEADER, *PCLFS_LOG_BLOCK_HEADER;

typedef struct _CLFS_CONTROL_RECORD

{

    CLFS_METADATA_RECORD_HEADER hdrControlRecord;

    ULONGLONG ullMagicValue;

    UCHAR Version;

    CLFS_EXTEND_STATE eExtendState;

    USHORT iExtendBlock;

    USHORT iFlushBlock;

    ULONG cNewBlockSectors;

    ULONG cExtendStartSectors;

    ULONG cExtendSectors;

    CLFS_TRUNCATE_CONTEXT cxTruncate;

    USHORT cBlocks;

    ULONG cReserved;

    CLFS_METADATA_BLOCK rgBlocks[ANYSIZE_ARRAY];

} CLFS_CONTROL_RECORD, *PCLFS_CONTROL_RECORD;

typedef struct _CLFS_BASE_RECORD_HEADER

{

    CLFS_METADATA_RECORD_HEADER hdrBaseRecord;

    CLFS_LOG_ID cidLog;

    ULONGLONG rgClientSymTbl[CLIENT_SYMTBL_SIZE];

    ULONGLONG rgContainerSymTbl[CONTAINER_SYMTBL_SIZE];

    ULONGLONG rgSecuritySymTbl[SHARED_SECURITY_SYMTBL_SIZE];

    ULONG cNextContainer;

    CLFS_CLIENT_ID cNextClient;

    ULONG cFreeContainers;

    ULONG cActiveContainers;

    ULONG cbFreeContainers;

    ULONG cbBusyContainers;

    ULONG rgClients[MAX_CLIENTS_DEFAULT];

    ULONG rgContainers[MAX_CONTAINERS_DEFAULT];

    ULONG cbSymbolZone;

    ULONG cbSector;

    USHORT bUnused;

    CLFS_LOG_STATE eLogState;

    UCHAR cUsn;

    UCHAR cClients;

} CLFS_BASE_RECORD_HEADER, *PCLFS_BASE_RECORD_HEADER;

其中ClfsMetaBlockGeneral块包含容器类型,且容器的pContainer成员为内核对象指针,因此如果可以控制该成员,则将为执行任意代码打下基础。其结构定义如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

typedef struct _CLFS_CONTAINER_CONTEXT

{

    CLFS_NODE_ID cidNode;

    ULONGLONG cbContainer;

    CLFS_CONTAINER_ID cidContainer;

    CLFS_CONTAINER_ID cidQueue;   //队列标识符

    union

    {

        CClfsContainer* pContainer;   //指向描述容器类的内核对象

        ULONGLONG ullAlignment;

    };

    CLFS_USN usnCurrent;

    CLFS_CONTAINER_STATE eState;

    ULONG cbPrevOffset;

    ULONG cbNextOffset;

} CLFS_CONTAINER_CONTEXT, *PCLFS_CONTAINER_CONTEXT;

之前在CVE-2022-24521漏洞复现过程中,简单编写了一个010 Editor的模板,可以简单解析日志文件(.blf格式文件),便于了解文件结构布局,后续可在附件中下载。
image.png

漏洞通过 CreateLogFile和AddLogContainer 函数进行触发,接下来将根据构造的日志文件内容梳理这两个函数的执行流程。为了便于理解clfs.sys的函数,首先将ionescu007文章中的CLFS相关结构体整理成了头文件,并加载到IDA中。
image.png

CreateLogFile处理流程

《CVE-2022-37969 技术分析》中说到当在用户空间调用CreateLogFile函数时,CLFS!CClfsRequest::Create负责处理这个请求。当使用 CreateLogFile 函数打开现有基本日志文件时,将在 CLFS.sys 中调用函数CClfsLogFcbPhysical::Initialize ,并随后调用CClfsBaseFilePersisted::OpenImage。接下来将从OpenImage函数开始进行分析。
OpenImage首先进行初始化操作,如申请内存。
图片描述
图:OpenImage部分代码
OpenImage中调用了CClfsBaseFilePersisted::ReadImage函数,这里提前说明下函数做了哪些后续要关注的工作。26行将this+0x28赋值为6,27行为this+0x30 申请内存空间,45行调用CClfsBaseFile::GetControlRecord获取控制记录的地址。控制记录为_CLFS_CONTROL_RECORD结构,为ClfsMetaBlockControl块的第二个结构体。
图片描述
图:ReadImage部分代码
随后OpenImage函数的131行调用CClfsBaseFilePersisted::ReadImage函数加载日志文件,获取控制记录结构地址,加载成功后从142行代码开始执行。146行调用CClfsBaseFile::GetBaseLogRecord函数获取基本日志记录地址,该返回值为_CLFS_BASE_RECORD_HEADER指针,指向ClfsMetaBlockGeneral块的第二个结构体,随后在149-157行使用基本日志记录初始化数据。当控制记录的eExtendState不为0,iExtendBlock小于4,iFlushBlock小于6且大于等于iExtendBlock,cExtendStartSectors小于文件扇区总数量,cNewBlockSectors小于扩展元数据块的扇区总数量加上(iExtendBlock/2)时,将在179行调用CClfsBaseFilePersisted::ExtendMetadataBlock函数。
图片描述
图:OpenImage部分代码
ExtendMetadataBlock函数98行根据参数2(iExtendBlock)加载元数据块,然后获取控制记录指针。当控制记录的eExtendState成员值为2时,跳转到LABEL_41。
图片描述
图:ExtendMetadataBlock部分代码
192行检测eExtendState值是否为2,不为2则退出循环。随后在208行使用iFlushBlock作为参数2调用CClfsBaseFilePersisted::WriteMetadataBlock函数(本次调用该函数时,参数2被指定为了4,用于扩展ClfsMetaBlockScratch块,这一过程只是为了过渡到FlushControlRecord函数的调用)。随后209行检测iFlushBlock与iExtendBlock值是否相同,相同则v12复制为0,从而在下次for循环时,从192行的检测中退出循环。循环最后调用了CClfsBaseFilePersisted::FlushControlRecord,这个函数需要重点关注。
图片描述
图:ExtendMetadataBlock部分代码
FlushControlRecord函数在处理部分数据后,于23行调用了CClfsBaseFilePersisted::WriteMetadataBlock。
图片描述
图:FlushControlRecord代码

WriteMetadataBlock函数在31行根据参数2获取指定元数据块的地址,然后进行了部分数据的更新,此时更新的元数据块是iFlushBlock字段指定的。61行调用了ClfsEncodeBlock处理块数据,并在67行调用CClfsContainer::WriteSector将写入块数据,这两个函数是漏洞利用的关键前置步骤。需要注意的是,ClfsEncodeBlock和WriteSector操作的是参数2指定的元数据块,本次参数2为0,即操作的是ClfsMetaBlockControl块。
图片描述
图:WriteMetadataBlock部分代码

ClfsEncodeBlock先将_CLFS_LOG_BLOCK_HEADER的Checksum 成员值置为0,然后调用ClfsEncodeBlockPrivate函数进行下一步处理。当ClfsEncodeBlockPrivate返回结果为负数时,则不会重新计算校验和并返回。
图片描述
图:ClfsEncodeBlock代码

ClfsEncodeBlockPrivate检测_CLFS_LOG_BLOCK_HEADER的ValidSectorCount是否小于TotalSectorCount,如果小于则返回错误码0xC01A000A。
图片描述
图:ClfsEncodeBlockPrivate部分代码

回顾WriteMetadataBlock函数的61行代码,可以发现并没有对ClfsEncodeBlock的返回值进行判断,而是直接调用WriteSector函数将更新数据。假设ValidSectorCount小于TotalSectorCount,执行ClfsEncodeBlock时则首先将校验和置为0,然后ClfsEncodeBlockPrivate检测到异常,返回错误码0xC01A000A,因此ClfsEncodeBlock不会更新校验和。随后直接调用WriteSector函数将指定元数据块的校验和的值更新为了0。
图片描述
图:WriteMetadataBlock部分代码

将校验和值更新为0的目的在于绕过OpenImage中的检测。OpenImage的163~173行,检测了iFlushBlock和iExtendBlock的值是否大于6,当大于6时则会返回错误码0xC01A000D。简介文件格式时,提到过shadow块为备份块,当主块的校验和值为0时,则会检测失败,从而使用shadow块的数据,因此shadow块数据可以为大于6的数据,然后通过构造特定字段值,执行上述流程使校验和为0,下次获取元数据块则使用shadow块,下一部分内容会说明替换逻辑。
图片描述
图:OpenImage部分代码

AddLogContainer处理流程

《样本分析》中指出WIN32API AddLogContainer对应内核中的CLFS!CClfsLogFcbPhysical::AllocContainer函数,下面将从AllocContainer函数入手分析。
AllocContainer函数的69行调用了CClfsBaseFilePersisted::AddContainer函数。
图片描述
图:AllocContainer部分代码

AllocContainer函数的44行调用了CClfsBaseFilePersisted::AddSymbol函数。
图片描述
图:AddContainer部分代码

AddSymbol函数中当v16 值为 0xC0000023时,会在26行调用CClfsBaseFilePersisted::ExtendMetadataBlock函数。v16为CClfsBaseFile::FindSymbol函数的返回值,因此关注FindSymbol函数。
图片描述
图:AddSymbol代码

FindSymbol函数中没有发现0xC0000023错误码,但124行调用了虚函数,并且返回值为负数时将通过LABEL_26标记返回。使用windbg设置断点调试,最终确定该虚函数为CLFS!CClfsBaseFilePersisted::AllocSymbol()。
图片描述
图:FindSymbol部分代码

在AllocSymbol中的25行发现了错误码0xC0000023,其触发条件为当前容器的结束地址大于签名缓冲区地址。当前容器的结束地址根据_CLFS_BASE_RECORD_HEADER结构的cbSymbolZone成员值计算得到,因此可以修改cbSymbolZone值使其满足条件返回错误码0xC0000023,从而在AddSymbol中调用ExtendMetadataBlock函数。
图片描述
图:AllocSymbol代码

此时执行流程为:
AllocContainer -> AddContainer -> AddSymbol -> FindSymbol -> AllocSymbol //返回错误码0xC0000023
AllocContainer -> AddContainer -> AddSymbol -> ExtendMetadataBlock //根据错误码0xC0000023,调用ExtendMetadataBlock函数
ExtendMetadataBlock函数105行调用CClfsBaseFile::GetControlRecord获取控制记录地址(_CLFS_CONTROL_RECORD),控制记录存在于ClfsMetaBlockControl块中。在CreateLogFile执行过程中,通过构造特定的字段使的CreateLogFile校验和为0,因此本次获取的控制记录实则为ClfsMetaBlockControlShadow块的。下面分析GetControlRecord函数,查看控制记录是如何被替换的。
图片描述
图:ExtendMetadataBlock部分代码

GetControlRecord函数18行调用CClfsBaseFile::AcquireMetadataBlock函数加载ClfsMetaBlockControl块,随后进行一系列检查,最终将ClfsMetaBlockControl中CLFS_CONTROL_RECORD的地址写入到参数2。
图片描述
图:GetControlRecord代码

AcquireMetadataBlock中调用了一个虚函数,在windbg中跟踪为CClfsBaseFilePersisted::ReadMetadataBlock函数。
图片描述
图:AcquireMetadataBlock代码

ReadMetadataBlock函数31至52行代码读取参数2指定的元数据块到分配的池内存,随后在58行调用ClfsDecodeBlock函数进行解码(因为CreateLogFile函数处理流程中ClfsMetaBlockControl校验置为了0,因此该函数会返回错误码)。73行代码再次调用ReadMetadataBlock函数,加载当前元数据块的shadow块。随后通过一系列检查到达75行,此时v8小于0,将跳转到117行代码开始执行。117~122代码,释放了ReadMetadataBlock为当前元数据块申请的池空间,然后使用其shadow块地址代替当前元数据块。
图片描述
图:ReadMetadataBlock部分代码
图片描述
图:ReadMetadataBlock部分代码

ClfsDecodeBlock函数先判断校验和是否为0,这里为0然后跳转到16行继续判断。通过ReadMetadataBlock的函数调用可知a4值为0x10,然后MajorVersion值为0xF,因此该检测失败,直接返回错误代码 0xC01A000A。至此ReadMetadataBlock函数使用ClfsMetaBlockControlShadow块替换了ClfsMetaBlockControl块。
图片描述
图:ClfsDecodeBlock代码
图片描述
图:默认MajorVersion值

回到ExtendMetadataBlock函数,可以得知105行代码获取的实际为ClfsMetaBlockControlShadow块。随后通过检测,跳转到LABEL_41处。
图片描述
图:ExtendMetadataBlock部分代码

跳转到LABEL_41后,在208行调用了CClfsBaseFilePersisted::WriteMetadataBlock函数。需要注意的是ClfsMetaBlockControlShadow块中的iFlushBlock值为0x13,即WriteMetadataBlock的参数2值为0x13。
图片描述
图:ExtendMetadataBlock部分代码

此时查看WriteMetadataBlock处理逻辑。31行代码使用24乘以参数2作为this+0x30的偏移,随后取得数据。在ReadImage中可以得知this+0x30的内存空间为0x90,而24*0x13结果为0x1C8,已经超出了0x90,从而取得了错误的地址。然后从错误地址的0x28偏移取得偏移B,然后将错误地址的偏移B的数据进行+1。此时通过漏洞已经触发,实现了数据自增1。
图片描述
图:WriteMetadataBlock部分代码

任意函数执行

《样本分析》中指出CreateLogFile函数会调用CClfsBaseFilePersisted::CheckSecureAccess函数。CheckSecureAccess函数的55行起会遍历容器,然后64行获取pContainer值,并在69行和78行调用二次虚函数。因此可以创建一个日志文件,其包含了精心构造的容器,然后利用该漏洞修改容器的偏移,使其再次调用CreateLogFile时,遍历的容器为构造的容器,然后pContainer值指向用户空间中构造虚函数布局,这样调用虚函数时便可以执行我们想要执行的函数。由此构造函数利用链(函数利用链可以查看《CVE-2022-37969 技术分析》,该文章详细的介绍了相关内容),实现权限提升。
图片描述
图:CheckSecureAccess部分代码

权限提升思路

权限提升过程涉及到池空间分配、利用管道属性实现任意地址写入等操作,这里仅简单介绍下思路,不在详细展开,具体过程可以查看《样本分析》。
1.创建一个包含容器的日志文件A,然后容器地址偏移0x100处构造一个pContainer成员有值的容器。
2.通过NtQuerySystemInformation查询0x7A00大小的CLFS数据池。
3.调用CreateLogFile函数将日志文件A加载到内核。
4.通过NtQuerySystemInformation查询0x7A00大小的CLFS数据池,查询到新增的池地址,这个地址为日志文件A的ClfsMetaBlockGeneral块地址(该块的大小为0x7A00)。
5.创建多个漏洞利用日志文件B,构造文件数据,使其最终可以在WriteMetadataBlock中实现越界数据访问,并进行数据自增。
6.构造池布局空间。申请大量的匿名管道,然后将13个日志文件A的ClfsMetaBlockGeneral块地址写入到管道,使其申请0x90的池空间。
7.关闭匿名管道,释放池空间,然后调用CreateLogFile函数将多个日志文件B加载到内核,然后在将13个日志文件A的ClfsMetaBlockGeneral块地址写入到管道,这样在日志文件B的clsfhelp(this+0x30)的周围就是构造的日志文件A的ClfsMetaBlockGeneral块地址。
8.构造函数执行内存布局。
9.调用AddLogContainer函数触发漏洞,修改日志文件A的ClfsMetaBlockGeneral块中rgContainer数据,使其容器偏移增加0x100。增加0x100的原因是利用漏洞增加的是后24位的数据,因此自增1,等于增加了0x100偏移。
10.调用CreateLogFile函数再次加载日志文件A,随后通过CheckSecureAccess函数利用构造的容器,然后根据函数执行内存布局利用管道属性,实现任意数据写入,然后替换了进程的TOKEN从而实现提权。

根据上面分析可以得知触发漏洞是由于WriteMetadataBlock的ClfsEncodeBlock函数将校验和置为了0,然后在触发错误时,仍更新了ClfsMetaBlockControl块数据,从而导致下一次加载ClfsMetaBlockControl块时使用了导致越界访问的ClfsMetaBlockControlShadow块。因此修复后的WriteMetadataBlock函数在82行对ClfsEncodeBlock的返回值进行了检测,在出现异常时不调用WriteSector函数,从而使得ClfsMetaBlockControlShadow不会被使用。《样本分析》中描述了其他的修复内容,这里就不再重复陈述。
图片描述
图:修复后的WriteMetadataBlock部分代码

https://mp.weixin.qq.com/s/Qlst6CX_z1A698Tvvx-bIQ (CVE-2023-28252在野提权漏洞样本分析)
https://github.com/ionescu007/clfs-docs/ (CLFS格式)
https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part (CVE-2022-37969 技术分析 上)
https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part2-exploit-analysis(CVE-2022-37969 技术分析 下)

议题征集启动!看雪·第七届安全开发者峰会10月23日上海

最后于 14小时前 被i未若编辑 ,原因:

上传的附件:
  • blf(CVE-2022-24521).bt (28.02kb,3次下载)
  • clfs.h (4.20kb,4次下载)
  • MyLog(CVE-2022-24521).blf (64.00kb,3次下载)

文章来源: https://bbs.pediy.com/thread-278241.htm
如有侵权请联系:admin#unsafe.sh