CVE-2025-21333 - Windows Hyper-V NT Kernel Integration VSP Elevation of Privilege Vulnerabilities
A vulnerability in the Windows Hyper-V NT Kernel Integration VSP driver exists due to a vulnerable function, VkiRootAdjustSecurityDescriptorForVmwp()
, which can be invoked from user mode. This leads to a heap-based buffer overflow, ultimately resulting in privilege escalation.
CVE-2025-21333: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-21333
Vulnerability Type: Heap-based Buffer Overflow
Tested On: Windows 11 23H2
Driver Version: vkrnlintvsp.sys - 10.0.22621.2506
Requirements
To exploit this vulnerability, Windows Sandbox must be enabled in “Turn Windows features on or off”.
Vulnerability analysis
The vulnerability exists in the VkiRootAdjustSecurityDescriptorForVmwp()
function of the vkrnlintvsp.sys
driver, where the Dacl
is user-controllable. The memmove
operation uses Dacl->AclSize
without verifying the user-supplied size, leading to a heap-based buffer overflow by copying unvalidated data into the Paged Pool.
__int64 __fastcall VkiRootAdjustSecurityDescriptorForVmwp(void *a1, char a2)
{
struct _ACL *v4; // rdi
NTSTATUS ObjectSecurity; // ebx
__int16 v6; // bx
__int16 v7; // ax
WORD v8; // bx
struct _ACL *Pool2; // rax
int v10; // esi
unsigned __int8 DaclDefaulted[8]; // [rsp+20h] [rbp-50h] BYREF
PACL Dacl; // [rsp+28h] [rbp-48h] BYREF
PSID Sid; // [rsp+30h] [rbp-40h] BYREF
PSID P; // [rsp+38h] [rbp-38h] BYREF
PSECURITY_DESCRIPTOR SecurityDescriptor; // [rsp+40h] [rbp-30h] BYREF
_OWORD v17[2]; // [rsp+48h] [rbp-28h] BYREF
__int64 v18; // [rsp+68h] [rbp-8h]
unsigned __int8 DaclPresent; // [rsp+A0h] [rbp+30h] BYREF
unsigned __int8 MemoryAllocated; // [rsp+A8h] [rbp+38h] BYREF
Dacl = 0LL;
DaclDefaulted[0] = 0;
SecurityDescriptor = 0LL;
Sid = 0LL;
P = 0LL;
memset(v17, 0, sizeof(v17));
v18 = 0LL;
DaclPresent = 0;
v4 = 0LL;
MemoryAllocated = 0;
ObjectSecurity = ObGetObjectSecurity(a1, &SecurityDescriptor, &MemoryAllocated);
if ( ObjectSecurity >= 0 )
{
if ( !SecurityDescriptor )
goto LABEL_15;
ObjectSecurity = RtlGetDaclSecurityDescriptor(SecurityDescriptor, &DaclPresent, &Dacl, DaclDefaulted);
if ( ObjectSecurity < 0 )
goto LABEL_16;
if ( DaclPresent && Dacl )
{
ObjectSecurity = SeConvertStringSidToSid(L"S-1-5-83-0", &Sid);
if ( ObjectSecurity >= 0 )
{
ObjectSecurity = SeConvertStringSidToSid(
L"S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704",
&P);
if ( ObjectSecurity >= 0 )
{
v6 = RtlLengthSid(Sid);
v7 = RtlLengthSid(P);
v8 = Dacl->AclSize + v7 + v6 + 16;
Pool2 = (struct _ACL *)ExAllocatePool2(256LL, v8, 1867671894LL);
v4 = Pool2;
if ( Pool2 )
{
memmove(Pool2, Dacl, Dacl->AclSize); // Vulnerable memmove/memcpy call
v4->AclSize = v8;
v10 = a2 != 0 ? 2 : 0;
ObjectSecurity = RtlAddAccessAllowedAce(v4, 2u, v10 + 2031617, Sid);
if ( ObjectSecurity >= 0 )
{
ObjectSecurity = RtlAddAccessAllowedAce(v4, 2u, v10 + 2031617, P);
if ( ObjectSecurity >= 0 )
{
ObjectSecurity = RtlCreateSecurityDescriptor(v17, 1u);
if ( ObjectSecurity >= 0 )
{
ObjectSecurity = RtlSetDaclSecurityDescriptor(v17, 1u, v4, 0);
if ( ObjectSecurity >= 0 )
ObjectSecurity = ObSetSecurityObjectByPointer(a1, 4LL, v17);
}
}
}
}
else
{
ObjectSecurity = -1073741801;
}
}
}
}
else
{
LABEL_15:
ObjectSecurity = 0;
}
}
LABEL_16:
if ( Sid )
ExFreePoolWithTag(Sid, 0);
if ( P )
ExFreePoolWithTag(P, 0);
if ( v4 )
ExFreePoolWithTag(v4, 0x6F526956u);
ObReleaseObjectSecurity(SecurityDescriptor, MemoryAllocated);
return (unsigned int)ObjectSecurity;
}
The kernel has a mechanism to register and use specific callbacks for some predetermined drivers. When the specific driver gets loaded, the driver provides a table which contains list of callbacks that the kernel can use. During the initialization, the kernel calls ExRegisterHost()
function with some specific inputs this is specific for each driver to register some predetermined unexported functions.
In this case, it was discovered that VkiRootCalloutCreateEvent()
is one of the callback functions that can be invoked by NtCreateCrossVmEvent()
. Therefore, to reach the vulnerable function call, we need to invoke NtCreateCrossVmEvent()
from user space.
NtCreateCrossVmEvent()
is an undocumented function that takes an OBJECT_ATTRIBUTES
structure as an argument. This structure includes a SecurityDescriptor
(SECURITY_DESCRIPTOR
), which contains the security information associated with the object — including the Dacl
. This is where the payload should be injected in order to trigger the vulnerability.
NtCreateCrossVmEvent(
_Out_ PHANDLE CrossVmEvent,
_In_ ACCESS_MASK DesiredAccess,
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_ ULONG CrossVmEventFlags,
_In_ LPCGUID VMID,
_In_ LPCGUID ServiceID
);
Exploit
Tested on: Windows 11 23H2
Working POC: https://github.com/ghostbyt3/WinDriver-EXP/tree/main/CVE-2025-21333/POC
Acknowledgements
- The original PoC was developed by Alessandro Iandoli, on which the above POC is created.
- Corentin Bayet and Paul Fariello for their outstanding explanation of Windows Pool
- Yarden Shafir for the IORING method for an arbitrary Read/Write exploit primitive.
References: