CVE-2022-24521 — CLFS CLIENT_CONTEXT/CONTAINER_CONTEXT Overlap → Arbitrary Decrement

Last updated: 2026-04-10
Component: clfs.sys
Bug Class: OOB write (structure overlap via cbSymbolZone) → controlled vtable → arbitrary decrement
Patch: April 2022 Patch Tuesday
Exploited ITW: Yes — April 2022 (Kaspersky + Microsoft attribution)
Exploit Author: Same threat actor as CVE-2022-37969, CVE-2023-23376, CVE-2023-28252
Related: Clfs, Cve 2022 37969, Cve 2023 28252, Primitives
Tags: clfs, oob-write, structure-overlap, arbitrary-decrement, previousmode, kernel-mode, lpe


Summary

CVE-2022-24521 is the first CLFS zero-day captured in the wild by Kaspersky GReAT in the sustained 2022–2023 CLFS ransomware campaign. Root cause: cbSymbolZone (the “next free slot” pointer in the General metadata block) is set to an offset that overlaps an existing CLFS_CLIENT_CONTEXT structure. When the exploit calls AddLogContainer(), a new CLFS_CONTAINER_CONTEXT symbol is written at cbSymbolZone, partially overwriting CLFS_CLIENT_CONTEXT fields. When FlushMetadata restores cached client values, the CClfsContainer kernel pointer in CLFS_CONTAINER_CONTEXT is overwritten with attacker-controlled data.

This is the foundational vulnerability in a 5-CVE series by the same exploit author.


Root Cause

Setup: Manipulating cbSymbolZone

The General metadata block header (CLFS_BASE_RECORD_HEADER) contains cbSymbolZone — the offset of the next free byte for new symbol allocations. Normally this points past all existing symbols.

The exploit patches cbSymbolZone (at file offset 0x1B98) to point into the middle of an existing CLFS_CLIENT_CONTEXT structure.

BLF patch sequence:

Offset 0x1B98   cbSymbolZone → set to overlap target CLFS_CLIENT_CONTEXT
Additional:     "fake" CLFS_CONTAINER_CONTEXT structure built at the overlap position

Key requirement for the overlap to work:

  • CLFS_CLIENT_CONTEXT.llCreateTime must be set to 0x40000000
    • This value is read back as the CClfsContainer* pointer when the structure overlap occurs
    • 0x40000000 is within a user-mode-accessible mapped region (the heap spray target)

Structure Overlap Mechanics

  1. Open the crafted BLF → CLFS caches CLFS_CLIENT_CONTEXT fields in memory (including llCreateTime = 0x40000000)
  2. Call AddLogContainer()CClfsBaseFilePersisted::AddSymbol → AllocSymbol writes a new CLFS_CONTAINER_CONTEXT starting at cbSymbolZone (which is inside the existing CLFS_CLIENT_CONTEXT)
  3. Now CLFS_CLIENT_CONTEXT and CLFS_CONTAINER_CONTEXT overlap in the block image
  4. The pContainer field of CLFS_CONTAINER_CONTEXT occupies the same bytes as llCreateTime of CLFS_CLIENT_CONTEXTpContainer = 0x40000000
  5. CClfsLogFcbPhysical::FlushMetadata restores cached llCreateTime value into the block → overwrites pContainer back to 0x40000000 (attacker-controlled)
  6. When the container is closed/cleaned up → CClfsContainer* is dereferenced → vtable dispatch to 0x40000000

Exploit Primitive: Arbitrary Decrement via ObDereferenceObject

The controlled vtable at 0x40000000 is crafted to call ObDereferenceObject with an attacker-controlled argument:

// Heap spray at 0x40000000 — fake CClfsContainer object:
vtable_ptr = &fake_vtable;    // fake_vtable+N → ObDereferenceObject gadget

// When vtable dispatch fires:
ObDereferenceObject(target_address);

ObDereferenceObject decrements the reference count field of any kernel object. Called on an attacker-specified address X:

  • Decrements *X by 1 (at the reference count offset +0x18 in the OBJECT_HEADER)
  • If X = KTHREAD.PreviousMode_address - 0x18, then KTHREAD.PreviousMode is decremented

PreviousMode Decrement → Arbitrary Kernel R/W

KTHREAD.PreviousMode at offset 0x232 (_KTHREAD.PreviousMode — validated via NtQuerySystemInformation(SystemExtendedHandleInformation, 0x40) to get the KTHREAD address):

  1. Locate current KTHREAD address via NtQuerySystemInformation(0x40)SYSTEM_HANDLE_INFORMATION_EX → find current thread handle → Object field = ETHREAD*
  2. Calculate KTHREAD.PreviousMode - 0x18 as the argument to ObDereferenceObject
  3. ObDereferenceObject decrements 1 → 0 at PreviousModeKernelMode
  4. Now NtReadVirtualMemory / NtWriteVirtualMemory accept kernel addresses directly
  5. Walk EPROCESS.ActiveProcessLinks to find SYSTEM process (PID 4), copy Token to current process

Key Data Structures (Exploit Offsets)

CLFS_CLIENT_CONTEXT key fields (at overlap target):
+0x00  cidNode.cType     = 0x07 (CLFS_NODE_TYPE_CLIENT_CONTEXT)
+0x04  cidNode.cbNode    = size
+0x08  cidClient         USHORT
+0x0A  fAttributes       USHORT   ← overlap with CONTAINER_CONTEXT header
...
+0x10  llCreateTime      ULONGLONG = 0x40000000  ← becomes pContainer in overlap

CLFS_CONTAINER_CONTEXT fields when overlapped at cbSymbolZone:
+0x00  cidNode.cType     = 0x09 (CLFS_NODE_TYPE_CONTAINER_CONTEXT)
+0x04  cidNode.cbNode
+0x08  cbContainer
+0x10  cidContainer/cidQueue
+0x18  pContainer        → inherits llCreateTime value (0x40000000)

Exploit Flow

1. CreateLogFile("MyLog.blf") with patched cbSymbolZone → opens crafted BLF
   → CLFS caches CLIENT_CONTEXT fields (llCreateTime = 0x40000000)

2. Heap spray at 0x40000000:
   → fake CClfsContainer vtable
   → vtable[N] = gadget → ObDereferenceObject(KTHREAD.PreviousMode - 0x18)

3. AddLogContainer() → AllocSymbol writes CONTAINER_CONTEXT at cbSymbolZone
   → pContainer field overlaps with llCreateTime → pContainer = 0x40000000

4. FlushMetadata restores cached CLIENT_CONTEXT → llCreateTime copied back
   → CONTAINER_CONTEXT.pContainer confirmed = 0x40000000

5. Close container / trigger cleanup:
   → CClfsBaseFilePersisted::RemoveContainer
   → calls virtual function on fake CClfsContainer(0x40000000)
   → ObDereferenceObject(KTHREAD.PreviousMode - 0x18)
   → KTHREAD.PreviousMode: 1 → 0

6. Token steal via NtReadVirtualMemory / NtWriteVirtualMemory (PreviousMode=0)
   → SYSTEM privilege

Affected Versions

  • Windows 10 (all builds before April 2022 patch)
  • Windows 11 (builds before April 2022 patch)

Patch Analysis

The patch added validation in CClfsBaseFilePersisted::LoadContainerQ checking that cbSymbolZone + end_of_CLFS_BASE_RECORD_HEADER does not overlap with existing symbols or exceed valid bounds.

However, the same vulnerability pattern existed in adjacent code (CONTROL block instead of GENERAL block) → CVE-2023-23376.


CVE Series Context

This is Exploit #1 in the Kaspersky 5-CVE series (Boris Larin). The exploit author reused nearly identical techniques across all 5 CVEs:

  • Exploit #1 (CVE-2022-24521): GENERAL block, llCreateTime=0x40000000, ObDereferenceObject decrement
  • Exploit #2 (CVE-2022-37969): Different trigger for same pContainer corruption
  • Exploit #3 (Oct 2022): cbSymbolZone manipulation (variant of Exploit #1)
  • Exploit #4 (CVE-2023-23376): CONTROL block targeting
  • Exploit #5 (CVE-2023-28252): ValidSectorCount=1 → shadow fallback

References

  • Kaspersky GReAT / Boris Larin — “Windows CLFS and five exploits (Exploit #1 — CVE-2022-24521)” — securelist.com, 2023-12-21
  • Microsoft MSRC — CVE-2022-24521 advisory — April 2022