Skip to content

Latest commit

 

History

History
182 lines (173 loc) · 10.9 KB

writeup_eng.md

File metadata and controls

182 lines (173 loc) · 10.9 KB

Writeup

About this vulnerability: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-36900.
What is CLFS.sys: 1, 2

Basic analysis of the patch

The function at address 0x1C005107C has been patched:
drawing
This cmp has been added:
drawing
After the cmp instruction, the small block of code is executed and function goes to return:
drawing
The small block of code in disassembler:
drawing
And in decompiler:
drawing
This code is typical for drivers. It is WPP tracing. So it is not interesting to us. But we should pay attention to the return code. It is 0xC0000095 that means STATUS_INTEGER_OVERFLOW. That's a good hint for us.

Pseudocode of the patched function without details:

MACRO_STATUS CClfsBaseFilePersisted::ExtendMetadataBlockDescriptor(
        CClfsBaseFilePersisted *this,
        CLFS_METADATA_BLOCK_TYPE *iFlushBlock,
        unsigned int num_of_extendSectors_div_2)
{
  // size_of_extend_sectors_div2 = num_of_extendSectors_div_2 * 0x200u
  if ( RtlULongMult(num_of_extendSectors_div_2, 0x200u, &size_of_extend_sectors_div2) < 0 )
    return 0xC01A000Di64;
  //size_of_flushBlockSectors_and_extendSecorsDiv2 = size_of_extend_sectors_div2 * this->CClfsBaseFile.m_rgBlocks[*iFlushBlock].cbImage
  sum_ntstatus = RtlULongAdd(
                   size_of_extend_sectors_div2,
                   this->CClfsBaseFile.m_rgBlocks[*iFlushBlock].cbImage,
                   &size_of_flushBlockSectors_and_extendSecorsDiv2);
  if ( sum_ntstatus < 0 )
    return 0xC01A000Di64;

  m_cbRawSectorSize = this->CClfsBaseFile.m_cbRawSectorSize;// always 512 (0x200)

  // Round the number up
  if ( m_cbRawSectorSize )
    rounded_sum = -m_cbRawSectorSize & (m_cbRawSectorSize + size_of_flushBlockSectors_and_extendSecorsDiv2 - 1);
  else
    rounded_sum = 0;

  // Cmp that was added by the patch
  if ( rounded_sum >= size_of_flushBlockSectors_and_extendSecorsDiv2 )
  {/* main body of the funcion */}
  else{
    /* something with WPP */
    return STATUS_INTEGER_OVERFLOW;
  }
...}

The vulnerability is that the variable rounded_sum may be less than size_of_flushBlockSectors_and_extendSecorsDiv2. If size_of_flushBlockSectors_and_extendSecorsDiv2 will be great than 0xffffffff - 0x200 + 1 we'll end up in the 'else' block and leave the function with STATUS_INTEGER_OVERFLOW. But in the unpatched version, we will continue execution and get BSOD.

The variable size_of_flushBlockSectors_and_extendSecorsDiv2 depends on the value of size_of_extend_sectors_div2 and the value of iFlushBlock. Both of them are function parameters. So, we need to manipulate these parameters so that we end up in the 'else' block.

Analysis of the vulnerable function

This function is called only from two other functions:

  • CClfsBaseFilePersisted::ProcessCurrentBlockForExtend
  • CClfsBaseFilePersisted::ExtendMetadataBlock

The arguments submitted to the function are identical in both cases:

status = CClfsBaseFilePersisted::ExtendMetadataBlockDescriptor(
             this,
             control_record->iFlushBlock,
             control_record->cExtendSectors / 2);

That is control_record in this call?

The clfs.sys driver keeps data about it's logs in files with .blf extension. When you call CreateLogFile WinAPI function, clfs.sys creates a new log file. This file contains an area called control record. The control record contains information about the layout of the blf file.

drawing

Here we see the fields that are passed as arguments to the vulnerable function. How this fields are used in the function? The field cExtendSectors is divided by two when submitted to the function and multiplied by 0x200 in the function itself:

RtlULongMult(num_of_extendSectors_div_2, 0x200u, &size_of_extend_sectors_div2)

The field iFlushBlock is used as an index in rgBlocks array that reside in control record too, but in the code below, it is already loaded into the CClfsBaseFile structure, that is an in-memory representation of the blf file.

RtlULongAdd(size_of_extend_sectors_div2,
            this->CClfsBaseFile.m_rgBlocks[*iFlushBlock].cbImage,
            &size_of_flushBlockSectors_and_extendSecorsDiv2);

A blf file consists of six blocks, each with its own purpose. The rgBlocks array stores the location and size of the blocks. The cbImage field sets the size of the block in the file.

drawing

So, in the code above we multiply size of the block that has been determine by iFlushBlock field and value that we get from operations with cExtendSectors.
Then, the obtained value is rounded up to the disk sector boundary (512 bytes):

  m_cbRawSectorSize = this->CClfsBaseFile.m_cbRawSectorSize;// always 512 (0x200)

  // Round the number up
  if ( m_cbRawSectorSize )
    rounded_sum = -m_cbRawSectorSize & (m_cbRawSectorSize + size_of_flushBlockSectors_and_extendSecorsDiv2 - 1);
  else
    rounded_sum = 0;

And if we run unpatched version of clfs.sys, the rounded up value will be passed to ExAllocatePoolWithTag function, what will allocate kernel memory. Then from block with index iFlushBlock into the new memory will be copied rgBlock[iFlushBlock].cbImage number of bytes.

allocated_mem = ExAllocatePoolWithTag(PagedPoolCacheAligned, rounded_up_size, 'sflC');
memmove(allocated_mem, pbImage, cbImage);

But if value of size_of_flushBlockSectors_and_extendSecorsDiv2 is 0xfffffe01, it will be rounded up to 0. And 0 will be passed to ExAllocatePoolWithTag. Then ExAllocatePoolWithTag will allocate number of bytes less than rgBlock[iFlushBlock].cbImage, because the minimum value of rgBlock[iFlushBlock].cbImage is 0x200. So, chunks that lie after the allocated chunk will be corrupted. But that's not all. Then the following code will be called:

difference = rounded_up_size - cbImage;
memset(allocated_mem + rgBlocks[iFlushBlock].cbImage, 0, difference);

It means that 0 - 0x200 = 0xfffffe00 additional kernel memory will be corrupted.

Road to the vulnerable function

All paths to the vulnerable function starts from CClfsRequest::Create function.
drawing

This function is called when we use CreateLogFile WinAPI. When we open a log first time after it's creation, we can reach the vulnerable function via this path:

CClfsRequest::Create->
CClfsLogFcbPhysical::Initialize ->
CClfsBaseFilePersisted::OpenImage ->
CClfsBaseFilePersisted::ExtendMetadataBlock ->
CClfsBaseFilePersisted::ExtendMetadataBlockDescriptor

The function CClfsBaseFilePersisted::OpenImage has the following constraints:

if ( control_record->eExtendState == ClfsExtendStateNone ) // ClfsExtendStateNone = 0
          goto END;
iExtendBlock = control_record->iExtendBlock;
if ( iExtendBlock )
{
  m_cBlocks = this->CClfsBaseFile.m_cBlocks;// = 6

  if ( iExtendBlock < m_cBlocks && ((iExtendBlock - 2) & 0xFFFD) == 0 )
  {
    iFlushBlock = control_record->iFlushBlock;
    if ( iFlushBlock )
    {
      if ( iFlushBlock < m_cBlocks
        && iFlushBlock < 6u
        && iFlushBlock >= iExtendBlock
        && control_record->cExtendStartSectors <= CClfsBaseFile::GetSize(this) / 0x200 )
      {
        cExtendSectors_count = control_record->cExtendSectors / 2;
        if ( control_record->cNewBlockSectors <= cExtendSectors_count
                                               + (this->CClfsBaseFile.m_rgBlocks[control_record->iExtendBlock].cbImage / 0x200) )
        {
          status = CClfsBaseFilePersisted::ExtendMetadataBlock(
                     this,
                     control_record->iExtendBlock,
                     cExtendSectors_count);
          status_1 = status;
          goto END;
        }
      }
    }
  }
}

So, to reach the vulnerable function we need to change in the blf file the following values:

eExtendState = 2
iExtendBlock = 4
iFlushBlock = 5
cNewBlockSectors = 2

To get BSOD we will set cExtendSectors to 0xfffffc and rgBlock[5].cbImage to 0x201.

An another constraint in this call:

CClfsBaseFilePersisted::ExtendMetadataBlock ->
CClfsBaseFile::AcquireMetadataBlock ->
CClfsBaseFilePersisted::ReadMetadataBlock

It prevents us to set rgBlock[5].cbImage to 0x201 because it compares blocks. The blf file contain three types of blocks, and one shadow block for each type:

  • Control Metadata Block
  • Shadow Control Metadata Block
  • General Metadata Block
  • Shadow General Metadata Block
  • Scratch Metadata Block
  • Shadow Scratch Metadata Block

A shadow block is a backup copy of the original block. When clfs.sys sees that a block and its shadow copy are different, it selects the older block. So, the block rgBlock[5] is shadow copy of the rgBlock[4]. If we want to set rgBlock[5].cbImage to 0x201, we need to make this block older. To do this, we will change the first byte after its CLFS_LOG_BLOCK_HEADER (each block starts with this structure) to a value, that is greater than the first byte after CLFS_LOG_BLOCK_HEADER of the block rgBlock[4]. And we need to copy CLFS_LOG_BLOCK_HEADER of rgBlock[4] to rgBlock[5].

After all changes we also need to recalculate the checksum of each block. It can be found in the CLFS_LOG_BLOCK_HEADER of each block.
And with new crafted blf file we can get BSOD.