Windows Admin-to-Kernel Elevation of Privilege (CVE-2024-21338)

Escrito por  Rafael Felix

Abstract

On February 13th, 2024, during the patch Tuesday, Microsoft disclosed the CVE-2024-21338 based on the security report made by Jan Vojtěšek with Avast, which is a new Windowsadmin to kernel” elevation of privilege vulnerability, this vulnerability allows for malicious actors, in this case, mainly, the Lazarus group in cooperation with North Korea, to get kernel access through their FudModule.dll rootkit. We all know how much the BYOVD (“Bring Your Own Vulnerable Driver”) ideology was being used all around the world by threat actors and malware developers that exploits the ability to bring your own, signed, vulnerable driver to achieve kernel access, but this goes beyond BYOVD. This technique applies to an already installed Windows Driver in the system: meet appid.sys, which is a driver originally used by the Windows AppLocker, from the Microsoft website, “AppLocker helps you control which apps and files users can run“.

The CVE-2024-21338 vulnerability exploits the call to a specific function inside the appid.sys driver through IOCTL (“Input-output control“) communication, specifically, the AipSmartHashImageFile function which can be called from the vulnerable IOCTL control code of 0x22A018. By calling the control code with a specifically crafted input buffer, we are able to corrupt the PreviousMode field in the _KTHREAD thread context. The field PreviousMode determines if a direct syscall (Nt, Zw) is being called by the kernel, or the user. With that in mind, we are now able to call direct syscalls and make them affect the kernel of the operating system, thus, giving a malicious threat actor kernel access to the victim.

Analysis

The main focus of the FudModule.dll rootkit made by Lazarus is to circumvent the detection of EDR/Anti-Virus products by either tampering them, or making their own functionality hidden from them as seen on the “Lazarus and the FudModule Rootkit: Beyond BYOVD with an Admin-to-Kernel Zero-Day” post made by Avast, however, we are not going to be focusing on how the rootkit itself works in this post. We’ll be exploring how the appid.sys vulnerability works, how to exploit it, and what was done in the MSRC patch to fix it on the latest versions of Windows.

On February 13th, 2024, during the patch tuesday, the Microsoft Security Response Center (MSRC) disclosed a brand new vulnerability entitled “CVE-2024-21338“, this vulnerability allows an attacker to read and write on kernel memory by sending a specifically crafted gadget through a exposed function that allows for IOCTL (Input-output control) communication.

First of all, as of any communication through IOCTL on Windows, there needs to be a handle opened to the target driver we are communicating with. In this case, by analyzing the appid.sys (AppLocker) driver on IDA Pro, we can see on the DriverEntry function, that the object we need to open a handle to is “\\Device\\AppID” as seen on the screenshot below.

DriverEntry function, that the object we need to open a handle to is "\\Device\\AppID" as seen on the screenshot below.

However, when we try to open a handle from Administrator to “\\Device\\AppID” through NtCreateFile we are faced with an NTSTATUS code of 0xC0000022 (STATUS_ACCESS_DENIED), which means that the Administrator user does not have the right access to open a handle to the AppID device. This brings us to question which permission we need to successfully open a handle to it. By investigating the vulnerable function “AipSmartHashImageFile” to try and discover why we are not able to open a device handle to “\\Device\\AppID“, we figured that through xrefs of the “AipSmartHashImageFile” function, the “AipDeviceIoControlDispatch” function can be found, which relates to the function that handles all IOCTL calls to the driver through the Device handle.

"AipDeviceIoControlDispatch" function can be found which relates to the function that handles all IOCTL calls to the driver through the Device handle.

By opening this call, we can see that the “AipSmartHashImageFile” function is being called through the IOCTL code of “0x22A018” as seen in the screenshot below.

"AipSmartHashImageFile" function is being called through the IOCTL code of "0x22A018" as seen in the screenshot below.

Ok, now that we have our IOCTL code, what can we do with it? Besides sending a control request to this specific function through “NtDeviceIoControl“, IOCTL codes have their own structure. By breaking down the IOCTL code to the “AipSmartHashImageFile” function, based on the IOCTL Microsoft documentation, we can see that this IOCTL code needs the 0x0002 (FILE_WRITE_ACCESS) permission to be called, which means that – most likely – to open a handle to our driver we would need the same permission, but which user gives us this permission on the AppID device? You may ask. By using the “WinObj” tool present in the SysInternals Suite we can list all system objects there are present in the current installation, this includes all devices to all drivers too. By checking the permissions to the “AppID” device on the “WinObj” application, we can see that the “Administrators” group does not have the required “Write” access in the ACL (Access control list) of AppID as shown on the screenshot below.

"Administrators" group does not have the required "Write" access in the ACL (Access control list) of AppID as shown on the screenshow below.

And we can also see that the “LOCAL SERVICE” username does have the required “Write” permission to successfully access and call the function we need through the IOCTL code.

"LOCAL SERVICE" username does have the required "Write" permission to successfully access and call the function we need through the IOCTL code.

With that in mind, we now go to the exploitation phase, where we are going to exploit the “Windows Access Tokens” through “Token Impersonation” and send the meticulously crafted gadget to the “AipSmartHashImageFile” function.

Exploitation

The exploitation phase begins with gaining access to the “\\Device\\AppID” handle, for that, we need the “LOCAL SERVICE” access in our process, to gain this access, we need to apply some Access Token Impersonation techniques to achieve the “LOCAL SERVICE” permission in our process.

What are Windows Access Tokens? From the Microsoft documentation an access token is “an object that describes the security context of a process or thread. The information in a token includes the identity and privileges of the user account associated with the process or thread. When a user logs on, the system verifies the user’s password by comparing it with information stored in a security database. If the password is authenticated, the system produces an access token. Every process executed on behalf of this user has a copy of this access token”.

These tokens can be manipulated in such a way that we can steal them from remote processes in Windows by checking if processes have specific privileges. In this case from Elevated (Administrator) to “NT AUTHORITY\SYSTEM” (S-1-5-18), two privileges are needed: “SeDebugPrivilege” and “SeAssignPrimaryTokenPrivilege“.

The “winlogon.exe” process exposes a token with both privileges because by design, this process runs as SYSTEM due to its requirement to manipulate Windows’s logon states, and users can easily interact with, thus, allowing a user to duplicate the “NT AUTHORITY\SYSTEM” access token from winlogon.

Right after successfully duplicating the SYSTEM token using “DuplicateTokenEx” we can now have access to the “NT AUTHORITY\LOCAL SERVICE” (S-1-5-19) access token by looking for any token with the said privileges: “SeDebugPrivilege” and “SeImpersonatePrivilege“, normally, most of the “svchost.exe” processes run as “NT AUTHORITY\LOCAL SERVICE” so finding one should be really easy and fast.

Now that we have “NT AUTHORITY\LOCAL SERVICE” access, which means we have “Write” access to both the “AppID” device handle (“\\Device\\AppID“) and the 0x22A018 control code, we can now successfully open a handle to the mentioned device handle. By having the proper handle opened and access to the “AipSmartHashImageFile” function, we can know craft the payload to trigger the “PreviousMode” byte memory “corruption”.

PreviousMode is a field present in every _KTHREAD structure that indicates if a syscall is going to be called from KernelMode, or UserMode. With that in mind, some Nt or Zw (direct) syscalls check for the “PreviousMode” value by checking the current thread’s “PreviousMode” field, here’s an example with the “NtWriteVirtualMemory” syscall code snippet taken from “ntoskrnl.exe” disassembly:

"NtWriteVirtualMemory" syscall code snippet taken from "ntoskrnl.exe" disassembly:

The “PreviousMode” field is a simple enum that has two members, “UserMode” and “KernelMode“, that is, KernelMode being 0 and UserMode being 1. Knowing that we can possibly modify this value in the memory’s current _KTHREAD structure, we delve into how the memory corruption to this field actually works.

The main culprit that allows the “PreviousMode” exploit to actually work is the “AppHashComputeImageHashInternal” which gets called by the function “AppHashComputeFileHashesInternal” inside the “AipSmartHashImageFile” function as shown in the screenshow below.

"AppHashComputeFileHashesInternal" inside the "AipSmartHashImageFile" function as shown in the screenshow below.

As we can see in the screenshot below, two arguments are passed through these chain of functions, at last being the “AppHashComputeImageHashInternal” that executes a function pointer controlled by the user based on a buffer that is controlled by the user, in this case, controlled in our call to the 0x22A018 control code.

"AppHashComputeImageHashInternal" that executes a function pointer controlled by the user based on a buffer that is controlled by the user, in this case, controlled in our call to the 0x22A018 control code.

Now, can’t we just craft a user-mode shellcode to be sent to the control code telling the “PreviousMode” of the current thread to be decreased to 0? Unfortunately, we cannot. Meet kCFG (Kernel Control Flow Guard) and SMEP (Supervisor Mode Execution Prevention).

  • SMEP (Supervisor Mode Execution Prevention) will not allow us to execute the code in user-mode when in a higher privilege level which will not allow us to execute a user-mode shellcode to achieve what we want.
  • kCFG (Kernel Control Flow Guard) will not allow us to invoke arbitrary or unverified function pointers, particularly those pointing to user-mode addresses or locations outside the established CFG bitmap of valid kernel functions. This mechanism ensures that all indirect function calls are checked against a control flow integrity policy, preventing exploitation that diverts the execution flow to malicious or unexpected code segments.

With both protections in mind, we now need to find a function that is present in the CFG bitmap of valid kernel functions, allowing it to be used as a legitimate target for indirect function calls. This function should ideally provide capabilities that can be leveraged for further exploitation, in this case, memory manipulation, while adhering to the constraints set by kCFG and SMEP.

Thanks to the previous research on this matter, the “Windows AppLocker Driver Elevation of Privilege (CVE-2024-21338)” post goes into details on how to find valid CFG functions inside the CFG bitmap. The one chosen in our PoC is the “ExpProfileDelete” which is a valid CFG function, and it’s functionality is practically perfect for what we want. By looking at the disassembly of the “ExpProfileDelete” function:

disassembly of the "ExpProfileDelete" function:

We can see that the function receives an argument which will be passed to the function “ObfDereferenceObject” that will then, cause a decrement in the address passed as the argument to the “ExpProfileDelete” function, causing the “PreviousMode” address data to go from 1 to 0, causing the “NtWriteVirtualMemory” and “NtReadVirtualMemory” syscalls to interpret its calls as from kernel-mode. Allowing a malicious user to control the kernel-space memory of the system that was exploited using this vulnerability.

Patch

The patch made by the Microsoft Security Response Center (MSRC) in February adds a “ExGetPreviousMode” check before the “AipSmartHashImageFile” call, this prevents user-mode initiated calls from triggering the said callback function. The image below shows the disassembly of the same control code on the newer versions of Windows.

The image below shows the disassembly of the same control code on newer versions of Windows.

Image source: https://decoded.avast.io/janvojtesek/lazarus-and-the-fudmodule-rootkit-beyond-byovd-with-an-admin-to-kernel-zero-day/

By using the BinDiff tool which shows differences between two binaries, we can also see the changes applied in the patch, which are just the “ExGetPreviousMode” check.

we can also see the changes applied in the patch, which are just the "ExGetPreviousMode" check.

Image source: https://nero22k.github.io/posts/windows-applocker-driver-elevation-of-privilege-cve-2024-21338/#patch-diffing

Proof of concept

Based on all concepts shown, a Proof of concept (PoC) was crafted to show the severity of this vulnerability. Below, is an image showing the call-stack of execution of the PoC, following the same principles shown on the post above.

Below, is an image showing the call-stack of execution of the PoC, following the same principles shown on the post above.

The link to the PoC is available on GitHub here: https://github.com/hakaioffsec/CVE-2024-21338.

Conclusion

In conclusion, the CVE-2024-21338 vulnerability presents a significant elevation of privilege threat in Windows systems, notably exploited by the Lazarus group to bypass conventional security measures such as EDRs and antiviruses. This vulnerability demonstrates a sophisticated technique that goes beyond the already concerning BYOVD approach, manipulating the “PreviousMode” field within the kernel to achieve unauthorized access alongside an extense token impersonation technique allowing to achieve execution from admin-to-kernel respectively. The response from Microsoft, incorporating a critical patch in their February update, highlights the ongoing cat-and-mouse game between cybersecurity professionals and threat actors. The CVE-2024-21338 research helped us to understand even more how specific vulnerabilities like this can happen in the wild.

References

Logo da Hakai.