🤬
  • ■ ■ ■ ■ ■ ■
    0day-RCAs/2022/CVE-2022-24521.md
     1 +# 0-day Root Cause Analysis Template
     2 +
     3 +
     4 +# CVE-2022-24521: Windows Common Log File System (CLFS) Logical-Error Vulnerability
     5 +Sergey Kornienko (@b1thvn_) of PixiePoint Security
     6 +
     7 +## The Basics
     8 +
     9 +**Disclosure or Patch Date:** April 12, 2022
     10 +
     11 +**Product:** Microsoft Windows
     12 +
     13 +**Advisory:** https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-24521
     14 +
     15 +**Affected Versions:** Before security updates of April 12, 2022, for Windows 7, 8.1, 10, 11 and Windows Server 2008, 2012, 2016, 2019, 2022
     16 +
     17 +**First Patched Version:** Security updates of April 12, 2022, for CVE-2022-24521
     18 +
     19 +**Issue/Bug Report:** N/A
     20 +
     21 +**Patch CL:** N/A
     22 +
     23 +**Bug-Introducing CL:** N/A
     24 +
     25 +**Reporter(s):** Sergey Kornienko (@b1thvn_) of PixiePoint Security
     26 +
     27 +## The Code
     28 +
     29 +**Proof-of-concept:** N/A
     30 +
     31 +**Exploit sample:** N/A
     32 +
     33 +**Did you have access to the exploit sample when doing the analysis?** No
     34 +
     35 +## The Vulnerability
     36 +
     37 +**Bug class:** Logical error (lack of indirect-call validation)
     38 +
     39 +**Vulnerability details:**
     40 +
     41 +As per the CLFS format, the array of signatures intersects with the container or client context.
     42 +
     43 +When the log block is encoded, sector's bytes from `SIG_*` are transferred to an array, pointed by `SignaturesOffset`. While decoding, these bytes are written back to their initial location. If we'll construct the base log record in a way that the container context and the signature array will be close to each other and then copy context's bytes to `SIG_0` ... `SIG_X`, encode and decode operation will not corrupt the container context. Moreover, all the data modified between encoding and decoding will be restored.
     44 +
     45 +Now let's assume that container context is modified in memory (`PCLFS_CONTAINER_CONTEXT->pContainer` is zeroed). We searched for a while where it is actually used and this led us to `CClfsBaseFilePersisted::RemoveContainer` which can be called directly from `LoadContainerQ`:
     46 +
     47 +```c
     48 +__int64 __fastcall CClfsBaseFilePersisted::RemoveContainer(CClfsBaseFilePersisted *this, unsigned int a2)
     49 +{
     50 +...
     51 + v11 = CClfsBaseFilePersisted::FlushImage((PERESOURCE *)this);
     52 + v9 = v11;
     53 + v16 = v11;
     54 + if ( v11 >= 0 )
     55 + {
     56 + pContainer = *((_QWORD *)containerContext + 3);
     57 + if ( pContainer )
     58 + {
     59 + *((_QWORD *)containerContext + 3) = 0i64;
     60 + ExReleaseResourceForThreadLite(*((PERESOURCE *)this + 4), (ERESOURCE_THREAD)KeGetCurrentThread());
     61 + v4 = 0;
     62 + (*(void (__fastcall **)(__int64))(*(_QWORD *)pContainer + 0x18i64))(pContainer); // remove method
     63 + (*(void (__fastcall **)(__int64))(*(_QWORD *)pContainer + 8i64))(pContainer); // release method
     64 + v9 = v16;
     65 + goto LABEL_20;
     66 + }
     67 + goto LABEL_19;
     68 + }
     69 +...
     70 +}
     71 +```
     72 +
     73 +To ensure that the user cannot pass any `FAKE_pContainer` pointer to the kernel, before any indirect call this field is set to zero:
     74 +
     75 +```c
     76 +v44 = *((_DWORD *)containerContext + 5); // to trigger RemoveContainer one should set this field to -1
     77 +if ( v44 == -1 )
     78 +{
     79 + *((_QWORD *)containerContext + 3) = 0i64; // pContainer is set to NULL
     80 + v20 = CClfsBaseFilePersisted::RemoveContainer(this, v34);
     81 + v72 = v20;
     82 + if ( v20 < 0 )
     83 + goto LABEL_134;
     84 + v23 = v78;
     85 + v34 = (unsigned int)(v34 + 1);
     86 + v79 = v34;
     87 +}
     88 +```
     89 +
     90 +Everything goes as planned until there is no logical issue described above. To understand it better lets look inside the call chain `CClfsBaseFilePersisted::FlushImage -> CClfsBaseFilePersisted::WriteMetadataBlock` which is in `RemoveContainer`. The information associated with the deleted container should be also removed from the linked structures and this is done with the following code:
     91 +
     92 +```c
     93 +...
     94 +// Obtain all container contexts represented in blf
     95 +// save pContainer class pointer for each valid container context
     96 +for ( i = 0; i < 0x400; ++i )
     97 +{
     98 +v20 = CClfsBaseFile::AcquireContainerContext(this, i, &v22);
     99 +v15 = (char *)this + 8 * i;
     100 +if ( v20 >= 0 )
     101 +{
     102 + v16 = v22;
     103 + *((_QWORD *)v15 + 56) = *((_QWORD *)v22 + 3); // for each valid container save pContainer
     104 + *((_QWORD *)v16 + 3) = 0i64; // and set the initial pContainer to zero
     105 + CClfsBaseFile::ReleaseContainerContext(this, &v22);
     106 +}
     107 +else
     108 +{
     109 + *((_QWORD *)v15 + 56) = 0i64;
     110 +}
     111 +}
     112 +// Stage [1] enode block, prepare it for writing
     113 +ClfsEncodeBlock(
     114 +(struct _CLFS_LOG_BLOCK_HEADER *)v9,
     115 +*(unsigned __int16 *)(v9 + 4) << 9,
     116 +*(_BYTE *)(v9 + 2),
     117 +0x10u,
     118 +1u);
     119 +// write modified data
     120 +v10 = CClfsContainer::WriteSector(
     121 + *((CClfsContainer **)this + 19),
     122 + *((struct _KEVENT **)this + 20),
     123 + 0i64,
     124 + *(void **)(*((_QWORD *)this + 6) + 24 * v8),
     125 + *(unsigned __int16 *)(v9 + 4),
     126 + &v23);
     127 +...
     128 +if ( v7 )
     129 +{
     130 +// Stage [2] Decode file again for futher processing in clfs.sys
     131 +ClfsDecodeBlock((struct _CLFS_LOG_BLOCK_HEADER *)v9, *(unsigned __int16 *)(v9 + 4), *(_BYTE *)(v9 + 2), 0x10u, &v21);
     132 +// optain new pContainer class pointer
     133 +v17 = (_QWORD *)((char *)this + 448);
     134 +do
     135 +{
     136 + // Stage [3] for each valid container
     137 + // update pContainer field
     138 + if ( *v17 && (int)CClfsBaseFile::AcquireContainerContext(this, v6, &v22) >= 0 )
     139 + {
     140 + *((_QWORD *)v22 + 3) = *v17;
     141 + CClfsBaseFile::ReleaseContainerContext(this, &v22);
     142 + }
     143 + ++v6;
     144 + ++v17;
     145 +}
     146 +while ( v6 < 0x400 );
     147 +}
     148 +...
     149 +```
     150 +
     151 +When the operation begins, `pContainer` is set to zero. During *Stage [1]* the information is encoded -> bytes from each sector are written to their location -> we restore the zeroed field with the information we provide from the user mode. The only issue is to make `CClfsBaseFile::AcquireContainerContext` fail at *Stage [3]* (rather easy to do). If everything is done, we'll be able to pass any address to an indirect call chain inside `CClfsBaseFilePersisted::RemoveContainer` which leads to the direct RIP control.
     152 +
     153 +**Patch analysis:**
     154 +
     155 +The patch diffing of CLFS.sys reveals eight changed and two new functions. Of these, new logical block has been added to the `LoadContainerQ` function:
     156 +
     157 +```c
     158 +...
     159 +containerArray = (_DWORD *)((char *)BaseLogRecord + 0x328); // *CLFS_CONTAINER_CONTEXT->rgContainers
     160 +...
     161 +v22 = CClfsBaseFile::ContainerCount(this);
     162 +...
     163 +while ( containerIndex < 0x400 )
     164 +{
     165 + v17 = (CClfsContainer *)containerIndex;
     166 + if ( containerArray[containerIndex] )
     167 + ++v24;
     168 + v89 = ++containerIndex;
     169 +}
     170 +...
     171 +if ( v24 == v22 )
     172 +{
     173 + if ( (unsigned int)Feature_Servicing_38197806__private_IsEnabled() )
     174 + {
     175 + v25 = (_OWORD *)((char *)v19 + 0x138);
     176 + v26 = (unsigned int *)operator new(0x11F0ui64, PagedPool);
     177 + rgObject = v26;
     178 + if ( !v26 )
     179 + {
     180 + goto LABEL_135;
     181 + }
     182 + memmove(v26, containerArray, 0x1000ui64);
     183 + v28 = rgObject + 0x400;
     184 + v29 = 3i64;
     185 + ...
     186 + v20 = CClfsBaseFile::ValidateRgOffsets(this, rgObject);
     187 + v72 = v20;
     188 + operator delete(rgObject);
     189 +}
     190 +```
     191 +
     192 +In fact, this block is a wrapper for `CClfsBaseFile::ValidateRgOffsets`:
     193 +
     194 +```c
     195 +__int64 __fastcall CClfsBaseFile::ValidateRgOffsets(CClfsBaseFile *this, unsigned int *rgObject)
     196 +{
     197 +...
     198 +LogBlockPtr = *(_QWORD *)(*((_QWORD *)this + 6) + 48i64); // * _CLFS_LOG_BLOCK_HEADER
     199 +...
     200 +signatureOffset = LogBlockPtr + *(unsigned int *)(LogBlockPtr + 0x68); // PCLFS_LOG_BLOCK_HEADER->SignaturesOffset
     201 +...
     202 +qsort(rgObject, 0x47Cui64, 4ui64, CompareOffsets); // sort rgObject array
     203 +while ( 1 )
     204 +{
     205 + currObjOffset = *rgObject2; // obtain offset from rgObject
     206 + if ( *rgObject2 - 1 <= 0xFFFFFFFD )
     207 + {
     208 + pObjContext = CClfsBaseFile::OffsetToAddr(this, currObjOffset); // Obtain in-memory representation
     209 + // of the object's context structure
     210 +...
     211 + unkn = currObjOffset - 0x30;
     212 + v13 = rgIndex * 4 + v5 + 0x30;
     213 + if ( v13 < v5 || v5 && v13 > unkn )
     214 + break;
     215 + v5 = unkn;
     216 + if ( *pObjContext == 0xC1FDF008 ) // CLFS_NODE_TYPE_CLIENT_CONTEXT
     217 + {
     218 + rgIndex = 0xC;
     219 + }
     220 + else
     221 + {
     222 + if ( *pObjContext != 0xC1FDF007 ) // CLFS_NODE_TYPE_CONTAINER_CONTEXT
     223 + return 0xC01A000D;
     224 + rgIndex = 0x22;
     225 + }
     226 + criticalRange = &pObjContext[rgIndex]; // get the address of context + 0x30
     227 + if ( criticalRange < pObjContext || (unsigned __int64)criticalRange > signatureOffset ) // comapre with sig offset
     228 + break;
     229 + }
     230 + ++i;
     231 + ++rgObject2;
     232 + if ( i >= 0x47C )
     233 + return ret;
     234 +}
     235 +return 0xC01A000D;
     236 +}
     237 +```
     238 +
     239 +As we can see, this function simply checks that the signature offset does not intersect with any of the context objects. In addition, it also validates several context fields like `CLFS_NODE_ID`.
     240 +
     241 +**Thoughts on how this vuln might have been found _(fuzzing, code auditing, variant analysis, etc.)_:**
     242 +
     243 +Code auditing
     244 +
     245 +**(Historical/present/future) context of bug:**
     246 +
     247 +https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-24521
     248 +
     249 +## The Exploit
     250 +
     251 +(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).)
     252 +
     253 +**Exploit strategy (or strategies):**
     254 +
     255 +Similar procedure to overwrite process token with pipe objects as outlined in the
     256 +[SSTIC2020: Scoop the Windows 10 pool!](https://www.sstic.org/media/SSTIC2020/SSTIC-actes/pool_overflow_exploitation_since_windows_10_19h1/SSTIC2020-Article-pool_overflow_exploitation_since_windows_10_19h1-bayet_fariello.pdf) paper.
     257 +
     258 +**Exploit flow:**
     259 +
     260 +1. Create pipe objects and add pipe attributes. The attributes are a key-value pair and stored in a linked list, and the `PipeAttribute` object is allocated in the Paged Pool.
     261 +2. Use `NtQuerySystemInformation` to leak kernel virtual address of pipe objects in big pool.
     262 +3. Allocate `fake_pipe_attribute` object. It will be used later to inject its address to an original doubly linked list.
     263 +4. Obtain selected gadget-module base address using `NtQuerySystemInformation`.
     264 +5. Trigger CLFS bug which allows us to call a module-gadget performing arbitrary data modification to achieve an arbitrary read primitive which can be used to obtain `EPROCESS` address.
     265 +6. Trigger CLFS bug to overwrite usermode process token to elevate to system privileges.
     266 +
     267 +**Known cases of the same exploit flow:**
     268 +
     269 +N/A
     270 +
     271 +**Part of an exploit chain?**
     272 +
     273 +N/A
     274 +
     275 +## The Next Steps
     276 +
     277 +### Variant analysis
     278 +
     279 +**Areas/approach for variant analysis (and why):** N/A
     280 +
     281 +**Found variants:** N/A
     282 +
     283 +### Structural improvements
     284 +
     285 +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.?
     286 +
     287 +**Ideas to kill the bug class:** N/A
     288 +
     289 +**Ideas to mitigate the exploit flow:** N/A
     290 +
     291 +**Other potential improvements:** N/A
     292 +
     293 +### 0-day detection methods
     294 +
     295 +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**?
     296 +
     297 +## Other References
     298 +
     299 +- More information about the affected versions can be found on the [Microsoft Advisory](https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-24521) web site.
     300 +- More details about the exploitation can be found on the [CVE-2022-24521: Analysing and Exploiting the Windows Common Log File System (CLFS) Logical-Error Vulnerability](https://www.pixiepointsecurity.com/blog/nday-cve-2022-24521.html) blog post.
Please wait...
Page is in error, reload to recover