
Análisis técnico y exploit de prueba de concepto para CVE-2023-28252, una vulnerabilidad de escalada de privilegios en el controlador del Sistema de Archivos de Registro Común (CLFS) de Windows utilizada en ataques del ransomware Nokoyawa.
Desde febrero de 2022 se reportó un nuevo ransomware que parece estar utilizando una vulnerabilidad de día cero de Windows, según la investigación realizada por Trend Micro.
Se puede encontrar más información sobre este ransomware en este enlace.
Según el análisis de Kaspersky, el grupo de ransomware Nokoyawa ha utilizado otros exploits dirigidos al controlador Common Log File System (CLFS) desde junio de 2022, con características similares pero distintas, todas vinculadas a un único desarrollador de exploits.
En abril de 2023, cuando Microsoft lanzó el parche, se asignó CVE-2023-28252.
Anteriormente, en 2022, investigamos un error similar en el mismo componente y lo documentamos en este blogpost
Para abordar el análisis, es necesario conocer el formato de archivo .blf, que es manejado por el controlador vulnerable Common Log File System llamado CLFS.sys y que se encuentra en la carpeta de controladores dentro de system32.
Se puede encontrar más información sobre este tipo de archivo en los siguientes enlaces:
https://github.com/ionescu007/clfs-docs/blob/main/README.md
https://www.coresecurity.com/core-labs/articles/understanding-cve-2022-37969-windows-clfs-lpe
Este análisis se realiza para Windows 11 21H2, clfs.sys versión 10.0.22000.1574, aunque también funciona en Windows 10 21H2, Windows 10 22H2, Windows 11 22H2 y Windows Server 2022.
En versiones anteriores de Windows, es necesario ajustar algunos valores, de lo contrario se produciría un BSOD.
Microsoft Patch Tuesday de abril de 2023.
Puede verificar la versión del controlador como se muestra
Cuando se publicó la vulnerabilidad, en abril de 2023 empecé con Esteban Kazimirow a realizar la ingeniería inversa del controlador CLFS.sys, aunque en este caso, solo analizar el parche era muy difícil de deducir dónde estaba el error y cómo desencadenarlo, ya que la explotación es muy compleja.
Más tarde, apareció un blogpost cuyo autor, a partir de una muestra de un malware, mostró algunas partes del código descompilado por HexRays y algo de información que orientaba hacia dónde debía enfocarse la explotación.
Obviamente, la información proporcionada no era completa, pero sin esta ayuda habría sido poco probable haber llegado a construir el PoC y posteriormente un exploit funcional.
Para facilitar su comprensión, primero explicaremos cómo construir el PoC y luego haremos el análisis de la vulnerabilidad.
Este blogpost contiene dos secciones:
Construcción del PoC:
1- Obtener las direcciones del kernel que necesitamos para la explotación
2- Preparar la ruta para crear los archivos .blf:
3- Crear el archivo "trigger blf" usando la función CreateLogFile()
4- Modificar el archivo "trigger blf"
5- Obtener la dirección en kernel del BASE BLOCK del trigger blf
6- Llamar a AddLogContainer con el manejador del trigger blf
7- Preparar los archivos spray blf
8- Preparar la memoria para realizar el spray
9- Desencadenar el error
Depuración:
1- Verificar el spray de memoria
2- Observar el RecordOffset[12] del trigger blf
3- Observar el valor iFlushBlock en el archivo spray blf
4- ¿Por qué lee desde BLOCK 1 SHADOW en lugar de BLOCK 0 CONTROL?
5- ¿Por qué la suma de verificación es igual a cero en los archivos spray blf?
6- Finalizar la explotación.
7- El parche real
Crearé una función llamada InitEnvironment para obtener algunas direcciones necesarias del kernel.
Obtendré la dirección EPROCESS de mi proceso y la almacenaré en la variable g_EProcessAddress, luego la dirección EPROCESS del proceso SYSTEM y la almacenaré en system_EPROCESS, luego la dirección EHTREAD del hilo principal de mi proceso y la almacenaré en g_EThreadAddress y finalmente la dirección del PREVIOUS MODE que en esta versión del PoC no se usará.

Este método es bien conocido, la función GetObjectKernelAddress llama a NtQuerySystemInformation dos veces con el primer argumento SystemExtendedHandleInformation, la primera llamada se realiza con un tamaño incorrecto y devuelve un error, pero también devuelve el tamaño correcto que se usa en la segunda llamada y obtiene la información de todos los manejadores, luego recorre en un bucle la información de cada manejador y en el campo Object del handleinfo correcto obtiene la dirección buscada en el kernel.

También necesito las direcciones en kernel de las siguientes funciones exportadas por CLFS.sys:
• ClfsEarlierLsn
• ClfsMgmtDeregisterManagedClient
Y las funciones exportadas desde NTOSKRNL.exe
• RtlClearBit/PoFxProcessorNotification
• SeSetAccessStateGenericMapping
Para obtener estas direcciones se utiliza un método similar al que se usa para obtener la base en kernel de ambos módulos, llamando a NtQuerySystemInformation dos veces, pero en este caso el primer argumento será SYSTEM_INFORMATION_CLASS (en el PoC usamos la función FindKernelModulesBase para este propósito).

Luego carga CLFS.sys y NTOSKRNL.exe como módulos normales en modo usuario llamando a LoadLibrary, obtiene las direcciones en modo usuario con GetProcAddress y luego resta la imagen base de cada uno, lo que da el desplazamiento de la función y finalmente suma cada desplazamiento a las bases correspondientes del kernel y así obtiene las direcciones en kernel de todas las funciones necesarias.

Creo una función llamada createInitialTriggerBlfFile que generará y escribirá un archivo .blf.
La ruta que se utiliza como argumento en CreateLogFile es diferente de una ruta normal, por ejemplo, para abrir el archivo 1280.blf ubicado en la carpeta C:\Users\Public, debemos establecer la ruta LOG:C:\Users\Public\1280. Esto se guardará en la variable stored_name_CreateLog.