Technical Analysis of Zero-Day Vulnerability CVE-2022-37969 - Part 1: Root Cause Analysis

On September 2, 2022, Zscaler Threatlabz captured an in-the-wild 0-day exploit in the Windows Common Log File System Driver (CLFS.sys) and reported this discovery to Microsoft. In the September Tuesday patch, Microsoft fixed this vulnerability that was identified as CVE-2022-37969, which is a Windows Common Log File System Driver elevation of privilege vulnerability. An attacker who successfully exploits this vulnerability may gain SYSTEM privileges. The 0-day exploit can execute the privilege escalation successfully on Windows 10 and Windows 11 prior to the September patch. The cause of the vulnerability is due to the lack of a strict bounds check on the field cbSymbolZone in the Base Record Header for the base log file (BLF) in CLFS.sys. If the field cbSymbolZone is set to an invalid offset, an out-of-bound write will occur at the invalid offset. In this two-part blog series, we will demystify the vulnerability and the 0-day exploit discovered in-the-wild. The blogs consist of two parts: an analysis of the root cause, and an analysis of the exploit. In this blog, we first present a detailed analysis of the root cause for CVE-2022-37969.

Debugging Environment

All analysis and debugging in this two-part blog series are conducted in the following environment.

Windows 11 21H2 version 22000.918
CLFS.sys 10.0.22000.918

Introduction to CLFS Internals

The Common Log File System (CLFS) is a general-purpose logging subsystem that can be used by applications running in both kernel mode and user mode for building high-performance transaction logs, and is implemented in the driver CLFS.sys. The Common Log File System generates transaction logs in a base log file (BLF). The concepts and terminology introductions for CLFS are specified in the official documentation from Microsoft.

Prior to using CLFS, a log file is created using the CreateLogFile API. A log file is composed of a base log file that consists of metadata blocks, and several containers that store the actual data. The AddLogContainer API is used to add a container to the physical log that is associated with the log handle.

Figure 1 illustrates the BLF format based on the official CLFS documentation and Alex Ionescu’s unofficial documentation.

Figure 1. Base Log File (BLF) Format

A base log file is made up of six different metadata blocks, which are the control block, base block, and truncate block along with their corresponding block shadows. The three types of records (Control Record, Base Record, and Truncate Record) can reside in these blocks. This blog only focuses on the Base Record that is relevant to this vulnerability. The Base Record comprises the symbol tables that store information on the client contexts, container contexts, and security contexts associated with the base log file.

Every log block begins with a log block header, with the structure defined below:

typedef struct _CLFS_LOG_BLOCK_HEADER
{
UCHAR MajorVersion;
UCHAR MinorVersion;
UCHAR Usn;
CLFS_CLIENT_ID ClientId;
USHORT TotalSectorCount;
USHORT ValidSectorCount;
ULONG Padding;
ULONG Checksum;
ULONG Flags;
CLFS_LSN CurrentLsn;
CLFS_LSN NextLsn;
ULONG RecordOffsets[16];
ULONG SignaturesOffset;
} CLFS_LOG_BLOCK_HEADER, *PCLFS_LOG_BLOCK_HEADER;

The memory layout of the CLFS_LOG_BLOCK_HEADER structure whose size is 0x70 bytes has been illustrated in Figure 1. In Figure 1, the Base Block starts at offset 0x800 and ends at offset 0x71FF in a .BLF file and starts with a Log Block Header (0x70 bytes), followed by the Base Record Header, followed by records. The Base Record Header can be represented by the CLFS_BASE_RECORD_HEADER structure described in Figure 2.

Figure 2. The definition of the CLFS_BASE_RECORD_HEADER structure

The structure layout of the CLFS_BASE_RECORD_HEADER structure is illustrated in Figure 3.

Figure 3. The structure layout of Base Record Header in a .BLF file

The Base Record begins with a header (CLFS_BASE_RECORD_HEADER) whose size is 0x1338 bytes, followed by related context data. In the CLFS_BASE_RECORD_HEADER structure, some important fields related to this vulnerability are described below:

rgClients represents the array of offsets that point to the Client Context object. 
rgContainers represents the array of offsets that point to the Container Context object. 
cbSymbolZone represents the next free available offset for a new symbol in the symbol zone.

In the Base Record, the Client Context, Container Context, and Shared Security Context are represented by symbols, which are preceded by the CLFSHASHSYM structure defined below:

typedef struct _CLFS_NODE_ID {
ULONG cType;
ULONG cbNode;
} CLFS_NODE_ID, *PCLFS_NODE_ID;

typedef struct _CLFSHASHSYM
{
CLFS_NODE_ID cidNode;
ULONG ulHash;
ULONG cbHash;
ULONGLONG ulBelow;
ULONGLONG ulAbove;
LONG cbSymName;
LONG cbOffset;
BOOLEAN fDeleted;
} CLFSHASHSYM, *PCLFSHASHSYM;

The memory layout of the CLFSHASHSYM structure is illustrated in Figure 4, followed by various context objects.

Figure 4. CLFSHASHSYM structure (symbol header)

In the Base Record, the client context is used to identify a client for a log file. At least one client context can be created in a base log file. The client context is represented by the CLFS_CLIENT_CONTEXT structure defined below:

typedef struct _CLFS_CLIENT_CONTEXT
{
CLFS_NODE_ID cidNode;
CLFS_CLIENT_ID cidClient;
USHORT fAttributes;
ULONG cbFlushThreshold;
ULONG cShadowSectors;
ULONGLONG cbUndoCommitment;
LARGE_INTEGER llCreateTime;
LARGE_INTEGER llAccessTime;
LARGE_INTEGER llWriteTime;
CLFS_LSN lsnOwnerPage;
CLFS_LSN lsnArchiveTail;
CLFS_LSN lsnBase;
CLFS_LSN lsnLast;
CLFS_LSN lsnRestart;
CLFS_LSN lsnPhysicalBase;
CLFS_LSN lsnUnused1;
CLFS_LSN lsnUnused2;
CLFS_LOG_STATE eState;
union
{
HANDLE hSecurityContext;
ULONGLONG ullAlignment;
};
} CLFS_CLIENT_CONTEXT, *PCLFS_CLIENT_CONTEXT;

In the Base Record, the container context is related to adding a container file for a base log file, which is represented by the CLFS_CONTAINER_CONTEXT structure defined below:

typedef struct _CLFS_CONTAINER_CONTEXT
{
CLFS_NODE_ID cidNode; //8 bytes
ULONGLONG cbContainer; //8 bytes
CLFS_CONTAINER_ID cidContainer; // 4 bytes
CLFS_CONTAINER_ID cidQueue; // 4 bytes
union
{
CClfsContainer* pContainer; //8 bytes
ULONGLONG ullAlignment;
};
CLFS_USN usnCurrent;
CLFS_CONTAINER_STATE eState;
ULONG cbPrevOffset; //4 bytes
ULONG cbNextOffset; //4 bytes
} CLFS_CONTAINER_CONTEXT, *PCLFS_CONTAINER_CONTEXT;

The field pContainer is a kernel pointer to the CClfsContainer object representing the container at runtime, which is located at offset 0x18 in the CLFS_CONTAINER_CONTEXT structure. Figure 5 shows the memory layout of the CLFS_CONTAINER_CONTEXT structure.

Figure 5. The memory layout of the CLFS_CONTAINER_CONTEXT structure

Reproduce BSOD Crash

In order to determine the root cause of CVE-2022-37969, ThreatLabz developed a Proof-of-Concept (PoC) that triggers a “blue screen of death” (BSOD) crash stably. Figure 6 shows detailed crash information after triggering the vulnerability.

Figure 6. CVE-2022-37969 crash information in WinDbg

As shown in Figure 6, the register rdi points to an invalid memory address. The register rdi stores a pointer to the CClfsContainer object. In the CLFS_CONTAINER_CONTEXT structure described before, the field pContainer is a pointer to the CClfsContainer object and located at offset 0x18 in the memory layout. Based on the location of the crash, the field pContainer in the CLFS_CONTAINER_CONTEXT structure has been corrupted. This leads to a BSOD crash when this pointer is dereferenced.

Figure 7 shows a comparison between a properly structured base log file (.BLF) and a specially crafted base log file that is used to trigger the CVE-2022-37969 vulnerability.

Figure 7. A specially crafted Base Log File (BLF) for CVE-2022-37969

After this base log file is created, specific bytes including the field SignatureOffset, client context offset array, cbSymbol, a fake client context, etc must be modified accordingly. As shown in Figure 7, all mutated bytes are located in the Base Log Record (offset: 0x800 ~ 0x81FF in the .blf file). The modifications to the .blf file are listed in Figure 8.

Figure 8. Modifications to the .BLF file to trigger CVE-2022-37969

Proof-of-Concept code to trigger CVE-2022-37969 is shown in Figure 9.

Figure 9. Proof-of-Concept code snippet for CVE-2022-37969

The Proof-of-Concept of CVE-2022-37969 involves the following steps.

Create a base log file MyLog.blf in the folder C:\Users\Public\ via the CreateLogFile API
Create dozens of base log files named MyLog_xxx.blf. It’s crucial to make the offset between the two subsequently created memory regions representing the Base Block constant (where the constant offset equals 0x11000 bytes). The original 0-day sample leveraged an advanced method to produce the offset constant. Part 2 of this blog series will cover this technique. The ThreatLabz PoC uses a constant count to create base log files, and therefore, might not make the offset between the two subsequently created memory regions constant. Hence, the constant value has to be tweaked for this situation.
Modify a couple of bytes at specific offsets in MyLog.blf, recalculate the new CRC32 checksum for the Base Block, then write the new checksum value at offset 0x80C. Then, open the existing base log file MyLog.blf.
Call the CreateLogFile API to create a base log file MyLxg_xxx.blf in the folder C:\Users\Public\.
Call the AddLogContainer API to add a log container for the base log file MyLxg_xxx.blf created in Step 4.
Call GetProcAddress(LoadLibraryA("ntdll.dll"), "NtSetInformationFile") to obtain the function address of the NtSetInformationFile API.
Call the AddLogContainer API to add a log container for the base log file MyLog.blf opened in Step 3.
Call NtSetInformationFile(v55, (PIO_STATUS_BLOCK)v33, v28, 1, (FILE_INFORMATION_CLASS)13), where the last parameter is the type of FileInformationClass. When the value is FileDispositionInformation (13), the function will delete the file when it is closed or will cancel a previously requested deletion.
Call the CloseHandle API to close the handle of the base log file MyLxg_xxx.blf, to trigger this vulnerability.

Root Cause Analysis

Now that Proof-of-Code has been introduced, the root cause can be analyzed. In Figure 9, Step 3 calls the CreateLogFile API whose 5th parameter is 4 (OPEN_ALWAYS), which opens an existing file or creates the file if it does not exist. In this case, the existing base log file MyLog.blf is opened. In Step 4, the code calls the CreateLogFile API to create a new base log file named MyLxg_xxx.blf. In Steps 5 and 7, respectively, the code calls AddLogContainer to add a container to the physical log that is associated with the log handle.

First, let’s take a closer look at how the CLFS driver handles the request of adding a log container when the AddLogContainer() function is called in user space. The following breakpoint can be set to trace the process of handling this request.

bu CLFS!CClfsRequest::AllocContainer

In CLFS.sys, the CClfsRequest class is responsible for handling the requests from user space. The CClfsRequest::AllocContainer function is used to handle the request of adding a container to the physical log. The CClfsRequest::AllocContainer function calls CClfsLogFcbPhysical::AllocContainer whose declaration is shown below:

CClfsLogFcbPhysical::AllocContainer(CClfsLogFcbPhysical *this, _FILE_OBJECT *,_UNICODE_STRING *,unsigned __int64 *)

Next, the breakpoint at CClfsLogFcbPhysical::AllocContainer is set as follows:

bu CLFS!CClfsLogFcbPhysical::AllocContainer

In Step 5, when the code calls the AddLogContainer function, the breakpoint at CClfsLogFcbPhysical::AllocContainer is triggered. When the breakpoint is hit, let’s inspect the this pointer of the CClfsLogFcbPhysical class. The this pointer points to the CClfsLogFcbPhysical object. As shown in Figure 10, the register rcx stores the this pointer of the CClfsLogFcbPhysical class.

Figure 10. Inspection of the this pointer for the CClfsLogFcbPhysical class at CClfsLogFcbPhysical::AllocContainer

The address of vftable in the CClfsLogFcbPhysical class is stored at offset 0x00. At offset this+0x30, a pointer to the log name is stored. At offset this+0x2B0, the this pointer to CClfsBaseFilePersisted class is stored. Once in memory, a CLFS Base Log File is represented by a CClfsBaseFile class, which can be further extended by a CClfsBaseFilePersisted class. In the this pointer of the CClfsBaseFilePersisted class, at offset 0x30 a pointer to a heap buffer is stored whose size is 0x90 bytes. Furthermore, in the heap buffer, a pointer to the Base Block is stored at offset 0x30. Additionally, in the this pointer of CClfsBaseFilePersisted, a pointer to the CClfsContainer object is stored at offset 0x1C0. After a container is added successfully, we can check the CLFS_CONTAINER_CONTEXT structure described in Figure 5 in memory as shown in Figure 11.

Figure 11. The CLFS_CONTAINER_CONTEXT structure after a container is added successfully

At offset 0x1C0 in the CClfsBaseFilePersisted object, a pointer to the CClfsContainer object is stored which comes from the field pContainer in the CLFS_CONTAINER_CONTEXT structure. At this point, a memory write breakpoint at CLFS_CONTAINER_CONTEXT+0x18 can be set to trace when the pointer to the CClfsContainer object in the CLFS_CONTAINER_CONTEXT structure is corrupted. Another memory write breakpoint at offset 0x1C0 in the CClfsBaseFilePersisted object can be set as follows:

1: kd> ba w8 ffffc80ccc86a4f0 //CLFS_CONTAINER_CONTEXT: +0x18 1: kd> ba w8 ffffb7023cf251c0 //CClfsBaseFilePersisted: +0x1C0

In Step 7, when the code calls the AddLogContainer function, the breakpoints at CLFS!CClfsRequest::AllocContainer and CLFS!CClfsLogFcbPhysical::AllocContainer are hit again. At this point, let’s inspect the this pointer (see Figure 12) of the CClfsLogFcbPhysical class.

Figure 12. Inspection of the this pointer for CClfsLogFcbPhysical class

In WinDbg, let’s continue to run the code. The memory write breakpoint “ba w8 ffffc80c`cc86a4f0” will be hit, and the CLFS_CONTAINER_CONTEXT structure in the Base Record produces an out-of-bound write, which leads to a corrupted pointer in the CClfsContainer object shown in Figure 13.

Figure 13. Corrupting the pointer to the CClfsContainer object in CLFS_CONTAINER_CONTEXT structure

Based on the above back stack trace, the memset function was called to trigger the memory write breakpoint in the CClfsBaseFilePersisted::AllocSymbol function. Figure 14 shows the pseudocode of the CClfsBaseFilePersisted::AllocSymbol function.

Figure 14. The pseudocode of the CClfsBaseFilePersisted::AllocSymbol function

This function is described as follows:

The variable v9 is the value of the field cbSymbolZone in the Base Record Header. The field cbSymbolZone was set to be an abnormal value described in Figure 8. This value was modified from 0x000000F8 to 0x0001114B. 
The variable v10 is equal to v9 plus the BaseLogRecord plus 0x1338, and the variable v9 is equal to 0x0001114b, which leads to an invalid offset at v10. 
The function calls memset() which causes an out-of-bound write at an invalid offset v10, which falls into the CLFS_CONTAINER_CONTEXT structure in the Base Record for MyLxg_xxx.blf, leading to a corrupted CClfsContainer pointer at offset 0x18 in the CLFS_CONTAINER_CONTEXT structure.

Figure 15 shows how the out-of-bound write occurs, leading to a corrupted pointer in the CClfsContainer object.

Figure 15. Explanation of the out-of-bound write caused by CVE-2022-37969

So far, we have discussed why an out-of-bound write occurred and how the pointer to CClfsContainer object in the CLFS_CONTAINER_CONEXT structure was corrupted. Next, let’s take a look at when the corrupted CClfsContainer pointer will be dereferenced. When the CloseHandle function is called in user space, CClfsRequest::Close(PIRP Irp) is responsible for handling this request. In the kernel, another memory breakpoint (0x1c0+CClfsBaseFilePersisted) is hit in the ClfsBaseFilePersisted::WriteMetadataBlock function as shown in Figure 16.

Figure 16. Memory breakpoint(0x1c0+CClfsBaseFilePersisted) hit in ClfsBaseFilePersisted::WriteMetadataBlock

The corrupted pointer is copied to the CClfsContainer object in the CLFS_CONTAINER_CONTEXT structure in the Base Record to the offset 0x1c0 in the CClfsBaseFilePersisted object.

Figure 17 shows the pseudocode of the ClfsBaseFilePersisted::WriteMetadataBlock function after the corrupted pointer to CClfsContainer is stored at offset 0x1c0 in the CClfsBaseFilePersisted object. The code zeros out the field of the pointer to the CClfsContainer object in the CLFS_CONTAINER_CONTEXT structure. After decoding the block, the pointer to the CClfsContainer object is restored in the CLFS_CONTAINER_CONTEXT structure from the offset 0x1c0 in the CClfsBaseFilePersisted object.

Figure 17. The pseudocode of the ClfsBaseFilePersisted::WriteMetadataBlock function

Finally, the breakpoint at CLFS!CClfsBaseFilePersisted::RemoveContainer can be set to trace when the corrupted pointer to the CClfsContainer object in the CLFS_CONTAINER_CONTEXT structure in the Base Record is dereferenced.

Figure 18 shows the pseudocode of the CClfsBaseFilePersisted::RemoveContainer function.

Figure 18.The pseudocode of the CClfsBaseFilePersisted::RemoveContainer function

The following steps are executed in the CClfsBaseFilePersisted::RemoveContainer function.

Obtains the container context offset at offset 0x398 in the Base Record.
Calls CClfsBaseFile::GetSymbol to obtain the pointer to the CLFS_CONTAINER_CONTEXT structure and stores it in the 4th parameter.
Assigns the value of the pointer to the CClfsContainter object in the CLFS_CONTAINER_CONTEXT structure obtained in Step 2 to the local variable v13.
Zeros out the pointer to the CClfsContainter object in the CLFS_CONTAINER_CONTEXT structure.
Calls CClfsContainer::Remove and CClfsContainer::Release, in turn, to remove the associated container log file and release the CClfsContainer object.

Dereferencing the corrupted pointer to the CClfsContainter object leads to a memory violation. Figure 19 shows the crash information in WinDbg, consequently producing the BSOD crash.

Figure 19. Dereferencing the corrupted pointer to the CClfsContainter object

Conclusion

In this blog, ThreatLabz presented a detailed root cause analysis for CVE-2022-37969, which is due to improper bounds checking for the field cbSymbolZone in the Base Record Header for the base log file (BLF) in CLFS.sys. When the field cbSymbolZone is set to an invalid offset, an out-of-bound write at the invalid offset can be triggered. Therefore, the pointer to the CClfsContainer object can be corrupted. When dereferenced, the corrupted pointer to the CClfsContainer object causes a memory violation that triggers a BSOD crash. In Part 2, we will present the analysis of the 0-day exploit that leverages this vulnerability for privilege escalation. Stay tuned!

Mitigation

All users of Windows are encouraged to upgrade to the latest version. Zscaler’s Advanced Threat Protection and Advanced Cloud Sandbox can protect customers against the in-the-wild 0-day exploit of CVE-2022-37969.

Win32.GenExploit.LogFile
Win32.Exploit.CVE-2022-37969

Cloud Sandbox Detection

References

https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-37969

https://www.pixiepointsecurity.com/blog/nday-cve-2022-24521.html

Article Link: CVE-2022-37969