Share
## https://sploitus.com/exploit?id=D598E7AD-BBBF-5B09-9F22-E6375FA7DE94
# CVE-2022-37969 Windows Local Privilege Escalation PoC

authors: [Ricardo Narvaja](https://twitter.com/ricnar456) & [Daniel Kazimirow (Solid)](https://twitter.com/solidclt)

For demonstration purposes only. Complete exploit works on vulnerable Windows 11 21H2 systems.

Functional PoC based on previously [published information by Zscaler](https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part)

Checkout the writeup [Understanding the CVE-2022-37969 Windows Common Log File System Driver Local Privilege Escalation](https://www.coresecurity.com/core-labs/articles/understanding-cve-2022-37969-windows-clfs-lpe).

# Usage

## Understanding the CVE-2022-37969 Windows Common Log File System Driver Local Privilege Escalation.

Exploitation walkthrough:

-   Creating the initial BLF log file
    -   Creating multiple random BLF log files
    -   Crafting the initial log file
    -   Performing a controlled Heap Spray
    -   Preparing the methods CreatePipe() / NtFsControlFile()
    -   Once memory was prepared will trigger the vulnerability
    -   Reading the System Token
    -   Validating the token
    -   Overwrite our process’s token with the system one
    -   Executing process as system
    -   Reversing the Patch: Analyzing the structures
    -   Corrupting the “pContainer” pointer
    -   Revisiting the Patch
    -   Corrupting the SignatureOffset
    -   Corrupting more values
    -   Controlling the functions that allows to read the SYSTEM token
    -   Write our own process to achieve the local privilege escalation
    -   PoC source code

The scenario used here was Windows 11 21H2 (OS Build 22000.918) clfs.sys v10.0.22000.918


## Creating the initial BLF log File

The first step is to create a file named *MyLog.blf* in the public folder (%public%), by using the **CreateLogFile()** function:

![](media/23bfbe262940032fba771e9010eeb594.png)

![](media/3f6652da3921f2d0f77868bef8f65a0e.png)

![](media/d58d89618903d37fd132c1dd0d38374f.png)


## Creating multiple random BLF log files

Then it creates several log files with random names using a Loop.

And within the loop, it calls to our *getBigPoolInfo()* function:

![](media/37ce66c7038977148fe9e7e200442c33.png)

It calls the **NtQuerySystemInformation()**, with 0x42 (66 decimal) as the first argument, it will return in **v5** the information about the raids made in the bigpool, whose structure is of type **SYSTEM_BIGPOOL_INFORMATION**.

![](media/20e5e4fd9cfbd8ca76e85c7d4037a4f1.png)

We have to call this function twice. The first one will return an error, but it will give us the correct size of the buffer to call the second time to obtain the desired information.

![Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente](media/6f51a4a8e84c007ec9d679a8b5a6e4a3.png)

**v5** will receive the information of the **SYSTEM_BIG_POOL_INFORMATION** structure.

![](media/7c35a5b4f2bfd8945939cb2cccd35c01.png)

The number of allocations in the *bigpool*, is stored in the first field called **Count,** in the second field there is an array of structures **SYSTEM_BIGPOOL_ENTRY**.

![](media/7343ab15d357c6831c49430569f82228.png)

Then we’ll search through all the structures for the "**Clfs**" tag and the size **0x7a00**.

![](media/f571308a45070f289b9c3d6adb9c1150.png)

It stores in an array called *kernelAddrArray* the VirtualAddress which is the first field of each structure that has **CLFS** tag and size **0x7a00**. From now on, the pools that meet both conditions will be called: *“right pools”.*

![](media/b975353d4f8fdb5b16e19ae8f62a8134.png)

In addition to store each *right pool* in the array, it stores the last *right pool* found in the content of **a2** variable, which is used as argument of the function.

![Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente](media/f7c2e5292ab39c143d8ecb983be92100.png)

In this way **a2** always points to the last *right pool* with **CLFS** tag and size 0x7a00 created.

The variable **v26** always stores the previous *right pool* found since it is equal to **v24** (v26=v24), before calling **getBigPoolinfo()**, but **v24** is updated when leaving this call with the last *right pool* found, and **v26** stays with de previous *right pool* found.

![Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente](media/a2444a618e58697523dc2dbbe0fb7a98.png)

Then It subtracts both directions, and in case the result is negative, inverts the operands so that it is always positive.

![Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente](media/c4ce238f9bb9e312fb33f6a18be46810.png)

In this way in **v32** will stores the difference between the VirtualAddress of the last two *right pool* found.

Then it does something similar, in this case **v23** is initially zero so it does **v23**=**v32** the first time.

![](media/2accc7e9451b14f2a2b8b3cdf03f38fd.png)

The next time in loop **v23** it still has the same value and is not zero, so it breaks and goes here.

![](media/73f0f9ad4a034101921d0ba93c1b0965.png)

**V32** has the last difference and **v23** the previous one, if they are equal, it comes out and increments one, but resets the counter to zero.

The idea is to find 6 consecutive comparisons of **CLFS** tags and size 0x7a00 whose differences are equal, and that difference will be **0x11000**. We will see when executing that when it finds 6 (since it starts from scratch) consecutive with equal distances it will give that value of difference between them.

![](media/b3d00de94b1ace75572b53c6bc6deec5.png)

![Texto Descripción generada automáticamente](media/2a1cf713977a4ec5c2f77439a420f5d3.png)

There we see that he found 6 consecutive and left the loop of creating log files.

In the "*public*" folder we can see the files created

![](media/75986e4d4775b8b65a227e8e86c20164.png)

## Crafting the initial log file:

Our **craftFile()** function opens the original file (*MyLog.blf*) and modifies it to trigger the bug.

![](media/d65eb863fe21ffdc42bbce972bba9e23.png)

After modifying the file, it's necessary to change the *CRC32*, otherwise we'll get a corrupt file error

This value is located at offset *0x80C* of the file.

![](media/c0d9f742a42b19f21bc24529ed649844.png)

## Performing a controlled Heap Spray

Next, it performs a HeapSpray, using the **VirtualAlloc()** function to allocate memory, at arbitrary addresses *0x10000* and *0x5000000* respectively, and saving in the second allocation (*0x10000*), the value *0x5000000*, every 0x10 bytes.

![](media/e05be4c39ffa2fecc47531d82dc15c4c.png)

## Preparing the methods CreatePipe() / NtFsControlFile()

It uses **CreatePipe()** to create an anonymous pipe and call **NtFsControlFile()** using 0x11003c as an argument to add an attribute, later you can call this same function with the 0x110038 argument to read it.

More details of this method can be found [HERE](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)

![](media/fe55b280956b8886cc94873606e1e758.png)

There we see the input buffer that is the attribute we are adding, if we call **NtFsControlFile()** again with the argument 0x11038 in the output it should return this same attribute.

![](media/19288d13634e57244347a137adf3ba3a.png)

Search the pool for the tag of the created attribute (**NpAt**)

![](media/3eac0d05384e73d231e954115c7e5f7b.png)

![](media/a9b434c6072dc77b972aa8945960681f.png)

And when it finds it, it saves it in **v30**.**Pointer** the VirtualAddress of this pool.

**V30.pointer+24** points to the *AttributeValueSize* in the kernel pool and saves it in one of the HeapSprays we’ve made before.

![](media/bf90e9e764456b6b8f9855c245868cb3.png)

The idea is to write to that kernel address+8, to overwrite the **AttributeValue**.

![Texto Descripción generada automáticamente](media/53678b903e5fff6dbf4465ffd7e5ce5f.png)

![](media/5e40cc82a8913ec3faffb12f6a3a24b8.png)

The PipeAttribute structure has as its first field a *LIST_ENTRY* that has a size of 16 bytes, then a pointer to the name of the attribute that has a size of 8 bytes and then comes in 0x18 (24 decimal) the *AttributeValueSize* field that is the one we are storing in the HeapSpray.

After that, we load in **CLFS.sys** and **ntoskrnl** in usermode, and by using GetProcAddress() we find the addresses of the **ClfsEarlierLsn()** and **SeSetAccessStateGenericMapping()** functions.

![](media/472e19bf1963ef45c983a61ab5723d2e.png)

Then we call the **FindKernelModulesBase()** function that will find the kernel base of both same modules using **NtquerySystemInformation()** this time with the *SystemModuleInformation* argument to return the info about all the modules.

![](media/13e9f602f744b96c56fa23709a1ee8b1.png)

In this way, we can calculate the offset of each function, and then obtain them in kernel

![](media/65bedec4ef0269cf851c672ef23215d2.png)

## Once memory was prepared will trigger the vulnerability:

The **pipeArbitraryWrite()** function is called twice, there is a flag that initially is zero for the first call and when in the second call it is value 1, it will change the values of the HeapSpray.

![Texto Descripción generada automáticamente](media/1049c88b1e85f4da8ce6b2175fe8b481.png)

In the first call in the 0x5000000 memory address, the following values are located

![](media/8bc9e541c151347abf7ec6eead54064d.png)

Remember that this value in addition to alloc in that direction, is stored in our HeapSpray.

![](media/36cca164f53318b93429edb0603c9139.png)

This is how the memory is after the first call, as we said in the address around of 0x5000000

![](media/494e8a1ff997253f0b7f2e802dde7870.png)

And in the HeapSpray from memory 0x10000 it will store the pointer to **AttributeValueSize** every 0x10 bytes, besides the pointer to 0x5000000.

![](media/3d7daf50eaf010c74f992545e017780d.png)

## Reading the System Token:

This sequence will trigger the bug:

![](media/34de80a6d13362fce18f7a4e46c6942e.png)

**CreateLogFile()** is called again on the crafted file and on another with a random name.

**AddLogContainer()** is then called using the handles of those files.

![](media/4eddb63ff524603ecced2916eb4bd1a0.png)

The **NtSetinformationFile()** is called, and the handles are closed with which the pointer is corrupted (it'll be explained later)

![Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente con confianza media](media/417e8cef456d0c7bb5dc7d25114aae07.png)

The HeapSpray, prevents a BSOD from occurring at this point:

![](media/519cb4c0f191a359419dda213b82c02d.png)

Setting a breakpoint there, we can see that the pointer is corrupt and points to our HeapSpray, with which we can handle the next two function calls of the vtable.

![Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente](media/d468f56d214ef2e45093843c33f3b74c.png)

![Texto Descripción generada automáticamente](media/be099ae6389b13c0fc6cf5f2c3623afd.png)

**RAX** takes the value *0x5000000* and jumps first to the function located at *0x5000000+18* and then to *0x5000000+8*.

![Texto Descripción generada automáticamente con confianza media](media/40e30d6fad88165c09e501ac5929991e.png)

![Imagen que contiene Interfaz de usuario gráfica Descripción generada automáticamente](media/a4ee774020ef2a8f5ea1f22e100d467a.png)

So first jump to **fnClfsEarlierLsn()** and then to **fnSeSetAccessStateGenericMapping()**.

We trace from the breakpoint and see that it reaches **CLFS!ClfsEarlierLsn()**.

![Texto Descripción generada automáticamente](media/3ee895ddf3a631428d914d3730d6a38c.png)

This function is called exclusively because when it returns, it sets *EDX* to *0xFFFFFFFF*

![Interfaz de usuario gráfica Descripción generada automáticamente con confianza media](media/29754cdf9bfdcd6f59bed75e7f812401.png)

In the address 0xFFFFFFFF we had stored the result of the SYSTEM \_EPROCESS & 0xFFFFFFFFFFFFFFF000

![Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente](media/0903098d3c43251b58b0b819d9f605da.png)

As we mentioned, when returning from **CLFS!ClfsEarlierLsn()**, *RDX* value is 0x00000000FFFFFFFF

![Texto Descripción generada automáticamente](media/adb6783d4ebf7ef1171d1a50e0a290b2.png)

We come to the second function **nt!SeSetAccessStateGenericMapping()**

![Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente](media/d759d21982f51badfa638c03f2bf2dff.png)

This function is useful, since **RCX** points to our HeapSpray, and RDX value is 0xFFFFFFFF, whose content we control

![Interfaz de usuario gráfica, Aplicación, Teams Descripción generada automáticamente](media/39c890ef6c798f68561293ec866337a5.png)

![Texto Descripción generada automáticamente](media/298c695f497e1601bee46d739ebec995.png)

The content of *RCX+0x48* have the pointer to **AttributeValueSize** that was stored in **v30.Pointer+24**

![Interfaz de usuario gráfica, Texto Descripción generada automáticamente](media/097e76059eb6297bd706308d0c0f26a4.png)

![Interfaz de usuario gráfica, Texto Descripción generada automáticamente con confianza media](media/8c97ff4f555d8820b97f4d266245953c.png)

![Interfaz de usuario gráfica, Texto Descripción generada automáticamente](media/da7272b49df6e07b5e159a240767995b.png)

That pointer value of **AttributeValueSize** is moved to **RAX**, then reads the contents of the address 0xFFFFFFFF where we had stored the address of the SYSTEM \_EPROCESS & 0xFFFFFFFFFFFFFFF000.

![Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente](media/d9b89aeaeb48dea7922d4d49f14f0aa9.png)

Then overwrites in **RAX+8** the next field which is the **AttributeValue()**

![Texto Descripción generada automáticamente](media/99d1ee82573d2cec22770ec41be3b11d.png)

![Texto Descripción generada automáticamente](media/064a8482331765892ce30ebba70c5228.png)

Of course, the **AttributeValue** would normally point in kernel to the attribute we added.

![Calendario Descripción generada automáticamente](media/76e6c6749ae1d86d1bb9336368eb69ba.png)

And now we’ll overwrite it with a pointer of the result of the system \_EPROCESS & 0xFFFFFFFFFFFFFFF00.

That will mean that when we call the **NtFsControlFile()** function again, this time with the 0x110038 argument to read the attribute, instead of returning the "A” that were pointed by the **AttributeValue** pointer, it will now read from \_EPRROCESS & 0xFFFFFFFFFFFFFFFFF000 the requested number of bytes and return it in the output buffer with which we can obtain in the first call the value of the **SYSTEM TOKEN**.

![Texto Descripción generada automáticamente](media/2d58846b58a11952e391c71364ab5a69.png)

**v9b** is the start address of the *Output Buffer* where the content of the result of System EPROCESS & 0xFFFFFFFFFFFFFFF000 were copied.

To that he adds v14 which are the last 3 bytes of the System **EPROCESS** and then adds **0x4b8** which is the offset of Token for this version of Windows 11, then finds the contents of that address that will have saved the value of the **System Token**.

![Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente](media/b4963eaf5f77f360089a84da3f39bc4b.png)

![Texto Descripción generada automáticamente](media/87d3fc9094384b6318e3243f655e3d2d.png)

## Validating the token

![Texto Descripción generada automáticamente con confianza baja](media/585e2bd5aa251866d2d1ca939581c9b5.png)

Remember that the last 4 bits were changed, it is not significant, so the value still matches.

## Overwrite our process’s token with the system one

In the second call the value of Flag is 1 since it was incremented at the end of the first call.

![Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente](media/8c44d31c4b28f49ddc0bdff3c86656a1.png)

There we see the order in which the values are stored

![Interfaz de usuario gráfica, Texto Descripción generada automáticamente](media/0149eb13d876c9445fbb21eb0b3b38ce.png)

The address **0xFFFFFFFF** with the value we have just found of the **System Process Token**.

![Interfaz de usuario gráfica, Texto Descripción generada automáticamente](media/f005f904defa08371a65e7e301a97dcc.png)

![Texto Descripción generada automáticamente](media/6bd0e605614ce997b4cec26c4d019449.png)

And in the HeapSpray is the value of the Token address of my process to which I subtract 8. This value plus eight will be used as a target, remember that you wrote on the address pointed by **RAX+8**.

![Graphical user interface, text Description automatically generated](media/f41c400a88985a50bb2d6fdfea265804.png)

![Texto Descripción generada automáticamente](media/a36c08dbb8c6f86f7871a2c395b568bb.png)

In the memory address starting on **0x5000000**

![Interfaz de usuario gráfica Descripción generada automáticamente](media/1e3c8617c79a20ee56f86be0476d97bc.png)

We also see that it uses the name of other container, since the previous one is being used by the system process cannot be opened again or deleted.

![](media/0776b9af209ceb859b3710137c6e6220.png)

Then the bug is triggered for the second time in the same way as it was in the first try.

![](media/41a62292dec3b436f847515ad7f94270.png)

It comes again to **CLFS!ClfsEarlierLsn()**.

![Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente](media/bc0393697b5a7d3a9f116ab55180df1f.png)

setting **RDX** to **0xFFFFFFFF**

![Una captura de pantalla de un celular Descripción generada automáticamente](media/e41f907bdb5d07eaff19672de45e6df1.png)

Then it comes to **nt!SeSetAccessStateGenericMapping()**

![Texto Descripción generada automáticamente](media/4491000ecb7c965fefdb36e0c6d55abe.png)

Read the address of the Token of my process minus 8 where is going to write

![Texto Descripción generada automáticamente](media/944476195e2ae3cf419718748138d027.png)

Then reads the **SYSTEM TOKEN**

![Texto Descripción generada automáticamente](media/5cf6b957b7dda491feb3ae129287dd06.png)

And writes in the address of the Token of my process (it adds 8), the **System Token**

![](media/02757f7dcc2a8ba85eb22ba2e0ce4533.png)

And that way my process is with the System Token

![Texto Descripción generada automáticamente](media/94ddc9e8e52745252668e816e67c0128.png)

Once the token is written, we start a process to check the privileges, in this case, we launch Notepad.exe

## Executing process as system:

![Texto Descripción generada automáticamente](media/56c7e934a624114aeaac632563078644.png)

![Interfaz de usuario gráfica, Aplicación, Word Descripción generada automáticamente](media/c152e2540d2e2d07e2e2bf78210c8954.png)

![Texto Descripción generada automáticamente con confianza baja](media/c8f092d14e55486ed779cc6a9abedecc.png)

Remember that this POC only works in Windows 11, in Windows 10 it will produce a BSOD, so you should make some modifications to work correctly, it is not explained in this blogpost.

## Reversing the Patch:

Analyzing the structures

The structures and most of the documentation on the CLFS file format, we have taken from IONESCU's excellent work on [CLFS Internals](https://github.com/ionescu007/clfs-docs/blob/main/README.md).

We can see that a check has been added in the function **ClfsBaseFilePersisted::LoadContainerQ**

![Texto Descripción generada automáticamente con confianza media](media/1016eb11bc91ef1b34fa555f239b5202.png)

The values that perform an addition, belongs to the \_**CLFS_BASE_RECORD_HEADER** structure.

![Escala de tiempo Descripción generada automáticamente con confianza media](media/b8603f16fe89b20bfc4bbb29dd880cc2.png)

Note that the **Base Block** starts at offset 0x800 of the file, and ends at offset 0x71FF, corresponding the first 0x70 bytes to the **Log Block Header**

![](media/3e1ec3f9d7f28d1f32a6a9be4e50cdfb.png)

As a good practice, we can add the **\_CLF_LOG_BLOCK_HEADER** structure on IDA

struct **\_CLFS_LOG_BLOCK_HEADER**

{

UCHAR MajorVersion;

UCHAR MinorVersion;

UCHAR Usn;

char ClientId;

USHORT TotalSectorCount;

USHORT ValidSectorCount;

ULONG Padding;

ULONG Checksum;

ULONG Flags;

CLFS_LSN CurrentLsn;

CLFS_LSN NextLsn;

ULONG RecordOffsets[16];

ULONG SignaturesOffset;

};

Then we have the **Base Record Header (_CLFS_BASE_RECORD_HEADER)** that starts at the offset 0x870 from the beginning of the file and is 0x1338 bytes long.

![Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente](media/d98138801c6468ed084b4a882ffbf5be.png)

If you want to import it to IDA, before you must add the following types and missing structures

typedef GUID **CLFS_LOG_ID**;  
typedef UCHAR **CLFS_LOG_STATE**;

struct **\_CLFS_METADATA_RECORD_HEADER**

{

ULONGLONG ullDumpCount;

};

Now is ready to be added:

typedef struct **\_CLFS_BASE_RECORD_HEADER**

{

CLFS_METADATA_RECORD_HEADER hdrBaseRecord;

CLFS_LOG_ID cidLog;

ULONGLONG rgClientSymTbl[0x0b];

ULONGLONG rgContainerSymTbl[0x0b];

ULONGLONG rgSecuritySymTbl[0x0b];

ULONG cNextContainer;

CLFS_CLIENT_ID cNextClient;

ULONG cFreeContainers;

ULONG cActiveContainers;

ULONG cbFreeContainers;

ULONG cbBusyContainers;

ULONG rgClients[0x7c];

ULONG rgContainers[0x400];

ULONG cbSymbolZone;

ULONG cbSector;

USHORT bUnused;

CLFS_LOG_STATE eLogState;

UCHAR cUsn;

UCHAR cClients;

} CLFS_BASE_RECORD_HEADER, \*PCLFS_BASE_RECORD_HEADER;

![Interfaz de usuario gráfica, Texto Descripción generada automáticamente](media/cbec5d60cd9dac672c9a5182a54adb67.png)

After including the structures, we notice that performs an addition between the **cbSymbolZone** and the address where the **\_CLFS_BASE_RECORD_HEADER** ends. (start + 1338h)![Texto, Aplicación Descripción generada automáticamente](media/796b36868271191e4b27df9313560b60.png)

Remember that **cbSymbolZone** was modified in the crafted log file from 0x000000F8 to 0x0001114B.

(offset **0x1b98** of the file)

0x800(offset of the start of the Base Block) + 0x70 (logBlockHeader) + 0x1328 (cbsymbolZone)

0x800+0x70+0x1328 = **0x1b98**

Crafted **cbsymbolZone** on *MyLog.blf* file:

![Tabla Descripción generada automáticamente](media/095983893a20fb5c2d92f3f45fe7b257.png)

![Imagen que contiene Texto Descripción generada automáticamente](media/dd3dc96e0d35558651c3bb1fc2c61d72.png)

As the patch is in the **CClfsBaseFilePersisted::LoadContainerQ** function, we have to take a look at the **CClfsBaseFilePersisted** object.

Setting a breakpoint in **CLFS!CClfsBaseFilePersisted::LoadContainerQ** and when **CreateLogFile** is called with the handle of the crafted file it’ll break.

![Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente](media/8eb82c3a265a999ed368ee23a69db35e.png)

Call the **CClfsBaseFile::GetBaseLogRecord** function to get the address of the **Base Log Record** (**\_CLFS_BASE_RECORD_HEADER)**

![Graphical user interface, application, timeline Description automatically generated](media/eee9a3ed44c17b671288932b52a0fdb8.png)

**RAX** will point to the \_**CLFS_BASE_RECORD_HEADER** address

![Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente](media/85e35ebc5b3151be9452fc78e70b0844.png)

Note the **\_CLFS_BASE_RECORD_HEADER** structure in memory and the **cbsymbolZone** field **0x1328**

bytes forward

![Imagen que contiene Texto Descripción generada automáticamente](media/12cf6e1efd4dc79e4ddb3523a74e7582.png)

![Texto Descripción generada automáticamente](media/8550a4868b8b82813d43a75295716ba2.png)

**r14**  stores the structure corresponding to the “this”, which is **CClfsBaseFilePersisted** since it is the  **this** of the function **CClfsBaseFilePersisted**:**:LoadContainerQ.**

![Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente](media/1bad33c921d3b1f5fe64cd5e014ba869.png)

The **CClfsBaseFilePersisted** structure in memory:

![Texto Descripción generada automáticamente](media/01b40e29a145ca0e71748840f84d27f6.png)

So, let's create a structure with length **0x21c0** to complete its fields while we reverse it (it's an undocumented structure) we'll call it **struct_CClfsBaseFilePersisted**

![Tabla Descripción generada automáticamente con confianza media](media/5558b8b9c8abe1052dfa19baebfa76ea.png)

Inside the function **CClfsBaseFile::GetBaseLogRecord()** gets the pointer to **\_CLFS_BASE_RECORD_HEADER**. and we know that the "*this*" in that function is the structure: **struct_CClfsBaseFilePersisted**.

![Escala de tiempo Descripción generada automáticamente con confianza media](media/2a5c04ad01c96b4df67f048ffcd48703.png)

Read two fields (offset *0x28* and *0x30*)

![Interfaz de usuario gráfica, Aplicación, Tabla Descripción generada automáticamente](media/347410d3f6e79cab65fea5a3b0163f92.png)

Field *0x28* is a word and has the value 6, so we change the type to **word** in the structure.

![Texto Descripción generada automáticamente](media/090cd9e067cd1edaaddea03aefc392ae.png)

![Texto Descripción generada automáticamente con confianza media](media/ddb4494c6ded40c3e099b305d9a3a2ef.png)

![Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente](media/17a6c9a5a3b217bb1cc4fe587dc9e46e.png)

For now, we rename it to constant 6 (*const_6*)

![Texto Descripción generada automáticamente](media/079c8eee97604f1d812426887a58d2a9.png)

![Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente](media/2ff1e6de5d218252793b45765447fb52.png)

According to the documentation, 6 would be the number of blocks **CLFS_METADATA_BLOCK_COUNT**. The field could refer to this value.

And that pointer is at offset *0x30*.

![Tabla Descripción generada automáticamente](media/bf9bd34736157b91f5f4046077734158.png)

Note that the size shown there includes the header with length 0x10

![Texto Descripción generada automáticamente](media/d271ada6eed02dfbdd27eb3b668a5c20.png)

![Forma Descripción generada automáticamente con confianza media](media/532f0fc7fc9fccceeee187ee97d9aefd.png)

When the ExAllocatePoolWithTag function is called, a few bytes are requested, but the header is not included, therefore, 0x90 bytes (0xa0 – 0x10) will be requested in the call.

Searching by text *+30h]*, the instructions that write at offset 0x30 we found a long list, but filtering the list by the type of object **CClfsBaseFilePersisted** leaves us with few results and immediately find where that size is allocated, and the same tag. (*Tip:* *Create* and *Initialize* function names, are always the first to look at)

![Texto Descripción generada automáticamente con confianza baja](media/938d70cacfe611e71cb18221b515d706.png)

![Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente](media/e1c25d0b0ae5611c356a3b0307d1f31e.png)

Since we still don't know the name, we'll put it *pool_0x90*, which is another undocumented structure, and we'll create a structure of that size.

![Texto Descripción generada automáticamente](media/17b31bfcb88ebdd82e831a66760cd8f5.png)

![Interfaz de usuario gráfica, Tabla Descripción generada automáticamente](media/6f00231c25743e408acedc33f53168a5.png)

The pool_0x90 in memory has another pointer at its own offset 0x30.

![Aplicación, Tabla Descripción generada automáticamente con confianza media](media/f0f5ec2aa0847777988ec017ea2adbde.png)

This other pointer points to the base block in the file (Base block starts at offset 0x800)

![Forma Descripción generada automáticamente](media/03bdfaab6cf6d79b3395d15550b22ab3.png)

![A picture containing calendar Description automatically generated](media/8c9a91225f1a90ae961c4bb6e846b538.png)

Image taken from the [Zscaler blogpost](https://www.zscaler.com/blogs/security-research/technical-analysis-windows-clfs-zero-day-vulnerability-cve-2022-37969-part):

![Graphical user interface, application, email Description automatically generated](media/f3babbfd6fa4125d92884ab26c55b042.png)

The allocation is huge, because it contains the entire base block.

![Text Description automatically generated](media/74ad1a03a0fed25b71b9b67f98553773.png)

![Graphical user interface, text, application Description automatically generated](media/eb9c240128c49e22f0e89d1e690d82d8.png)

![](media/6fd90f0b5e6d154ee24880941abf37e4.png)

So, we will create a new structure of size **0x7a00** and call it **BASE_BLOCK**

![Graphical user interface, text, application Description automatically generated](media/fdaed1f2fc0dffa269926eb16e71ed4e.png)

The first 70 bytes we already knew correspond to \_**CLFS_LOG_BLOCK_HEADER** and the following 0x1338 to \_**CLFS_BASE_RECORD_HEADER**.

![Text Description automatically generated](media/4afba1e7fe36c27f6a786e10c817f909.png)

So, adding the start of the **Base Block** with the offset to the next record (which is 0x70), we get the **\_CLFS_BASE_RECORD_HEADER**

![Application Description automatically generated with low confidence](media/d37ba7da05c117dc914d5ee3de9a2256.png)

The \_**CLFS_BASE_RECORD_HEADER** on memory.

![Calendar Description automatically generated](media/569e806a0126db3be8dda672b613b0ce.png)

Looking at other methods of the same **CClfsBaseFilePersisted** object, in **CClfsBaseFilePersisted::AddContainer** you get with **CClfsBaseFile::GetBaseLogRecord** also the address of **\_CLFS_BASE_RECORD_HEADER**.

![Text Description automatically generated](media/ea032ef9b858e5d393aa787f62b28bdf.png)

Next, call **CClfsBaseFile::OffsetToAddr** using *cbOffset*, it gets the address of **\_CLFS_CONTAINER_CONTEXT**, and stores **cboffset** in the **rgbcontainers** array which is at offset **0x328** of the **\_CLFS_BASE_RECORD_HEADER**.

![Graphical user interface, application Description automatically generated](media/8d8667795bdd2d3a52b6fb858a4f99bf.png)

**CClfsBaseFile::OffsetToAddr** function is used to find structures addresses from offset

![Graphical user interface, text, application Description automatically generated](media/2e925c95edeb52514f3ec7e7bc1aa19e.png)

At this point, the container offset that will be stored at 0x328 is still 0, because we have not added a container yet.

![Background pattern Description automatically generated with low confidence](media/abedb85eebe3986bbea64d7c08a39e75.png)

the PoC calls CreateLogFile twice, the first time with the malformed file MyLog.blf and the second time with the normal MyLogxxx.blf file, so we must stop debugging twice in all the above places and take note in notepad of the addresses of the above structures for both files.

![Text Description automatically generated](media/14679dbd33d68eb3f3b57364d0a8ea3c.png)

Let us fast forward a bit to **CLFS!CClfsLogFcbPhysical::AllocContainer** by setting a breakpoint on it and running there.

When the **AddLogContainer()** is reached on the POC, we stop at the breakpoint.

![A picture containing application Description automatically generated](media/49eb5210a1118103c5e61be9bc366186.png)

Let’s also set a breakpoint on the **CClfsBaseFilePersisted::AddContainer+176** where we saw before that will find the offset and pointer to the **\_CLFS_CONTAINER_CONTEXT** structure.

![A screenshot of a computer Description automatically generated with medium confidence](media/ea020ab80e6263e5c2fd7f9bf534c10e.png)

![Graphical user interface, text, application Description automatically generated](media/133b888e7bd7e57096b2fab69e4059bb.png)

When the Debugger breaks, we can see that the offset is **0x1468.**

![Text Description automatically generated](media/7e94d5a9f69c6a836454a0914e2b3991.png)

In **RAX** will return the address of the **\_CLFS_CONTAINER_CONTEXT** structure.

![Graphical user interface, text, application, table Description automatically generated](media/455c75deb684dbea7fc831ffc7c80925.png)

the structure is still empty because it was not added the container yet.

![Text Description automatically generated](media/53d4ad7c493e4423ef60d150a74a727e.png)

Note that the **SignatureOffset=0x50** value that we wrote at offset **0x868** to the malformed file, subtracting the **0x800** from the start of the base block, will be in the **\_CLFS_LOG_BLOCK_HEADER** structure at offset **0x68**.

![Graphical user interface, text, application Description automatically generated](media/0cfb6c06b75f63bc8311fd498a3326c6.png)

![Text, letter Description automatically generated](media/d2bad6aff3a8e437d6f6c734c9c486b8.png)

When the PoC calls **AddLogContainer**() function using the malformed file, at offset 0x68 of **\_CLFS_LOG_BLOCK_HEADER,** instead of the 0x50 value we wrote there, is currently a 0xFFFF0050 in memory.

![A picture containing text Description automatically generated](media/784a567022b42fa2fc8fbbbaaf80dba8.png)

At some point, that value was altered by the program, in order to see when it happened, in the next execution, we will set a memory breakpoint on write.

The offset is stored at **r15 + 0x328** (**r15** points to the **\_CLFS_BASE_RECORD_HEADER** structure)

![Text Description automatically generated with medium confidence](media/8a553fc60ba05f7e8352f828ab0a1ea2.png)

![Graphical user interface Description automatically generated with low confidence](media/a3747260ee434eaae7f00778be96202a.png)

RBX stores the offset **0x1468**.

![A picture containing calendar Description automatically generated](media/eb032f375e19d7a96e6a6f24b9a0ac51.png)

So, in the **Base Block** address + **0x70** + the offset **0x1468** that we found out, there will be the address of the **CLFS_CONTAINER_CONTEXT** container.

![Text Description automatically generated](media/b902ca95864481c1493d3917c9e11349.png)

In **CLFS_CONTAINER_CONTEXT** structure at offset **0x18** will be the **pContainer** pointer that will be stored there, we can set a breakpoint on write and see when it is written.

![Text Description automatically generated](media/e052ec6a98729c8cb83bd7c1aaf639cf.png)

![A picture containing text Description automatically generated](media/0fdcfe50bfd6d3e9090ce2f391f6d001.png)

This is the pointer that we must corrupt since in the function where the vulnerability is, it first reads the **CLFS_CONTAINER_CONTEXT,** then moves it to *r15* and next reads the value of *r15+18*, which is this pointer that we have just set the Breakpoint on write.

![Graphical user interface, application, table Description automatically generated](media/576ca65ed4bdf1423f24d33cd1853c57.png)

![Graphical user interface, application, Word Description automatically generated](media/abcee5b3969810f323ae89b36c37429b.png)

it stores the **pContainer** at offset **0x1c0** of the **struct_CClfsBaseFilePersisted** structure.

![Text, application, whiteboard Description automatically generated](media/48121a45225d1872e6673a7a8776793b.png)

After several times that it stops, we reach the moment where it gets corrupted. The top of the pointer address has been changed from *FFs* to zero.

![Calendar Description automatically generated](media/f3717b0f9fdd1a9ef788d017575f7d44.png)

This happens when the second **AddLogContainer()** of the malformed file is called, the pointer of the previous *MyLogxxx* is corrupted.

The problem occurs because the **SignaturesOffset**, which should be **0x50**, is now **0xFFFF0050**, so it allows writing out of bounds in the **memset** that follows.

![Graphical user interface, text, application Description automatically generated](media/27bb8025f56cb83e0a7fe9f0297725f2.png)

![Graphical user interface, text Description automatically generated](media/af11eaa7efe7d3bf27dd08914115495e.png)

## Corrupting the “pContainer” pointer:

The **memset()** function is going to corrupt the **\_CLFS_CONTAINER_CONTEXT** structure that is below, this structure corresponds to the **MyLogxxx file**, since when were created, it located them 0x11000 bytes away from each other.

This way, it calculates exactly where to write to the next structure and zeroes out the top of the pointer, so it points to the user heap where the HeapSspray was created.

the base block structure of the malformed file is just 0x11000 before that of the MyLogxxx file.

Malformed:

![Shape Description automatically generated](media/eec2797ee5ec076f1c792ac2da59f9b7.png)

MyLogxxx

![A picture containing text Description automatically generated](media/2f177bbad0b52e190c94c4e2bc077a89.png)

![](media/6f8ab9ad7efcad025c5273bea7b74d08.png)

*RCX* is smaller than *RDX* since **0xFFFF0050** was added to, instead of 0x50 as it should be.

![Graphical user interface, text, application Description automatically generated](media/ef3ef67356c07a33d99110bb05ce82cd.png)

and we got to the **memset()** function, to set the amount of 0xb0 bytes with Zeroes, with RCX pointing to the **CLFS_CONTAINER_CONTEXT** structure of the MyLogxxx file, specifically to the **pContainer** five high bytes.

![Text Description automatically generated](media/92197bf2b231de1ffc26e84fa583cc1e.png)

This pointer Will be corrupted by overwriting the first bytes:

![Text Description automatically generated](media/8e4f070ba210c227cb23c516fd94405b.png)

remaining pointing to a memory address previously controlled by us through HeapSpray

![Text Description automatically generated](media/86b091266dd8e7137fe7eebc036d26ae.png)

![A picture containing text Description automatically generated](media/465a4c8066f7e996a13a19ffa77eaa51.png)

Then, the handle of the **MyLogxxx** file will be closed, and reaches the **CClfsBaseFilePersisted::RemoveContainer**, the vulnerability finally is triggered.

![Text, application Description automatically generated with medium confidence](media/3ec3c8527fd8082ca659f1ae0a6f0c75.png)

## Revisiting the Patch

Now that we have more information, we notice that here it reads the **Base_Block.LOG_BLOCK_HEADER.SignaturesOffset** and **the Base_Block. .LOG_BLOCK_HEADER.TotalSectorCount**

In the first part of the patch that **SignaturesOffset** should not be greater than 0x7a00, in ours it was originally 0x50, if it arrived with a value greater than 0x7a00 it would throw us out.

![A picture containing diagram Description automatically generated](media/c403805f6e20144e953130185f743a94.png)

Running the PoC in the patched machine, it compares 0x50 with 0x7a00 and since it is smaller it continues.

![Text Description automatically generated](media/dc4d96f41fe4fd1e255a71ac7bb1f337.png)

In the following block, the malformed **cbSymbolZone** is added to the value of the final address of **\_CLFS_BASE_RECORD_HEADER** and this sum is stored in **result_1**.

![Text Description automatically generated](media/10385a7da0fa3d237948990809d269ab.png)

Then, the address of the **Base_Block** is added with the **SignatureOffset** value, which in a normal file is **0x7980**.

![Text Description automatically generated](media/da5fb89d97bd2d493f242e8f141177d3.png)

The maximum address of the **base_block** is 0x7a00, now the **SymbolZone** is allowed up to **0x80** before the limit.

It will store it in result_2, that is, that would be the maximum limit for the **SymbolZone** inside the **base block**, then it compares both results if the first is greater than the second, it means that it went out of bounds.

![Text Description automatically generated](media/2869084447c2d3dab338a6afa82e186e.png)

![Text Description automatically generated](media/783394831113e1bd9546f3d588583dcc.png)

Obviously the first member will be bigger than the second and it will not continue, since the first sum of the **cbSymbolZone + final address of the \_CLFS_BASE_RECORD_HEADER** exceeds the limit (which is the **result_2)** and leads in an “out of bounds”.

![Graphical user interface, text, application Description automatically generated](media/fa8c8ba34f2073393ec9905dd285b0f6.png)

## Corrupting the SignatureOffset

The last thing we would have to figure out is where the **SignatureOffset** value of **0x50** becomes **0xFFFF0050**.

So, let's start over, reboot and stop at **CLFS!CClfsBaseFilePersisted::LoadContainerQ** where the value has not yet been changed in memory and still is **0x50**.

Set an access breakpoint at offset **0x68** in **SignatureOffset**.

![Calendar Description automatically generated](media/414dd28f00d1a0ee4f14b7be6ff57d23.png)

And after several stops, we detect the right moment when it modifies the value, in the **ClfsEncodeBlockPrivate.**

![Graphical user interface, text Description automatically generated](media/9da5eaf9e67cc562e5926c5e7fecfd48.png)

This function is not patched, so it could be a behavior caused by the low value of **0x50** and the rest of the values being manipulated.

Among the crafted values, we can see the **ccoffsetArray** value whose name in the **\_CLFS_BASE_RECORD_HEADER** structure is **rgClients** and represents the array of offsets that point to the **Client Context** Object.

**rgClients** field is located at offset **0x138** *(0x9a8-0x800-0x70) of* the \_CLFS_BASE_RECORD_HEADER structure.

![Graphical user interface, table Description automatically generated](media/79dafd4b6cd9593124bb6fe34e110e8d.png)

![Text Description automatically generated with medium confidence](media/8e822e7f85f131ee73502ef1a530aa2c.png)

In the PoC, this value is malformed to point a fake client context object, called **FakeClientContext**![Text, whiteboard Description automatically generated](media/cddb44cbcf1dd2dda0b6a957ef2bcaf6.png)

![A screenshot of a computer Description automatically generated with medium confidence](media/3f478e705bcc251a8c369650872a2450.png)

This is the **Client Context** structure \_**CLFS_CLIENT_CONTEXT**

**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;**

**};**

**};**

The **eState** value is in offset **0x78** from the start of the structure, in the crafted file **0x23a0+0x78**.

![Text Description automatically generated with medium confidence](media/9df4808d07948568b136639477752cc5.png)

![Chart, scatter chart Description automatically generated](media/b01882435cc841428cde66a5eb30b2ea.png)

This value shows the status of the log.

typedef UCHAR CLFS_LOG_STATE, \*PCLFS_LOG_STATE;  
const CLFS_LOG_STATE CLFS_LOG_UNINITIALIZED = 0x01;  
const CLFS_LOG_STATE CLFS_LOG_INITIALIZED = 0x02;  
const CLFS_LOG_STATE CLFS_LOG_ACTIVE = 0x04;  
const CLFS_LOG_STATE CLFS_LOG_PENDING_DELETE = 0x08;  
const CLFS_LOG_STATE CLFS_LOG_PENDING_ARCHIVE = 0x10;  
**const CLFS_LOG_STATE CLFS_LOG_SHUTDOWN = 0x20**;  
const CLFS_LOG_STATE CLFS_LOG_MULTIPLEXED = 0x40;  
const CLFS_LOG_STATE CLFS_LOG_SECURE = 0x80;

this value is set to **CLFS_LOG_STATE CLFS_LOG_SHUTDOWN** =**0x20**

The other malformed value is **fAttributes** which corresponds to the set of **FILE_ATTRIBUTE** flags associated with the base log file (such as System and Hidden).

![Graphical user interface Description automatically generated with low confidence](media/c07053de9a4aa0bb2bb1ac61757f2728.png)

![Text, letter Description automatically generated](media/a0c9313c580a8afa0fbf0fe88c839e70.png)

Since the field starts a byte earlier at **0xa** and spans two bytes, the value of **fAttributes** is **0x100**.

![A picture containing table Description automatically generated](media/cf136dbc21537e22bd9232beb619e0e2.png)

![Graphical user interface, text, application Description automatically generated](media/be7e567421244222596d7bfc10462109.png)

Finally, there is the **blocknameoffset** value that points to the offset **0x1bb8**, I mean, by adding 0x78 and 0x800 points to the offset **0x2428** of the file.

![Text Description automatically generated](media/e1b17c194be9dabe01f9c00af1fb1b9d.png)

![Text, letter Description automatically generated](media/416f89dd29ccf939e67a61c40ce23e4d.png)

Note that the offset to the **Client Context** is *0x1b30*

![Table Description automatically generated](media/a0640ccacabeb8edf32e5ea263d5721c.png)

So, the **Client Context** is in offset 0x23a0.

![Text Description automatically generated with medium confidence](media/d994444654a2fb0e4955db5dd6e77c5c.png)

![Table Description automatically generated](media/d1663dddc334180cecdc14252fca4a9e.png)

And just 0x10 before, it is the value corresponding to **blocknameoffset**.

![Text, letter Description automatically generated](media/8aa56568274675fcf72804e582c7e2eb.png)

![Table Description automatically generated with low confidence](media/9ef1273405bbf5bc5308eba2723517d4.png)

Which would point to the string with the name

the last one is the **blockattributeoffset** which is **0xC** before the Client Context at **0x2394**.

![Table Description automatically generated](media/e90f2b048d850e3a34cfc53150068819.png)

These last two values ​​belong to a structure prior to the Client Context of 0x30 bytes long, called**\_CLFSHASHSYM**

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

![A picture containing diagram Description automatically generated](media/45b0542396ba506baec7ccdf5ee07260.png)

![Text Description automatically generated](media/156be25249e87412ba964e9fcf4f651b.png)

they are at 0x20 and 0x24 bytes from the beginning of the **\_CLFSHASHSYM** structure, so in the **\_CLFSHASHSYM** structure the value called **blockNameOffset** in the POC is the **cbSymName** field and the **blockAttributteoffset** is the **cbOffset** field.

![A picture containing text Description automatically generated](media/a629b8752df2a7b80a7c4a84e3558bae.png)

![Text Description automatically generated with medium confidence](media/344192097e052d9620c5acebfaeaef81.png)

Those are the malformed values, now we need to see how they affect to change our **SignaturesOffset** from **0x50** value to **0xFFFF0050**.

Let’s take a look at the **CClfsBaseFile::AcquireClientContext()** function, which should return the client context.

![Graphical user interface, text, application, email Description automatically generated](media/bc755caae18e17258276891ca04c77bd.png)

it calls the **CClfsBaseFile::GetSymbol** with the fourth argument which will be \_**CLFS_CLIENT_CONTEXT \*\*** where it will store the pointer to **Client Context**.

![Graphical user interface, text, application Description automatically generated](media/535f9add75cc413ae737b82f5012339d.png)

Inside the **CClfsBaseFile::GetSymbol** function we pass the malformed **ccoffsetArray** offset to **CClfsBaseFile::OffsetToAddr** and get the address of the client context, let’s set a breakpoint there so it’ll stop when calling the file created with **CreatelogFile** .

![Graphical user interface, application Description automatically generated with medium confidence](media/0ab30090cb9fe5f506c98b531b6ab6e0.png)

There it is stopped with the **ccoffsetArray** crafted argument.

![Graphical user interface, application Description automatically generated](media/894a5901138835bf86d4cc90b6221a89.png)

![Table Description automatically generated](media/d82cf261dc3f52c183f09e2d85092878.png)

The **CClfsBaseFile::OffsetToAdd**r function returns the false Client Context

![A picture containing graphical user interface Description automatically generated](media/bbe4add085f88155fdca2d80aaf411fc.png)

And checks that the value of **cbOffset** is not zero since 0xC is found before the **\_CLFS_CLIENT_CONTEXT** structure that is in RAX.

![A screenshot of a computer Description automatically generated](media/586cbacac6429e688ad7453450639396.png)

![A picture containing text Description automatically generated](media/30c51755c0b44bce3acb00cb13aa9129.png)

Then It compares the **cbOffset** with the **ccoffsetArray (**which is in RSI), they must be equal, otherwise we’ll get an error.

![A screenshot of a computer Description automatically generated](media/b2931ad454c3435a3c573af7bfc30dc7.png)

It also checks that **cbSymName** be equal to **cbOffset+0x88,** if not we’ll get an error too.

![Graphical user interface, text, application Description automatically generated with medium confidence](media/adf479c479b50ed46a74660af44b4b9e.png)

And finally, It compares the **cidClient** byte with zero

![A picture containing diagram Description automatically generated](media/189c6b6478d28ea5b640a7c7c1a5677d.png)

If all those checks are successful, the **client context** will be saved.

![A screenshot of a computer Description automatically generated](media/9d6196c034cfa5f57efa1d543696e0ad.png)

The output of function **r14** points to Client Context

![Graphical user interface, text, application Description automatically generated](media/6a1252644d847ed449b3b2de8efcc130.png)

When exiting from **CClfsLogFcbPhysical::Initialize** we'll have the address of **CLFS_CLIENT_CONTEXT**.

![Text Description automatically generated](media/08f0479d12f771e96aff9ff1c569b428.png)

Now It reads the value of **fAttributes** (**0x100**)

![Graphical user interface, text, application Description automatically generated](media/ea26f541d758ee570077f47c67be26bc.png)

this function belongs to the class **CClfsLogFcbPhysical**

![Text Description automatically generated](media/8b24cc6c8e806ca2482ab43586199025.png)

![Graphical user interface, text, application Description automatically generated](media/9d84e17c18ee4a370828ad101591824d.png)

Which was allocated here, and its size is **0x15d0** and its tag is **“ClfC”**

![Text Description automatically generated with medium confidence](media/ec7a6d54368b04538733a32ac5fc9eab.png)

Let’s create a structure to store what we are reversing, we’ll call it: **struct_CClfsLogFcbPhysical.**

![A picture containing table Description automatically generated](media/021a0e59109cd2c1fefd6bcc6d39ebbc.png)

Note that at **0x2b0** it saves the address of the **CClfsBaseFilePersisted** structure.

![A picture containing application Description automatically generated](media/4b416f61b1a20ecd509b40d42b4446e0.png)

After saving many values in the structure, it goes to an important part, it tests the **eState** with **0x20**.

![Graphical user interface, text, application Description automatically generated](media/dbb90ba5d93649144f656755f8c018a1.png)

![Graphical user interface, text, application, table Description automatically generated](media/b9f75e1bf359cefcd13f3c0045ce0b24.png)

Since the crafted value was **0x20**, the test will return 1.

![Table Description automatically generated](media/08e481d4dcdaa0647ee3ec1265d4d6df.png)

![Graphical user interface, text, application Description automatically generated](media/0afe1c482f6d3b8a0305ecda49839d6b.png)

We see that in the constructor in the **vtable** is

![Text Description automatically generated](media/8a2f5ded5152701716b7709d538d0386.png)

It will check if the file is **multiplexed**.

![Graphical user interface, application Description automatically generated](media/6bf55b20688c98b57c8094ebbb541d6d.png)

So, it goes by the desired path, reaching **CClfsLogFcbPhysical::ResetLog.**

![Graphical user interface, application Description automatically generated](media/07b271feca40cd1d4bd7f35359a976f8.png)

![Text, application, table Description automatically generated with medium confidence](media/ad821a17a552dd5fcabf7c97be23a3f3.png)

Several fields are initialized to zero except one that is initialized to 0xFFFFFFFF00000000.

![Graphical user interface Description automatically generated with low confidence](media/c4accb401ae49467f7bee324194fa8af.png)

Here retrieves the **Client Context**

![Graphical user interface, application Description automatically generated with medium confidence](media/31c5067008fb4e3aeecbbcad4efa48a2.png)

it stores the value **0xFFFFFFFF00000000**.

![Graphical user interface, application Description automatically generated](media/062a4d88fbd5b2f8874971ef404849fc.png)

![Graphical user interface, application Description automatically generated](media/f2e29240c5cbe00476febbbb17272133.png)

![A picture containing calendar Description automatically generated](media/6fd487d56596c71052b44ef409c44b8e.png)

It writes **0xFFFFFFFF** is offset **0x5c** which is the high part of **CLFS_LSN lsnRestart.ullOffset**

![](media/3b8c95585135cbc5df36d0fa10f3a6ee.png)

![Text Description automatically generated with medium confidence](media/115afcb613049b30fb52f7289bef4ac6.png)

![Graphical user interface, text, letter Description automatically generated](media/f911563ad0d16558fb6b2bfd336395ed.png)

Now we execute the **ClfsEncodeBlockPrivate()** function, which is the responsible to overwrites the 0x50 with 0xFFFF0050 as we have seen before.

There it reads the value of **SignatureOffset = 0x50** which is still as we put it in the malformed file and adds it to the start of **CLFS_LOG_BLOCK_HEADER**.

![Text Description automatically generated](media/477f7d89e9f6851d62076eca5fa73a09.png)

this is a loop that is writing 2 bytes, like the **SignatureOffset** instead of pointing to a correct value that in a normal file is a high value, for example 0x3f8 which makes it to write more forward, here it will write in the same CLFS_LOG_BLOCK_HEADER

The idea is to change the write destination to try to corrupt the **SignatureOffset** value.

Normal File

![Table Description automatically generated](media/46b772b0d55962225d7834c0ffe2b173.png)

At this point, it will start to loop and write two bytes.

![Graphical user interface, application Description automatically generated](media/8e97d31fb9a8e0d6c4fb940d7b638f3f.png)

The counter must reach the value 0x3d to exit the loop.

![Graphical user interface, application Description automatically generated](media/cd8cf02fe0597b74322f71cdc62e64ee.png)

RCX is increasing from 0x200, we are already in the third cycle, and its value is 0x600

![Graphical user interface, text, application Description automatically generated](media/90cc2ee4d222c458165d59cc1e25bb71.png)

in the iteration 0xe, RCX is 0x1a00

![Graphical user interface, text Description automatically generated](media/eb3b7da0b6379040789194b533cb84eb.png)

![A picture containing calendar Description automatically generated](media/d20f707149fc7aa77dfb5a82b751c407.png)

That was where he had written the **0xFFFFFFFF000000**.

![Graphical user interface, text, application Description automatically generated](media/58a86e211a46d533962a0d484ce7076c.png)

![Table Description automatically generated with medium confidence](media/b4ed0f91bf6823fb93d87ad79dd0d124.png)

It’s reading the last two bytes **FFFF**

![Text Description automatically generated](media/76ea3eeebde0dca776ca46a8975d56a1.png)

And it’ll copy then in R8

![A picture containing calendar Description automatically generated](media/47407488012a0939fd7bc351c6d070c2.png)

![A picture containing calendar Description automatically generated](media/fbbc116f79e11000397952701c7311e7.png)

As we've seen, this value is critical as it allows you to bypass the check and write out of bounds to corrupt the **pContainer** pointer of the file that follows the **memset()** and write zeros at the top and leave it pointing to our controlled memory (HeapSpray).

In the **CClfsBaseFilePersisted::AllocSymbol** that the same sum that is going to get the destination of the memset which is **cbSymbolZone + final address of CLFS_BASE_RECORD_HEADER** compares it before against **Base_block + 0xFFFF0050**, so it has corrupted values on both sides of the equation.

**CbSymbolZone= 0x1114B**

It is the malformed value that added to the final address of **CLFS_BASE_RECORD_HEADER** will make it write out of bounds and the other member of the comparison that should be the address of the **Base Block + SignatureOffset**, remains **SignatureOffset =0xFFFF0050** which allows this check to pass and write out of bounds in the **memset()** and zero the top of the pointer that will remain pointing to our HeapSpray.

![Graphical user interface, application, table Description automatically generated](media/fca5afe2dab2be700cfb1eed233d99dd.png)

Since **RCX** is smaller than **RDX**.

![Graphical user interface, application Description automatically generated](media/772edba280c0edd8ec8622fcd59900aa.png)

As we have seen before. (Values may differ because they belong to a previous execution)

It will corrupt the pointer, setting the highest bytes to 0

![Table Description automatically generated](media/62fc27c4f669f0d43f4b299540a13ac0.png)

Leaving it pointing to a memory area that we control through HeapSpray

![A screenshot of a computer Description automatically generated with low confidence](media/4efa074269a969dea6488111b5c771e6.png)

![Table Description automatically generated](media/30acc379766d76a0f8637d158a080d53.png)

So, when the vulnerability is triggered, we get to **CClfsBaseFilePersisted::RemoveContainer**

![A picture containing graphical user interface Description automatically generated](media/836961a9ab152d5ea12014163b77b2a5.png)

There will be the already corrupt pointer and it can be exploited as we saw previously.

![Graphical user interface, application Description automatically generated](media/0dd3fbe0c02c73af4136fcd4bba619ac.png)

At this point we have bug exploited, it leads to control the functions that allows to read the SYSTEM token and write in our own process to achieve the local privilege escalation.

We hope you find it useful, if you have any doubt can contact us at [Ricardo.narvaja@fortra.com](mailto:Ricardo.narvaja@fortra.com) and [Esteban.kazimiro@fortra.com](mailto:Esteban.kazimiro@fortra.com)

Enjoy!