Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2022-37969 — 针对 CVE-2022-37969 的概念验证漏洞利用程序,这是一个 Windows 通用日志文件系统驱动程序本地权限提升漏洞。演示了堆喷射、令牌窃取和任意内核写入,以实现 SYSTEM 权限。 | Kitploit
工具/GitHubGitHub/fortra/cve-2022-37969
权限提升内存取证漏洞分析漏洞利用二进制利用
GitHubfortra/cve-2022-37969

CVE-2022-37969

针对 CVE-2022-37969 的概念验证漏洞利用程序,这是一个 Windows 通用日志文件系统驱动程序本地权限提升漏洞。演示了堆喷射、令牌窃取和任意内核写入,以实现 SYSTEM 权限。

查看仓库
1353813年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2022-37969 Windows 本地权限提升 PoC

作者:Ricardo Narvaja 和 Daniel Kazimirow (Solid)

仅用于演示目的。完整的漏洞利用适用于易受攻击的 Windows 11 21H2 系统。

基于先前 Zscaler 发布的信息 的功能性 PoC。

查看相关文章 理解 CVE-2022-37969 Windows 通用日志文件系统驱动程序本地权限提升。

用法

理解 CVE-2022-37969 Windows 通用日志文件系统驱动程序本地权限提升。

利用过程详解:

  • 创建初始的 BLF 日志文件
    • 创建多个随机的 BLF 日志文件
    • 构造初始日志文件
    • 执行受控的堆喷射
    • 准备 CreatePipe() / NtFsControlFile() 方法
    • 内存准备就绪后触发漏洞
    • 读取系统令牌
    • 验证令牌
    • 用系统令牌覆盖我们进程的令牌
    • 以系统权限执行进程
    • 逆向补丁:分析结构体
    • 破坏 “pContainer” 指针
    • 重新审视补丁
    • 破坏 SignatureOffset
    • 破坏更多值
    • 控制用于读取 SYSTEM 令牌的函数
    • 写入我们自己的进程以实现本地权限提升
    • PoC 源代码

此处使用的场景是 Windows 11 21H2 (OS Build 22000.918) clfs.sys v10.0.22000.918

创建初始的 BLF 日志文件

第一步是使用 CreateLogFile() 函数在公共文件夹 (%public%) 中创建一个名为 MyLog.blf 的文件:

创建多个随机的 BLF 日志文件

然后,它使用循环创建多个名称随机的日志文件。

在循环内部,它调用我们的 getBigPoolInfo() 函数:

它调用 NtQuerySystemInformation(),第一个参数为 0x42 (十进制 66),该调用将在 v5 中返回关于 bigpool 中 raid 的信息,其结构体类型为 SYSTEM_BIGPOOL_INFORMATION。

我们需要调用此函数两次。第一次会返回错误,但会给出正确的缓冲区大小,以便第二次调用时获取所需信息。

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente

v5 将接收 SYSTEM_BIG_POOL_INFORMATION 结构体的信息。

bigpool 中分配的数量存储在第一个字段 Count 中,第二个字段是 SYSTEM_BIGPOOL_ENTRY 结构体的数组。

然后,我们遍历所有结构体,查找标签为 “Clfs” 且大小为 0x7a00 的项。

它将这些结构体的第一个字段(VirtualAddress)存储在一个名为 kernelAddrArray 的数组中,这些结构体具有 CLFS 标签和大小 0x7a00。从现在起,同时满足这两个条件的池被称为 “正确池”。

除了将每个 正确池 存储在数组中之外,它还将最后找到的 正确池 存储在函数参数 a2 指向的内容中。

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

这样,a2 始终指向最后创建的具有 CLFS 标签和大小 0x7a00 的 正确池。

变量 v26 始终存储前一个找到的 正确池,因为它在调用 getBigPoolinfo() 之前等于 v24 (v26=v24),但退出此调用后,v24 会更新为最后找到的 正确池,而 v26 保持为前一个找到的 正确池。

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

然后,它计算两个地址的差值,如果结果为负,则交换操作数以确保结果始终为正。

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

这样,v32 将存储最后两个 正确池 的 VirtualAddress 之间的差值。

然后,它执行类似的操作,此例中 v23 初始为零,因此第一次执行 v23=v32。

下次循环时,v23 仍保持相同值且不为零,因此跳出并转到此处。

V32 包含最新的差值,v23 包含前一个差值,如果它们相等,则跳出并递增一个计数器,否则将计数器重置为零。

其目的是找到 6 个连续的 CLFS 标签和大小 0x7a00 的池,它们之间的差值相等,且该差值应为 0x11000。我们将会看到,当找到 6 个(因为从零开始)连续且距离相等的池时,它会给出它们之间的差值。

Texto Descripción generada automáticamente

在这里我们看到,它找到了 6 个连续的池,并退出了创建日志文件的循环。

在 “public” 文件夹中,我们可以看到创建的文件

构造初始日志文件:

我们的 craftFile() 函数打开原始文件 (MyLog.blf) 并修改它以触发漏洞。

修改文件后,必须更改 CRC32,否则会出现文件损坏错误。

该值位于文件的偏移量 0x80C 处。

执行受控的堆喷射

接下来,它执行堆喷射,使用 VirtualAlloc() 函数在任意地址 0x10000 和 0x5000000 上分配内存,并在第二个分配 (0x10000) 中,每 0x10 字节存储值 0x5000000。

准备 CreatePipe() / NtFsControlFile() 方法

它使用 CreatePipe() 创建一个匿名管道,并调用 NtFsControlFile(),使用 0x11003c 作为参数来添加一个属性,之后可以再次调用此函数,使用 0x110038 参数来读取该属性。

关于此方法的更多详细信息,请参见 此处

此处我们看到输入缓冲区就是我们要添加的属性,如果再次调用 NtFsControlFile() 并传入参数 0x110038,输出应该返回相同的这个属性。

在池中搜索创建的属性的标签(NpAt)

当找到它时,将该池的 VirtualAddress 保存在 v30.Pointer 中。

V30.pointer+24 指向内核池中的 AttributeValueSize,并将其保存到我们之前进行的堆喷射之一中。

其想法是写入该内核地址+8,以覆盖 AttributeValue。

Texto Descripción generada automáticamente

PipeAttribute 结构体的第一个字段是一个大小为 16 字节的 LIST_ENTRY,接着是一个指向属性名称的指针,大小为 8 字节,然后在偏移量 0x18 (24 十进制) 处是 AttributeValueSize 字段,这就是我们存储在堆喷射中的那个字段。

之后,我们在用户模式下加载 CLFS.sys 和 ntoskrnl,并使用 GetProcAddress() 找到 ClfsEarlierLsn() 和 SeSetAccessStateGenericMapping() 函数的地址。

然后我们调用 FindKernelModulesBase() 函数,该函数将使用 NtquerySystemInformation()(这次使用 SystemModuleInformation 参数)来查找这两个相同模块的内核基址,返回所有模块的信息。

这样,我们可以计算每个函数的偏移量,然后在内核中获取它们。

内存准备就绪后触发漏洞:

调用 pipeArbitraryWrite() 函数两次,有一个标志在初始调用时为零,当第二次调用时该标志值为 1,它将更改堆喷射的值。

Texto Descripción generada automáticamente

在第一次调用中,内存地址 0x5000000 处存放了以下值

记住,这个值除了分配在那个方向外,还存储在我们的堆喷射中。

这就是第一次调用后内存的状态,正如我们所说,地址大约在 0x5000000

而在从内存 0x10000 开始的堆喷射中,每隔 0x10 字节存储指向 AttributeValueSize 的指针,此外还有指向 0x5000000 的指针。

读取系统令牌:

以下序列将触发漏洞:

再次对精心构造的文件和另一个随机名称的文件调用 CreateLogFile()。

然后,使用这些文件的句柄调用 AddLogContainer()。

调用 NtSetinformationFile(),并关闭句柄,从而破坏指针(后续会解释)

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente con confianza media

堆喷射在此处防止蓝屏发生:

在此处设置断点,我们可以看到指针被破坏并指向我们的堆喷射,从而我们可以控制 vtable 的下两个函数调用。

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Texto Descripción generada automáticamente

RAX 取值 0x5000000,并首先跳转到位于 0x5000000+18 的函数,然后跳转到 0x5000000+8。

Texto Descripción generada automáticamente con confianza media

Imagen que contiene Interfaz de usuario gráfica Descripción generada automáticamente

因此,首先跳转到 fnClfsEarlierLsn(),然后跳转到 fnSeSetAccessStateGenericMapping()。

我们从断点处跟踪,发现它到达了 CLFS!ClfsEarlierLsn()。

Texto Descripción generada automáticamente

此函数被调用是因为它返回后,会将 EDX 设置为 0xFFFFFFFF

Interfaz de usuario gráfica Descripción generada automáticamente con confianza media

在地址 0xFFFFFFFF 处,我们存储了 SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000 的结果

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

正如我们所提到的,当从 CLFS!ClfsEarlierLsn() 返回时,RDX 值为 0x00000000FFFFFFFF

Texto Descripción generada automáticamente

我们到达第二个函数 nt!SeSetAccessStateGenericMapping()

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente

这个函数很有用,因为 RCX 指向我们的堆喷射,而 RDX 值是 0xFFFFFFFF,其内容由我们控制。

Interfaz de usuario gráfica, Aplicación, Teams Descripción generada automáticamente

Texto Descripción generada automáticamente

RCX+0x48 的内容是指向 AttributeValueSize 的指针,该指针存储在 v30.Pointer+24 中。

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Texto Descripción generada automáticamente con confianza media

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

该指向 AttributeValueSize 的指针值被移动到 RAX,然后读取地址 0xFFFFFFFF 处的内容,其中存储了 SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000 的地址。

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

然后,它在 RAX+8 处覆盖下一个字段,即 AttributeValue()。

Texto Descripción generada automáticamente

Texto Descripción generada automáticamente

当然,正常情况下 AttributeValue 会在内核中指向我们添加的属性。

Calendario Descripción generada automáticamente

现在,我们将它覆盖为一个指向系统 _EPROCESS & 0xFFFFFFFFFFFFFFF00 结果的指针。

这意味着,当我们再次调用 NtFsControlFile() 函数(这次使用 0x110038 参数读取属性)时,它不会返回 AttributeValue 指针所指向的 “A”,而是从 _EPROCESS & 0xFFFFFFFFFFFFFFFFF000 地址读取请求的字节数,并在输出缓冲区中返回,这样我们就可以在第一次调用中获取到 SYSTEM TOKEN 的值。

Texto Descripción generada automáticamente

v9b 是 Output Buffer 的起始地址,其中复制了 System EPROCESS & 0xFFFFFFFFFFFFFFF000 的结果。

他在此基础上加上 v14(即 System EPROCESS 的最后 3 个字节),然后加上 0x4b8(这是此 Windows 11 版本中 Token 的偏移量),然后找到该地址的内容,其中将保存 System Token 的值。

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Texto Descripción generada automáticamente

验证令牌

Texto Descripción generada automáticamente con confianza baja

请记住,最后 4 位已被更改,这并不重要,因此值仍然匹配。

用系统令牌覆盖我们进程的令牌

在第二次调用中,标志的值为 1,因为它在第一次调用结束时递增了。

Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente

此处我们看到值存储的顺序

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

地址 0xFFFFFFFF 中存放了我们刚刚找到的 System Process Token 的值。

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Texto Descripción generada automáticamente

而在堆喷射中,存放的是我进程的 Token 地址减去 8 的值。这个值加八将用作目标地址,请记住,写入操作是在 RAX+8 指向的地址上进行的。

Graphical user interface, text Description automatically generated

Texto Descripción generada automáticamente

在起始地址为 0x5000000 的内存中

Interfaz de usuario gráfica Descripción generada automáticamente

我们还看到它使用了其他容器的名称,因为前一个容器正被系统进程使用,无法再次打开或删除。

然后,漏洞第二次以与第一次相同的方式被触发。

它再次到达 CLFS!ClfsEarlierLsn()。

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

将 RDX 设置为 0xFFFFFFFF

Una captura de pantalla de un celular Descripción generada automáticamente

然后到达 nt!SeSetAccessStateGenericMapping()

Texto Descripción generada automáticamente

读取我进程的 Token 地址减 8 的值,该地址即将被写入。

Texto Descripción generada automáticamente

然后读取 SYSTEM TOKEN

Texto Descripción generada automáticamente

并将 System Token 写入我进程的 Token 地址(它加上 8)

这样,我的进程就拥有了 System Token

Texto Descripción generada automáticamente

写入令牌后,我们启动一个进程以检查权限,在此例中,我们启动 Notepad.exe

以系统权限执行进程:

Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Aplicación, Word Descripción generada automáticamente

Texto Descripción generada automáticamente con confianza baja

请注意,此 PoC 仅适用于 Windows 11,在 Windows 10 上会导致蓝屏,因此需要进行一些修改才能正常工作,本文不对此进行解释。

逆向补丁:

分析结构体

结构体以及关于 CLFS 文件格式的大部分文档,我们取自 IONESCU 关于 CLFS 内部结构 的优秀工作。

我们可以看到,在函数 ClfsBaseFilePersisted::LoadContainerQ 中新增了一个检查。

Texto Descripción generada automáticamente con confianza media

进行加法操作的值属于 _CLFS_BASE_RECORD_HEADER 结构体。

Escala de tiempo Descripción generada automáticamente con confianza media

请注意,Base Block 从文件的偏移量 0x800 开始,到偏移量 0x71FF 结束,前 0x70 字节对应 Log Block Header

作为一种良好实践,我们可以在 IDA 中添加 _CLF_LOG_BLOCK_HEADER 结构

struct _CLFS_LOG_BLOCK_HEADER

{

UCHAR MajorVersion;

UCHAR MinorVersion;

UCHAR Usn;

char ClientId;

USHORT TotalSectorCount;

USHORT ValidSectorCount;

ULONG Padding;

ULONG Checksum;

ULONG Flags;

CLFS_LSN CurrentLsn;

CLFS_LSN NextLsn;

ULONG RecordOffsets[16];

ULONG SignaturesOffset;

};

然后我们有基记录头(_CLFS_BASE_RECORD_HEADER),它从文件起始偏移量 0x870 处开始,长度为 0x1338 字节。

Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente

如果你想将其导入 IDA,必须先添加以下类型和缺失的结构

typedef GUID CLFS_LOG_ID;
typedef UCHAR CLFS_LOG_STATE;

struct _CLFS_METADATA_RECORD_HEADER

{

ULONGLONG ullDumpCount;

};

现在可以添加了:

typedef struct _CLFS_BASE_RECORD_HEADER

{

CLFS_METADATA_RECORD_HEADER hdrBaseRecord;

CLFS_LOG_ID cidLog;

ULONGLONG rgClientSymTbl[0x0b];

ULONGLONG rgContainerSymTbl[0x0b];

ULONGLONG rgSecuritySymTbl[0x0b];

ULONG cNextContainer;

CLFS_CLIENT_ID cNextClient;

ULONG cFreeContainers;

ULONG cActiveContainers;

ULONG cbFreeContainers;

ULONG cbBusyContainers;

ULONG rgClients[0x7c];

ULONG rgContainers[0x400];

ULONG cbSymbolZone;

ULONG cbSector;

USHORT bUnused;

CLFS_LOG_STATE eLogState;

UCHAR cUsn;

UCHAR cClients;

} CLFS_BASE_RECORD_HEADER, *PCLFS_BASE_RECORD_HEADER;

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

包含这些结构后,我们注意到它执行了 cbSymbolZone 与 _CLFS_BASE_RECORD_HEADER 结束地址之间的加法运算。(起始 + 1338h)Texto, Aplicación Descripción generada automáticamente

请记住,在精心构造的日志文件中,cbSymbolZone 已从 0x000000F8 修改为 0x0001114B。

(文件偏移量 0x1b98)

0x800(基块起始偏移量)+ 0x70(logBlockHeader)+ 0x1328(cbSymbolZone)

0x800+0x70+0x1328 = 0x1b98

在 MyLog.blf 文件上精心构造的 cbSymbolZone:

Tabla Descripción generada automáticamente

Imagen que contiene Texto Descripción generada automáticamente

由于补丁位于 CClfsBaseFilePersisted::LoadContainerQ 函数中,我们必须查看 CClfsBaseFilePersisted 对象。

在 CLFS!CClfsBaseFilePersisted::LoadContainerQ 设置断点,当 CreateLogFile 被调用并传入精心构造文件的句柄时,它将中断。

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

调用 CClfsBaseFile::GetBaseLogRecord 函数以获取基日志记录(_CLFS_BASE_RECORD_HEADER)的地址

Graphical user interface, application, timeline Description automatically generated

RAX 将指向 _CLFS_BASE_RECORD_HEADER 地址

Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente

注意内存中的 _CLFS_BASE_RECORD_HEADER 结构和 cbSymbolZone 字段 0x1328

字节向前

Imagen que contiene Texto Descripción generada automáticamente

Texto Descripción generada automáticamente

r14 存储对应“this”的结构,即 CClfsBaseFilePersisted,因为它是函数 CClfsBaseFilePersisted::LoadContainerQ 的 this。

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente

内存中的 CClfsBaseFilePersisted 结构:

Texto Descripción generada automáticamente

所以,让我们创建一个长度为 0x21c0 的结构,以便在我们逆向分析时填充其字段(这是一个未文档化的结构),我们将其命名为 struct_CClfsBaseFilePersisted

Tabla Descripción generada automáticamente con confianza media

在函数 CClfsBaseFile::GetBaseLogRecord() 内部,获取指向 _CLFS_BASE_RECORD_HEADER 的指针。并且我们知道该函数中的“this”是结构:struct_CClfsBaseFilePersisted。

Escala de tiempo Descripción generada automáticamente con confianza media

读取两个字段(偏移量 0x28 和 0x30)

Interfaz de usuario gráfica, Aplicación, Tabla Descripción generada automáticamente

偏移量 0x28 是一个 word,值为 6,因此我们在结构中将其类型更改为 word。

Texto Descripción generada automáticamente

Texto Descripción generada automáticamente con confianza media

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

目前,我们将其重命名为常量 6(const_6)

Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

根据文档,6 应该是块的数量 CLFS_METADATA_BLOCK_COUNT。该字段可能指代此值。

而该指针位于偏移量 0x30 处。

Tabla Descripción generada automáticamente

注意,此处显示的大小包括长度为 0x10 的头部

Texto Descripción generada automáticamente

Forma Descripción generada automáticamente con confianza media

当 ExAllocatePoolWithTag 函数被调用时,会请求一些字节,但不包括头部,因此调用时将请求 0x90 字节(0xa0 – 0x10)。

通过文本 +30h] 搜索写入偏移量 0x30 的指令时,我们找到了一长串结果,但通过 CClfsBaseFilePersisted 对象类型过滤列表后,结果很少,我们立即找到了分配该大小和相同标签的位置。(提示: Create 和 Initialize 函数名称总是首先查看的)

Texto Descripción generada automáticamente con confianza baja

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

由于我们仍然不知道名称,我们将其命名为 pool_0x90,这是另一个未文档化的结构,我们将创建一个该大小的结构。

Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Tabla Descripción generada automáticamente

内存中的 pool_0x90 在其自身偏移量 0x30 处有另一个指针。

Aplicación, Tabla Descripción generada automáticamente con confianza media

这个另一个指针指向文件中的基块(基块起始于偏移量 0x800)

Forma Descripción generada automáticamente

A picture containing calendar Description automatically generated

图片取自 Zscaler 博客文章:

Graphical user interface, application, email Description automatically generated

该分配很大,因为它包含了整个基块。

Text Description automatically generated

Graphical user interface, text, application Description automatically generated

因此,我们将创建一个大小为 0x7a00 的新结构,并命名为 BASE_BLOCK

Graphical user interface, text, application Description automatically generated

前 70 个字节我们已经知道对应 _CLFS_LOG_BLOCK_HEADER,接下来的 0x1338 字节对应 _CLFS_BASE_RECORD_HEADER。

Text Description automatically generated

因此,将基块的起始地址与到下一条记录的偏移量(即 0x70)相加,我们就得到了 _CLFS_BASE_RECORD_HEADER

Application Description automatically generated with low confidence

内存中的 _CLFS_BASE_RECORD_HEADER。

Calendar Description automatically generated

查看同一 CClfsBaseFilePersisted 对象的其他方法,在 CClfsBaseFilePersisted::AddContainer 中,同样通过 CClfsBaseFile::GetBaseLogRecord 获取 _CLFS_BASE_RECORD_HEADER 的地址。

Text Description automatically generated

接下来,使用 cbOffset 调用 CClfsBaseFile::OffsetToAddr,获取 _CLFS_CONTAINER_CONTEXT 的地址,并将 cboffset 存储在 rgbcontainers 数组中,该数组位于 _CLFS_BASE_RECORD_HEADER 的偏移量 0x328 处。

Graphical user interface, application Description automatically generated

CClfsBaseFile::OffsetToAddr 函数用于从偏移量查找结构地址

Graphical user interface, text, application Description automatically generated

此时,将存储在 0x328 处的容器偏移量仍为 0,因为我们尚未添加容器。

Background pattern Description automatically generated with low confidence

PoC 调用了两次 CreateLogFile,第一次使用畸形文件 MyLog.blf,第二次使用正常的 MyLogxxx.blf 文件,因此我们必须在上述所有位置两次停止调试,并在记事本中记录两个文件上述结构的地址。

Text Description automatically generated

让我们快进到 CLFS!CClfsLogFcbPhysical::AllocContainer,在其上设置断点并运行到那里。

当 POC 到达 AddLogContainer() 时,我们停在断点处。

A picture containing application Description automatically generated

我们还在 CClfsBaseFilePersisted::AddContainer+176 处设置一个断点,之前我们看到在那里会找到 _CLFS_CONTAINER_CONTEXT 结构的偏移量和指针。

A screenshot of a computer Description automatically generated with medium confidence

Graphical user interface, text, application Description automatically generated

当调试器中断时,我们可以看到偏移量为 0x1468。

Text Description automatically generated

在 RAX 中,将返回 _CLFS_CONTAINER_CONTEXT 结构的地址。

Graphical user interface, text, application, table Description automatically generated

该结构仍然是空的,因为尚未添加容器。

Text Description automatically generated

注意,我们写入畸形文件偏移 0x868 处的 SignatureOffset=0x50 值,减去 0x800(基块起始地址),将位于 _CLFS_LOG_BLOCK_HEADER 结构的偏移量 0x68 处。

Graphical user interface, text, application Description automatically generated

Text, letter Description automatically generated

当 PoC 使用畸形文件调用 AddLogContainer() 函数时,在 _CLFS_LOG_BLOCK_HEADER 的偏移量 0x68 处,我们写入的 0x50 值现在变成了内存中的 0xFFFF0050。

A picture containing text Description automatically generated

在某个时刻,该值被程序修改了,为了查看何时发生,在下一次执行中,我们将设置一个写入内存断点。

该偏移量存储在 r15 + 0x328 处(r15 指向 _CLFS_BASE_RECORD_HEADER 结构)

Text Description automatically generated with medium confidence

Graphical user interface Description automatically generated with low confidence

RBX 存储偏移量 0x1468。

A picture containing calendar Description automatically generated

因此,在基块地址 + 0x70 + 我们发现的偏移量 0x1468 处,将存在 CLFS_CONTAINER_CONTEXT 容器的地址。

Text Description automatically generated

在 CLFS_CONTAINER_CONTEXT 结构的偏移量 0x18 处,将是存储在那里的 pContainer 指针,我们可以设置写入断点并查看它何时被写入。

Text Description automatically generated

A picture containing text Description automatically generated

这是我们必须损坏的指针,因为在存在漏洞的函数中,它首先读取 CLFS_CONTAINER_CONTEXT,然后将其移动到 r15,接着读取 r15+18 的值,这正是我们刚刚设置写入断点的指针。

Graphical user interface, application, table Description automatically generated

Graphical user interface, application, Word Description automatically generated

它将 pContainer 存储在 struct_CClfsBaseFilePersisted 结构的偏移量 0x1c0 处。

Text, application, whiteboard Description automatically generated

在它停止多次之后,我们到达了它被损坏的时刻。指针地址的高位已从 FFs 改为零。

Calendar Description automatically generated

当第二次对畸形文件调用 AddLogContainer() 时,就会发生这种情况,前一个 MyLogxxx 的指针被损坏了。

问题发生是因为 SignaturesOffset(本应为 0x50)现在是 0xFFFF0050,因此允许在随后的 memset 中进行越界写入。

Graphical user interface, text, application Description automatically generated

Graphical user interface, text Description automatically generated

损坏“pContainer”指针:

memset() 函数将损坏下面的 _CLFS_CONTAINER_CONTEXT 结构,该结构对应于 MyLogxxx 文件,因为在创建时,它们彼此相距 0x11000 字节。

这样,它精确计算出写入下一个结构的位置,并将指针的高位清零,使其指向用户堆,我们已在该堆上创建了 HeapSpray。

畸形文件的基块结构正好位于 MyLogxxx 文件之前 0x11000 处。

畸形:

Shape Description automatically generated

MyLogxxx

A picture containing text Description automatically generated

RCX 小于 RDX,因为加上了 0xFFFF0050,而不是应有的 0x50。

Graphical user interface, text, application Description automatically generated

我们到达了 memset() 函数,它将 0xb0 字节设置为零,RCX 指向 MyLogxxx 文件的 CLFS_CONTAINER_CONTEXT 结构,具体指向 pContainer 的高五个字节。

Text Description automatically generated

这个指针将通过覆盖前几个字节而被损坏:

Text Description automatically generated

剩下的指向一个我们之前通过 HeapSpray 控制的内存地址

Text Description automatically generated

A picture containing text Description automatically generated

然后,MyLogxxx 文件的句柄将被关闭,并到达 CClfsBaseFilePersisted::RemoveContainer,漏洞最终被触发。

Text, application Description automatically generated with medium confidence

重新审视补丁

现在我们掌握了更多信息,我们注意到这里读取了 Base_Block.LOG_BLOCK_HEADER.SignaturesOffset 和 Base_Block.LOG_BLOCK_HEADER.TotalSectorCount

在补丁的第一部分中,SignaturesOffset 不应大于 0x7a00,在我们的例子中它原本是 0x50,如果它以大于 0x7a00 的值到达,它将把我们踢出。

A picture containing diagram Description automatically generated

在已打补丁的机器上运行 PoC,它将 0x50 与 0x7a00 比较,由于 0x50 较小,程序继续执行。

Text Description automatically generated

在接下来的块中,将畸形的 cbSymbolZone 加到 _CLFS_BASE_RECORD_HEADER 的结束地址上,并将这个和存储在 result_1 中。

Text Description automatically generated

然后,将基块的地址与 SignatureOffset 值相加,在正常文件中该值为 0x7980。

Text Description automatically generated

base_block 的最大地址为 0x7a00,现在 SymbolZone 最多允许在限制前 0x80 字节。

它将存储在 result_2 中,也就是说,这将是 SymbolZone 在基块内的最大限制,然后它比较两个结果,如果第一个大于第二个,则意味着越界。

Text Description automatically generated

Text Description automatically generated

显然第一个成员将大于第二个,并且程序不会继续,因为 cbSymbolZone + _CLFS_BASE_RECORD_HEADER 的结束地址 的和超过了限制(即 result_2),导致“越界”。

Graphical user interface, text, application Description automatically generated

损坏 SignatureOffset

我们最后需要弄清楚的是,SignatureOffset 的值 0x50 是如何变成 0xFFFF0050 的。那么,让我们重新开始,重启并在 CLFS!CClfsBaseFilePersisted::LoadContainerQ 处停下,此时内存中的值尚未改变,仍为 0x50。

在 SignatureOffset 的偏移量 0x68 处设置一个访问断点。

自动生成的日历描述

经过几次停止后,我们在 ClfsEncodeBlockPrivate 中检测到修改值的正确时刻。

自动生成的图形用户界面、文字描述

这个函数没有被打补丁,因此这可能是由 0x50 的低值以及其他被操纵的值引起的行为。

在被精心构造的值中,我们可以看到一个名为 ccoffsetArray 的值,它在 _CLFS_BASE_RECORD_HEADER 结构中的名称为 rgClients,表示指向 Client Context 对象的偏移量数组。

rgClients 字段位于偏移量 0x138(_CLFS_BASE_RECORD_HEADER 结构的 0x9a8-0x800-0x70)处。

自动生成的图形用户界面、表格描述

自动生成的文本描述,中等置信度

在 PoC 中,该值被篡改,指向一个伪造的客户端上下文对象,称为 FakeClientContext

自动生成的文本、白板描述

自动生成的计算机截图,中等置信度

这是 Client Context 结构 _CLFS_CLIENT_CONTEXT

struct _CLFS_CLIENT_CONTEXT { CLFS_NODE_ID cidNode; CLFS_CLIENT_ID cidClient; USHORT fAttributes; ULONG cbFlushThreshold; ULONG cShadowSectors; ULONGLONG cbUndoCommitment; LARGE_INTEGER llCreateTime; LARGE_INTEGER llAccessTime; LARGE_INTEGER llWriteTime; CLFS_LSN lsnOwnerPage; CLFS_LSN lsnArchiveTail; CLFS_LSN lsnBase; CLFS_LSN lsnLast; CLFS_LSN lsnRestart; CLFS_LSN lsnPhysicalBase; CLFS_LSN lsnUnused1; CLFS_LSN lsnUnused2; CLFS_LOG_STATE eState; union { HANDLE hSecurityContext; ULONGLONG ullAlignment; }; };

eState 值位于结构起始偏移量 0x78 处,在精心构造的文件中为 0x23a0+0x78。

自动生成的文本描述,中等置信度

自动生成的图表、散点图描述

该值显示日志的状态。

typedef UCHAR CLFS_LOG_STATE, *PCLFS_LOG_STATE;
const CLFS_LOG_STATE CLFS_LOG_UNINITIALIZED = 0x01;
const CLFS_LOG_STATE CLFS_LOG_INITIALIZED = 0x02;
const CLFS_LOG_STATE CLFS_LOG_ACTIVE = 0x04;
const CLFS_LOG_STATE CLFS_LOG_PENDING_DELETE = 0x08;
const CLFS_LOG_STATE CLFS_LOG_PENDING_ARCHIVE = 0x10;
const CLFS_LOG_STATE CLFS_LOG_SHUTDOWN = 0x20;
const CLFS_LOG_STATE CLFS_LOG_MULTIPLEXED = 0x40;
const CLFS_LOG_STATE CLFS_LOG_SECURE = 0x80;

该值被设置为 CLFS_LOG_STATE CLFS_LOG_SHUTDOWN = 0x20

另一个被篡改的值是 fAttributes,它对应于与基本日志文件关联的 FILE_ATTRIBUTE 标志集(例如系统和隐藏)。

自动生成的图形用户界面,低置信度

自动生成的文本、字母描述

由于该字段从 0xa 处开始一个字节,并跨越两个字节,因此 fAttributes 的值为 0x100。

自动生成的包含表格的图片描述

自动生成的图形用户界面、文本、应用程序描述

最后,有一个 blocknameoffset 值,它指向偏移量 0x1bb8,我的意思是,加上 0x78 和 0x800 就指向文件的偏移量 0x2428。

自动生成的文本描述

自动生成的文本、字母描述

请注意,指向 Client Context 的偏移量是 0x1b30

自动生成的表格描述

因此,Client Context 位于偏移量 0x23a0。

自动生成的文本描述,中等置信度

自动生成的表格描述

而在它前面 0x10 处,是对应于 blocknameoffset 的值。

自动生成的文本、字母描述

自动生成的表格描述,低置信度

这将指向包含名称的字符串

最后一个是 blockattributeoffset,它在 0x2394 处,位于 Client Context 之前 0xC 的位置。

自动生成的表格描述

最后这两个值属于一个位于 Client Context 之前、长度为 0x30 字节的结构,称为 _CLFSHASHSYM

typedef struct _CLFSHASHSYM
{
CLFS_NODE_ID cidNode;
ULONG ulHash;
ULONG cbHash;
ULONGLONG ulBelow;
ULONGLONG ulAbove;
LONG cbSymName;
LONG cbOffset;
BOOLEAN fDeleted;
} CLFSHASHSYM, *PCLFSHASHSYM;

自动生成的包含图表的图片描述

自动生成的文本描述

它们位于 _CLFSHASHSYM 结构起始偏移量 0x20 和 0x24 字节处,因此在 _CLFSHASHSYM 结构中,POC 中称为 blockNameOffset 的值是 cbSymName 字段,而 blockAttributteoffset 是 cbOffset 字段。

自动生成的包含文本的图片描述

自动生成的文本描述,中等置信度

这些就是被篡改的值,现在我们需要看看它们如何影响将我们的 SignaturesOffset 从 0x50 值更改为 0xFFFF0050。

让我们来看看 CClfsBaseFile::AcquireClientContext() 函数,它应该返回客户端上下文。

自动生成的图形用户界面、文本、应用程序、电子邮件描述

它调用 CClfsBaseFile::GetSymbol,第四个参数将是 _CLFS_CLIENT_CONTEXT **,用于存储指向 Client Context 的指针。

自动生成的图形用户界面、文本、应用程序描述

在 CClfsBaseFile::GetSymbol 函数内部,我们将被篡改的 ccoffsetArray 偏移量传递给 CClfsBaseFile::OffsetToAddr,并获取客户端上下文的地址。让我们在那里设置一个断点,这样当调用使用 CreatelogFile 创建的文件时,它就会停止。

自动生成的图形用户界面、应用程序描述,中等置信度

它停在带有精心构造的 ccoffsetArray 参数的地方。

自动生成的图形用户界面、应用程序描述

自动生成的表格描述

CClfsBaseFile::OffsetToAddr 函数返回假的 Client Context

自动生成的包含图形用户界面的图片描述

并且检查 cbOffset 的值是否不为零,因为在 RAX 中的 _CLFS_CLIENT_CONTEXT 结构之前发现了 0xC。

自动生成的计算机截图描述

自动生成的包含文本的图片描述

然后它将 cbOffset 与 ccoffsetArray(位于 RSI 中)进行比较,它们必须相等,否则我们会得到错误。

自动生成的计算机截图描述

它还检查 cbSymName 必须等于 cbOffset+0x88,否则我们也会得到错误。

自动生成的图形用户界面、文本、应用程序描述,中等置信度

最后,它比较 cidClient 字节是否为零

自动生成的包含图表的图片描述

如果所有这些检查都成功,则 client context 将被保存。

自动生成的计算机截图描述

函数 r14 的输出指向 Client Context

自动生成的图形用户界面、文本、应用程序描述

从 CClfsLogFcbPhysical::Initialize 退出时,我们将拥有 CLFS_CLIENT_CONTEXT 的地址。

自动生成的文本描述

现在它读取 fAttributes 的值(0x100)

自动生成的图形用户界面、文本、应用程序描述

这个函数属于类 CClfsLogFcbPhysical

自动生成的文本描述

自动生成的图形用户界面、文本、应用程序描述

它被分配在这里,大小为 0x15d0,标记为 “ClfC”

自动生成的文本描述,中等置信度

让我们创建一个结构来存储我们正在逆向的内容,我们称之为:struct_CClfsLogFcbPhysical.

自动生成的包含表格的图片描述

注意,在 0x2b0 处,它保存了 CClfsBaseFilePersisted 结构的地址。

自动生成的包含应用程序的图片描述

在结构中保存了许多值之后,它进入一个重要的部分,它用 0x20 测试 eState。

自动生成的图形用户界面、文本、应用程序描述

自动生成的图形用户界面、文本、应用程序、表格描述

由于精心构造的值是 0x20,测试将返回 1。

自动生成的表格描述

自动生成的图形用户界面、文本、应用程序描述

我们看到在构造函数中,vtable 是

自动生成的文本描述

它将检查文件是否是多路复用的。

自动生成的应用程序描述,图形用户界面

因此,它沿着期望的路径前进,到达 CClfsLogFcbPhysical::ResetLog.

自动生成的图形用户界面、应用程序描述

自动生成的文本、应用程序、表格描述,中等置信度

几个字段被初始化为零,除了一个被初始化为 0xFFFFFFFF00000000。

自动生成的图形用户界面描述,低置信度

这里检索 Client Context

自动生成的图形用户界面、应用程序描述,中等置信度

它存储值 0xFFFFFFFF00000000。

自动生成的图形用户界面、应用程序描述

自动生成的图形用户界面、应用程序描述

自动生成的包含日历的图片描述

它写入 0xFFFFFFFF 到偏移量 0x5c,这是 CLFS_LSN lsnRestart.ullOffset 的高位部分

自动生成的文本描述,中等置信度

自动生成的图形用户界面、文本、字母描述

现在执行 ClfsEncodeBlockPrivate() 函数,该函数负责将 0x50 覆盖为 0xFFFF0050,正如我们之前所见。

它在那里读取 SignatureOffset = 0x50 的值,该值仍然如我们放在精心构造的文件中一样,并将其添加到 CLFS_LOG_BLOCK_HEADER 的起始地址。

自动生成的文本描述

这是一个正在写入 2 字节的循环,就像 SignatureOffset 没有指向一个正确的值一样,在正常文件中该值是一个高值,例如 0x3f8,这使得它写入更靠前的位置,而这里它将写入相同的 CLFS_LOG_BLOCK_HEADER

其想法是更改写入目标,以试图破坏 SignatureOffset 值。

正常文件

自动生成的表格描述

此时,它将开始循环并写入两个字节。

自动生成的图形用户界面、应用程序描述

计数器必须达到值 0x3d 才能退出循环。

自动生成的图形用户界面、应用程序描述

RCX 从 0x200 开始递增,我们已经处于第三个周期,其值为 0x600

自动生成的图形用户界面、文本、应用程序描述

在第 0xe 次迭代中,RCX 为 0x1a00

自动生成的文本描述,图形用户界面

自动生成的包含日历的图片描述

那就是他写入 0xFFFFFFFF000000 的地方。

自动生成的图形用户界面、文本、应用程序描述

自动生成的表格描述,中等置信度

它正在读取最后两个字节 FFFF

自动生成的文本描述

然后将其复制到 R8 中

自动生成的包含日历的图片描述

自动生成的包含日历的图片描述

正如我们所见,这个值至关重要,因为它允许绕过检查并越界写入,以破坏之后 memset() 函数中文件的 pContainer 指针,并在顶部写入零,使其指向我们控制的内存(HeapSpray)。

在 CClfsBaseFilePersisted::AllocSymbol 中,相同的加法将获得 memset 的目标地址,即 cbSymbolZone + CLFS_BASE_RECORD_HEADER 的最终地址,在此之前将其与 Base_block + 0xFFFF0050 进行比较,因此等式两边都有被破坏的值。

CbSymbolZone= 0x1114B

这是一个被破坏的值,加上 CLFS_BASE_RECORD_HEADER 的最终地址将使其越界写入,而比较的另一项应该是 Base Block + SignatureOffset 的地址,SignatureOffset =0xFFFF0050 允许此检查通过,并在 memset() 中越界写入,将指针的顶部归零,该指针将保持指向我们的 HeapSpray。

自动生成的图形用户界面、应用程序、表格描述

因为 RCX 小于 RDX。

自动生成的图形用户界面、应用程序描述

正如我们之前所见。(值可能有所不同,因为它们属于之前的执行)

它将破坏指针,将最高字节设置为 0

自动生成的表格描述

使其指向我们通过 HeapSpray 控制的内存区域

自动生成的计算机截图描述,低置信度

自动生成的表格描述

因此,当漏洞被触发时,我们到达 CClfsBaseFilePersisted::RemoveContainer

自动生成的包含图形用户界面的图片描述

那里将存在已经被破坏的指针,并且可以像我们之前看到的那样进行利用。

自动生成的图形用户界面、应用程序描述

此时我们已成功利用漏洞,它导致我们可以控制允许读取 SYSTEM 令牌并将其写入我们自己的进程以实现本地权限提升的函数。

我们希望您觉得它有用,如果有任何疑问,可以通过 [email protected] 和 [email protected] 联系我们。

享受吧!

下载工具