Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2022-37969 — Exploit de prueba de concepto para CVE-2022-37969, una escalada de privilegios local en el controlador Windows Common Log File System. Demuestra heap spray, robo de tokens y escritura arbitraria en el kernel para obtener privilegios de SYSTEM. | Kitploit
Herramientas/GitHubGitHub/fortra/cve-2022-37969
Escalada de PrivilegiosForensia de MemoriaAnálisis de VulnerabilidadesExplotaciónExplotación de Binarios
GitHubfortra/cve-2022-37969

CVE-2022-37969

Exploit de prueba de concepto para CVE-2022-37969, una escalada de privilegios local en el controlador Windows Common Log File System. Demuestra heap spray, robo de tokens y escritura arbitraria en el kernel para obtener privilegios de SYSTEM.

Ver Repositorio
135381hace 3 añosRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2022-37969 Windows Local Privilege Escalation PoC

autores: Ricardo Narvaja & Daniel Kazimirow (Solid)

Solo con fines de demostración. El exploit completo funciona en sistemas vulnerables Windows 11 21H2.

PoC funcional basado en información previamente publicada por Zscaler

Consulte el artículo Understanding the CVE-2022-37969 Windows Common Log File System Driver Local Privilege Escalation.

Uso

Comprendiendo el CVE-2022-37969 Windows Common Log File System Driver Local Privilege Escalation.

Recorrido de explotación:

  • Creación del archivo de registro BLF inicial
    • Creación de múltiples archivos de registro BLF aleatorios
    • Creación del archivo de registro inicial
    • Realización de un Heap Spray controlado
    • Preparación de los métodos CreatePipe() / NtFsControlFile()
    • Una vez que la memoria está preparada, se desencadena la vulnerabilidad
    • Lectura del Token del Sistema
    • Validación del token
    • Sobrescribir el token de nuestro proceso con el del sistema
    • Ejecución del proceso como sistema
    • Ingeniería inversa del parche: Análisis de las estructuras
    • Corrupción del puntero “pContainer”
    • Revisión del parche
    • Corrupción del SignatureOffset
    • Corrupción de más valores
    • Control de las funciones que permiten leer el token SYSTEM
    • Escritura de nuestro propio proceso para lograr la escalada de privilegios local
    • Código fuente del PoC

El escenario utilizado aquí fue Windows 11 21H2 (OS Build 22000.918) clfs.sys v10.0.22000.918

Creación del archivo de registro BLF inicial

El primer paso es crear un archivo llamado MyLog.blf en la carpeta pública (%public%), utilizando la función CreateLogFile():

Creación de múltiples archivos de registro BLF aleatorios

Luego crea varios archivos de registro con nombres aleatorios usando un bucle.

Y dentro del bucle, llama a nuestra función getBigPoolInfo():

Llama a NtQuerySystemInformation(), con 0x42 (66 decimal) como primer argumento, devolverá en v5 la información sobre las asignaciones realizadas en el bigpool, cuya estructura es de tipo SYSTEM_BIGPOOL_INFORMATION.

Tenemos que llamar a esta función dos veces. La primera devolverá un error, pero nos dará el tamaño correcto del búfer para llamar la segunda vez y obtener la información deseada.

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente

v5 recibirá la información de la estructura SYSTEM_BIG_POOL_INFORMATION.

El número de asignaciones en el bigpool se almacena en el primer campo llamado Count; en el segundo campo hay un arreglo de estructuras SYSTEM_BIGPOOL_ENTRY.

Luego buscaremos en todas las estructuras la etiqueta "Clfs" y el tamaño 0x7a00.

Almacena en un arreglo llamado kernelAddrArray la VirtualAddress que es el primer campo de cada estructura que tiene etiqueta CLFS y tamaño 0x7a00. De ahora en adelante, los pools que cumplan ambas condiciones se llamarán: "pools correctos".

Además de almacenar cada pool correcto en el arreglo, almacena el último pool correcto encontrado en el contenido de la variable a2, que se utiliza como argumento de la función.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

De esta manera, a2 siempre apunta al último pool correcto con etiqueta CLFS y tamaño 0x7a00 creado.

La variable v26 siempre almacena el pool correcto anterior encontrado, ya que es igual a v24 (v26=v24), antes de llamar a getBigPoolinfo(), pero v24 se actualiza al salir de esta llamada con el último pool correcto encontrado, y v26 se queda con el pool correcto anterior encontrado.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Luego resta ambas direcciones, y en caso de que el resultado sea negativo, invierte los operandos para que siempre sea positivo.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

De esta manera, en v32 se almacenará la diferencia entre la VirtualAddress de los dos últimos pools correctos encontrados.

Luego hace algo similar, en este caso v23 es inicialmente cero, así que hace v23=v32 la primera vez.

La siguiente vez en el bucle v23 todavía tiene el mismo valor y no es cero, por lo que se rompe y va aquí.

V32 tiene la última diferencia y v23 la anterior; si son iguales, sale e incrementa uno, pero reinicia el contador a cero.

La idea es encontrar 6 comparaciones consecutivas de etiquetas CLFS y tamaño 0x7a00 cuyas diferencias sean iguales, y esa diferencia será 0x11000. Veremos al ejecutar que cuando encuentre 6 (ya que empieza desde cero) consecutivas con distancias iguales, dará ese valor de diferencia entre ellas.

Texto Descripción generada automáticamente

Allí vemos que encontró 6 consecutivas y salió del bucle de creación de archivos de registro.

En la carpeta "public" podemos ver los archivos creados.

Creación del archivo de registro inicial:

Nuestra función craftFile() abre el archivo original (MyLog.blf) y lo modifica para desencadenar el error.

Después de modificar el archivo, es necesario cambiar el CRC32, de lo contrario obtendremos un error de archivo corrupto.

Este valor se encuentra en el offset 0x80C del archivo.

Realización de un Heap Spray controlado

A continuación, realiza un HeapSpray, utilizando la función VirtualAlloc() para asignar memoria, en direcciones arbitrarias 0x10000 y 0x5000000 respectivamente, y guardando en la segunda asignación (0x10000), el valor 0x5000000, cada 0x10 bytes.

Preparación de los métodos CreatePipe() / NtFsControlFile()

Usa CreatePipe() para crear un pipe anónimo y llama a NtFsControlFile() usando 0x11003c como argumento para agregar un atributo; luego se puede llamar a esta misma función con el argumento 0x110038 para leerlo.

Más detalles de este método se pueden encontrar AQUÍ

Allí vemos el búfer de entrada que es el atributo que estamos agregando; si llamamos a NtFsControlFile() nuevamente con el argumento 0x11038, en la salida debería devolver este mismo atributo.

Busca en el pool la etiqueta del atributo creado (NpAt)

Y cuando lo encuentra, guarda en v30.Pointer la VirtualAddress de este pool.

V30.pointer+24 apunta a AttributeValueSize en el pool del kernel y lo guarda en uno de los HeapSprays que hicimos antes.

La idea es escribir en esa dirección del kernel+8, para sobrescribir el AttributeValue.

Texto Descripción generada automáticamente

La estructura PipeAttribute tiene como primer campo una LIST_ENTRY de 16 bytes, luego un puntero al nombre del atributo de 8 bytes y luego, en 0x18 (24 decimal), el campo AttributeValueSize que es el que estamos almacenando en el HeapSpray.

Después de eso, cargamos CLFS.sys y ntoskrnl en modo usuario, y usando GetProcAddress() encontramos las direcciones de las funciones ClfsEarlierLsn() y SeSetAccessStateGenericMapping().

Luego llamamos a la función FindKernelModulesBase() que encontrará la base del kernel de ambos módulos usando NtquerySystemInformation() esta vez con el argumento SystemModuleInformation para devolver la información sobre todos los módulos.

De esta manera, podemos calcular el offset de cada función y luego obtenerlas en el kernel.

Una vez que la memoria está preparada, se desencadena la vulnerabilidad:

La función pipeArbitraryWrite() se llama dos veces; hay una bandera que inicialmente es cero para la primera llamada y cuando en la segunda llamada su valor es 1, cambiará los valores del HeapSpray.

Texto Descripción generada automáticamente

En la primera llamada, en la dirección de memoria 0x5000000 se ubican los siguientes valores.

Recuerde que este valor, además de asignarse en esa dirección, se almacena en nuestro HeapSpray.

Así es como queda la memoria después de la primera llamada, como dijimos, en la dirección alrededor de 0x5000000.

Y en el HeapSpray, desde la memoria 0x10000, almacenará el puntero a AttributeValueSize cada 0x10 bytes, además del puntero a 0x5000000.

Lectura del Token del Sistema:

Esta secuencia desencadenará el error:

Se llama nuevamente a CreateLogFile() en el archivo manipulado y en otro con un nombre aleatorio.

Luego se llama a AddLogContainer() usando los handles de esos archivos.

Se llama a NtSetinformationFile() y se cierran los handles con los que se corrompe el puntero (se explicará más adelante).

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente con confianza media

El HeapSpray evita que ocurra un BSOD en este punto:

Al establecer un breakpoint allí, podemos ver que el puntero está corrupto y apunta a nuestro HeapSpray, con lo cual podemos manejar las siguientes dos llamadas a funciones de la vtable.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Texto Descripción generada automáticamente

RAX toma el valor 0x5000000 y salta primero a la función ubicada en 0x5000000+18 y luego a 0x5000000+8.

Texto Descripción generada automáticamente con confianza media

Imagen que contiene Interfaz de usuario gráfica Descripción generada automáticamente

Así que primero salta a fnClfsEarlierLsn() y luego a fnSeSetAccessStateGenericMapping().

Rastreamos desde el breakpoint y vemos que llega a CLFS!ClfsEarlierLsn().

Texto Descripción generada automáticamente

Esta función se llama exclusivamente porque cuando retorna, establece EDX en 0xFFFFFFFF.

Interfaz de usuario gráfica Descripción generada automáticamente con confianza media

En la dirección 0xFFFFFFFF habíamos almacenado el resultado de SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Como mencionamos, al retornar de CLFS!ClfsEarlierLsn(), el valor de RDX es 0x00000000FFFFFFFF.

Texto Descripción generada automáticamente

Llegamos a la segunda función nt!SeSetAccessStateGenericMapping().

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente

Esta función es útil, ya que RCX apunta a nuestro HeapSpray, y RDX vale 0xFFFFFFFF, cuyo contenido controlamos.

Interfaz de usuario gráfica, Aplicación, Teams Descripción generada automáticamente

Texto Descripción generada automáticamente

El contenido de RCX+0x48 tiene el puntero a AttributeValueSize que se almacenó en v30.Pointer+24.

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Texto Descripción generada automáticamente con confianza media

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Ese valor del puntero de AttributeValueSize se mueve a RAX, luego lee el contenido de la dirección 0xFFFFFFFF donde habíamos almacenado la dirección de SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF000.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Luego sobrescribe en RAX+8 el siguiente campo que es el AttributeValue().

Texto Descripción generada automáticamente

Texto Descripción generada automáticamente

Por supuesto, el AttributeValue normalmente apuntaría en el kernel al atributo que agregamos.

Calendario Descripción generada automáticamente

Y ahora lo sobrescribiremos con un puntero al resultado de SYSTEM _EPROCESS & 0xFFFFFFFFFFFFFFF00.

Eso significará que cuando llamemos nuevamente a la función NtFsControlFile(), esta vez con el argumento 0x110038 para leer el atributo, en lugar de devolver la "A" a la que apuntaba el puntero AttributeValue, ahora leerá desde _EPRROCESS & 0xFFFFFFFFFFFFFFFFF000 la cantidad de bytes solicitada y la devolverá en el búfer de salida, con lo cual podemos obtener en la primera llamada el valor del SYSTEM TOKEN.

Texto Descripción generada automáticamente

v9b es la dirección de inicio del Output Buffer donde se copió el contenido del resultado de SYSTEM EPROCESS & 0xFFFFFFFFFFFFFFF000.

A eso le suma v14, que son los últimos 3 bytes de SYSTEM EPROCESS, y luego suma 0x4b8, que es el offset de Token para esta versión de Windows 11; luego encuentra el contenido de esa dirección que tendrá almacenado el valor del System Token.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Texto Descripción generada automáticamente

Validación del token

Texto Descripción generada automáticamente con confianza baja

Recuerde que los últimos 4 bits se cambiaron, no es significativo, por lo que el valor aún coincide.

Sobrescribir el token de nuestro proceso con el del sistema

En la segunda llamada, el valor de Flag es 1 ya que se incrementó al final de la primera llamada.

Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente

Allí vemos el orden en que se almacenan los valores.

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

La dirección 0xFFFFFFFF con el valor que acabamos de encontrar del System Process Token.

Interfaz de usuario gráfica, Texto Descripción generada automáticamente

Texto Descripción generada automáticamente

Y en el HeapSpray está el valor de la dirección del Token de mi proceso al que le resto 8. Ese valor más ocho se usará como objetivo, recuerde que se escribe en la dirección apuntada por RAX+8.

Graphical user interface, text Description automatically generated

Texto Descripción generada automáticamente

En la dirección de memoria que comienza en 0x5000000.

Interfaz de usuario gráfica Descripción generada automáticamente

También vemos que usa el nombre de otro contenedor, ya que el anterior está siendo usado por el proceso del sistema y no se puede abrir ni eliminar nuevamente.

Luego, el error se desencadena por segunda vez de la misma manera que en el primer intento.

Vuelve a CLFS!ClfsEarlierLsn().

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

estableciendo RDX en 0xFFFFFFFF.

Una captura de pantalla de un celular Descripción generada automáticamente

Luego va a nt!SeSetAccessStateGenericMapping().

Texto Descripción generada automáticamente

Lee la dirección del Token de mi proceso menos 8, donde va a escribir.

Texto Descripción generada automáticamente

Luego lee el SYSTEM TOKEN.

Texto Descripción generada automáticamente

Y escribe en la dirección del Token de mi proceso (suma 8), el System Token.

Y de esta manera, mi proceso tiene el System Token.

Texto Descripción generada automáticamente

Una vez que se escribe el token, iniciamos un proceso para verificar los privilegios; en este caso, lanzamos Notepad.exe.

Ejecución del proceso como sistema:

Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Aplicación, Word Descripción generada automáticamente

Texto Descripción generada automáticamente con confianza baja

Recuerde que este PoC solo funciona en Windows 11; en Windows 10 producirá un BSOD, por lo que deberá hacer algunas modificaciones para que funcione correctamente. Esto no se explica en esta publicación de blog.

Ingeniería inversa del parche:

Análisis de las estructuras

Las estructuras y la mayor parte de la documentación sobre el formato de archivo CLFS las hemos tomado del excelente trabajo de IONESCU sobre CLFS Internals.

Podemos ver que se ha agregado una comprobación en la función ClfsBaseFilePersisted::LoadContainerQ.

Texto Descripción generada automáticamente con confianza media

Los valores que realizan una suma pertenecen a la estructura _CLFS_BASE_RECORD_HEADER.

Escala de tiempo Descripción generada automáticamente con confianza media

Tenga en cuenta que el Base Block comienza en el offset 0x800 del archivo y termina en el offset 0x71FF, correspondiendo los primeros 0x70 bytes al Log Block Header.

Como buena práctica, podemos añadir la estructura _CLF_LOG_BLOCK_HEADER en 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;

};

Luego tenemos el Base Record Header (_CLFS_BASE_RECORD_HEADER) que comienza en el offset 0x870 desde el inicio del archivo y tiene una longitud de 0x1338 bytes.

Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente

Si quieres importarlo a IDA, antes debes agregar los siguientes tipos y estructuras faltantes

typedef GUID CLFS_LOG_ID;
typedef UCHAR CLFS_LOG_STATE;

struct _CLFS_METADATA_RECORD_HEADER

{

ULONGLONG ullDumpCount;

};

Ahora está listo para ser añadido:

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

Después de incluir las estructuras, notamos que realiza una suma entre el cbSymbolZone y la dirección donde termina el _CLFS_BASE_RECORD_HEADER. (inicio + 1338h)Texto, Aplicación Descripción generada automáticamente

Recuerda que cbSymbolZone se modificó en el archivo de registro manipulado de 0x000000F8 a 0x0001114B.

(offset 0x1b98 del archivo)

0x800(offset del inicio del Base Block) + 0x70 (logBlockHeader) + 0x1328 (cbsymbolZone)

0x800+0x70+0x1328 = 0x1b98

cbSymbolZone manipulado en el archivo MyLog.blf:

Tabla Descripción generada automáticamente

Imagen que contiene Texto Descripción generada automáticamente

Como el parche está en la función CClfsBaseFilePersisted::LoadContainerQ, tenemos que echar un vistazo al objeto CClfsBaseFilePersisted.

Estableciendo un punto de interrupción en CLFS!CClfsBaseFilePersisted::LoadContainerQ y cuando se llama a CreateLogFile con el identificador del archivo manipulado, se detendrá.

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Llama a la función CClfsBaseFile::GetBaseLogRecord para obtener la dirección del Base Log Record (_CLFS_BASE_RECORD_HEADER)

Graphical user interface, application, timeline Description automatically generated

RAX apuntará a la dirección de _CLFS_BASE_RECORD_HEADER

Interfaz de usuario gráfica, Texto, Aplicación, Correo electrónico Descripción generada automáticamente

Observa la estructura _CLFS_BASE_RECORD_HEADER en memoria y el campo cbSymbolZone 0x1328

bytes adelante

Imagen que contiene Texto Descripción generada automáticamente

Texto Descripción generada automáticamente

r14 almacena la estructura correspondiente al “this”, que es CClfsBaseFilePersisted ya que es el this de la función CClfsBaseFilePersisted::LoadContainerQ.

Interfaz de usuario gráfica, Aplicación Descripción generada automáticamente

La estructura CClfsBaseFilePersisted en memoria:

Texto Descripción generada automáticamente

Entonces, creemos una estructura con una longitud de 0x21c0 para completar sus campos mientras la invertimos (es una estructura no documentada) la llamaremos struct_CClfsBaseFilePersisted

Tabla Descripción generada automáticamente con confianza media

Dentro de la función CClfsBaseFile::GetBaseLogRecord() obtiene el puntero a _CLFS_BASE_RECORD_HEADER. y sabemos que el "this" en esa función es la estructura: struct_CClfsBaseFilePersisted.

Escala de tiempo Descripción generada automáticamente con confianza media

Lee dos campos (offset 0x28 y 0x30)

Interfaz de usuario gráfica, Aplicación, Tabla Descripción generada automáticamente

El campo 0x28 es una palabra y tiene el valor 6, así que cambiamos el tipo a word en la estructura.

Texto Descripción generada automáticamente

Texto Descripción generada automáticamente con confianza media

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Por ahora, lo renombramos como constante 6 (const_6)

Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Según la documentación, 6 sería el número de bloques CLFS_METADATA_BLOCK_COUNT. El campo podría referirse a este valor.

Y ese puntero está en el offset 0x30.

Tabla Descripción generada automáticamente

Observa que el tamaño mostrado allí incluye el encabezado con una longitud de 0x10

Texto Descripción generada automáticamente

Forma Descripción generada automáticamente con confianza media

Cuando se llama a la función ExAllocatePoolWithTag, se solicitan unos pocos bytes, pero el encabezado no está incluido, por lo tanto, se solicitarán 0x90 bytes (0xa0 – 0x10) en la llamada.

Buscando por texto +30h], las instrucciones que escriben en el offset 0x30 encontramos una larga lista, pero filtrando la lista por el tipo de objeto CClfsBaseFilePersisted nos quedamos con pocos resultados e inmediatamente encontramos dónde se asigna ese tamaño, y la misma etiqueta. (Consejo: los nombres de funciones Create e Initialize son siempre los primeros a buscar)

Texto Descripción generada automáticamente con confianza baja

Interfaz de usuario gráfica, Texto, Aplicación Descripción generada automáticamente

Como aún no conocemos el nombre, lo pondremos pool_0x90, que es otra estructura no documentada, y crearemos una estructura de ese tamaño.

Texto Descripción generada automáticamente

Interfaz de usuario gráfica, Tabla Descripción generada automáticamente

El pool_0x90 en memoria tiene otro puntero en su propio offset 0x30.

Aplicación, Tabla Descripción generada automáticamente con confianza media

Este otro puntero apunta al bloque base en el archivo (el bloque base comienza en el offset 0x800)

Forma Descripción generada automáticamente

A picture containing calendar Description automatically generated

Imagen tomada del blogpost de Zscaler:

Graphical user interface, application, email Description automatically generated

La asignación es enorme, porque contiene todo el bloque base.

Text Description automatically generated

Graphical user interface, text, application Description automatically generated

Entonces, crearemos una nueva estructura de tamaño 0x7a00 y la llamaremos BASE_BLOCK

Graphical user interface, text, application Description automatically generated

Los primeros 70 bytes ya sabemos que corresponden a _CLFS_LOG_BLOCK_HEADER y los siguientes 0x1338 a _CLFS_BASE_RECORD_HEADER.

Text Description automatically generated

Entonces, sumando el inicio del Base Block con el offset al siguiente registro (que es 0x70), obtenemos el _CLFS_BASE_RECORD_HEADER

Application Description automatically generated with low confidence

El _CLFS_BASE_RECORD_HEADER en memoria.

Calendar Description automatically generated

Mirando otros métodos del mismo objeto CClfsBaseFilePersisted, en CClfsBaseFilePersisted::AddContainer se obtiene con CClfsBaseFile::GetBaseLogRecord también la dirección de _CLFS_BASE_RECORD_HEADER.

Text Description automatically generated

A continuación, llama a CClfsBaseFile::OffsetToAddr usando cbOffset, obtiene la dirección de _CLFS_CONTAINER_CONTEXT, y almacena cboffset en el array rgbcontainers que está en el offset 0x328 de _CLFS_BASE_RECORD_HEADER.

Graphical user interface, application Description automatically generated

La función CClfsBaseFile::OffsetToAddr se utiliza para encontrar direcciones de estructuras a partir de un offset

Graphical user interface, text, application Description automatically generated

En este punto, el offset del contenedor que se almacenará en 0x328 sigue siendo 0, porque aún no hemos agregado un contenedor.

Background pattern Description automatically generated with low confidence

el PoC llama a CreateLogFile dos veces, la primera vez con el archivo malformado MyLog.blf y la segunda vez con el archivo normal MyLogxxx.blf, por lo que debemos detener la depuración dos veces en todos los lugares anteriores y tomar nota en un bloc de notas de las direcciones de las estructuras anteriores para ambos archivos.

Text Description automatically generated

Avancemos un poco hasta CLFS!CClfsLogFcbPhysical::AllocContainer estableciendo un punto de interrupción allí y ejecutando hasta allí.

Cuando se alcanza AddLogContainer() en el POC, nos detenemos en el punto de interrupción.

A picture containing application Description automatically generated

Establezcamos también un punto de interrupción en CClfsBaseFilePersisted::AddContainer+176 donde vimos antes que encontrará el offset y el puntero a la estructura _CLFS_CONTAINER_CONTEXT.

A screenshot of a computer Description automatically generated with medium confidence

Graphical user interface, text, application Description automatically generated

Cuando el depurador se detiene, podemos ver que el offset es 0x1468.

Text Description automatically generated

En RAX se devolverá la dirección de la estructura _CLFS_CONTAINER_CONTEXT.

Graphical user interface, text, application, table Description automatically generated

la estructura todavía está vacía porque aún no se ha agregado el contenedor.

Text Description automatically generated

Observa que el valor SignatureOffset=0x50 que escribimos en el offset 0x868 del archivo malformado, restando el 0x800 del inicio del bloque base, estará en la estructura _CLFS_LOG_BLOCK_HEADER en el offset 0x68.

Graphical user interface, text, application Description automatically generated

Text, letter Description automatically generated

Cuando el PoC llama a la función AddLogContainer() usando el archivo malformado, en el offset 0x68 de _CLFS_LOG_BLOCK_HEADER, en lugar del valor 0x50 que escribimos allí, actualmente hay un 0xFFFF0050 en memoria.

A picture containing text Description automatically generated

En algún momento, ese valor fue alterado por el programa; para ver cuándo ocurrió, en la siguiente ejecución estableceremos un punto de interrupción de memoria en escritura.

El offset se almacena en r15 + 0x328 (r15 apunta a la estructura _CLFS_BASE_RECORD_HEADER)

Text Description automatically generated with medium confidence

Graphical user interface Description automatically generated with low confidence

RBX almacena el offset 0x1468.

A picture containing calendar Description automatically generated

Entonces, en la dirección del Base Block + 0x70 + el offset 0x1468 que encontramos, estará la dirección del contenedor CLFS_CONTAINER_CONTEXT.

Text Description automatically generated

En la estructura CLFS_CONTAINER_CONTEXT en el offset 0x18 estará el puntero pContainer que se almacenará allí; podemos establecer un punto de interrupción en escritura y ver cuándo se escribe.

Text Description automatically generated

A picture containing text Description automatically generated

Este es el puntero que debemos corromper, ya que en la función donde está la vulnerabilidad, primero lee el CLFS_CONTAINER_CONTEXT, luego lo mueve a r15 y a continuación lee el valor de r15+18, que es este puntero que acabamos de establecer el punto de interrupción en escritura.

Graphical user interface, application, table Description automatically generated

Graphical user interface, application, Word Description automatically generated

almacena el pContainer en el offset 0x1c0 de la estructura struct_CClfsBaseFilePersisted.

Text, application, whiteboard Description automatically generated

Después de varias veces que se detiene, llegamos al momento en que se corrompe. La parte superior de la dirección del puntero ha cambiado de FFs a cero.

Calendar Description automatically generated

Esto sucede cuando se llama al segundo AddLogContainer() del archivo malformado, el puntero del MyLogxxx anterior se corrompe.

El problema ocurre porque el SignaturesOffset, que debería ser 0x50, ahora es 0xFFFF0050, lo que permite escribir fuera de los límites en el memset que sigue.

Graphical user interface, text, application Description automatically generated

Graphical user interface, text Description automatically generated

Corrompiendo el puntero "pContainer":

La función memset() va a corromper la estructura _CLFS_CONTAINER_CONTEXT que está debajo; esta estructura corresponde al archivo MyLogxxx, ya que cuando se crearon, se ubicaron a 0x11000 bytes de distancia entre sí.

De esta manera, calcula exactamente dónde escribir en la siguiente estructura y pone a cero la parte superior del puntero, por lo que apunta al heap del usuario donde se creó el HeapSpray.

La estructura del bloque base del archivo malformado está justo 0x11000 antes que la del archivo MyLogxxx.

Malformado:

Shape Description automatically generated

MyLogxxx

A picture containing text Description automatically generated

RCX es más pequeño que RDX ya que se sumó 0xFFFF0050, en lugar de 0x50 como debería ser.

Graphical user interface, text, application Description automatically generated

y llegamos a la función memset(), para establecer la cantidad de 0xb0 bytes con ceros, con RCX apuntando a la estructura CLFS_CONTAINER_CONTEXT del archivo MyLogxxx, específicamente a los cinco bytes altos de pContainer.

Text Description automatically generated

Este puntero será corrompido sobrescribiendo los primeros bytes:

Text Description automatically generated

quedando apuntando a una dirección de memoria previamente controlada por nosotros mediante HeapSpray

Text Description automatically generated

A picture containing text Description automatically generated

Luego, se cerrará el identificador del archivo MyLogxxx, y se llega a CClfsBaseFilePersisted::RemoveContainer, la vulnerabilidad finalmente se desencadena.

Text, application Description automatically generated with medium confidence

Revisando el Parche

Ahora que tenemos más información, notamos que aquí lee el Base_Block.LOG_BLOCK_HEADER.SignaturesOffset y el Base_Block. .LOG_BLOCK_HEADER.TotalSectorCount

En la primera parte del parche, ese SignaturesOffset no debe ser mayor que 0x7a00; en el nuestro originalmente era 0x50, si llegaba con un valor mayor que 0x7a00 nos expulsaría.

A picture containing diagram Description automatically generated

Ejecutando el PoC en la máquina parcheada, compara 0x50 con 0x7a00 y como es menor continúa.

Text Description automatically generated

En el siguiente bloque, se suma el cbSymbolZone malformado al valor de la dirección final de _CLFS_BASE_RECORD_HEADER y esta suma se almacena en result_1.

Text Description automatically generated

Luego, se suma la dirección del Base_Block con el valor de SignatureOffset, que en un archivo normal es 0x7980.

Text Description automatically generated

La dirección máxima del base_block es 0x7a00, ahora la SymbolZone está permitida hasta 0x80 antes del límite.

Lo almacenará en result_2, es decir, ese sería el límite máximo para la SymbolZone dentro del base block, luego compara ambos resultados; si el primero es mayor que el segundo, significa que se salió de los límites.

Text Description automatically generated

Text Description automatically generated

Obviamente el primer miembro será mayor que el segundo y no continuará, ya que la primera suma de cbSymbolZone + dirección final de _CLFS_BASE_RECORD_HEADER excede el límite (que es result_2) y conduce a un "fuera de límites".

Graphical user interface, text, application Description automatically generated

Corrompiendo el SignatureOffset

Lo último que tendríamos que averiguar es dónde el valor SignatureOffset de 0x50 se convierte en 0xFFFF0050.Así que, empecemos de nuevo, reiniciemos y paremos en CLFS!CClfsBaseFilePersisted::LoadContainerQ donde el valor aún no ha sido modificado en memoria y sigue siendo 0x50.

Establezca un punto de interrupción de acceso en el desplazamiento 0x68 en SignatureOffset.

Calendar Description automatically generated

Y después de varias paradas, detectamos el momento adecuado cuando modifica el valor, en ClfsEncodeBlockPrivate.

Graphical user interface, text Description automatically generated

Esta función no está parcheada, por lo que podría ser un comportamiento causado por el bajo valor de 0x50 y el resto de valores siendo manipulados.

Entre los valores manipulados, podemos ver el valor ccoffsetArray cuyo nombre en la estructura _CLFS_BASE_RECORD_HEADER es rgClients y representa el array de desplazamientos que apuntan al objeto Client Context.

El campo rgClients se encuentra en el desplazamiento 0x138 (0x9a8-0x800-0x70) de la estructura _CLFS_BASE_RECORD_HEADER.

Graphical user interface, table Description automatically generated

Text Description automatically generated with medium confidence

En el PoC, este valor está malformado para apuntar a un objeto de contexto de cliente falso, llamado FakeClientContextText, whiteboard Description automatically generated

A screenshot of a computer Description automatically generated with medium confidence

Esta es la estructura Client Context _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;

};

};

El valor eState está en el desplazamiento 0x78 desde el inicio de la estructura, en el archivo manipulado 0x23a0+0x78.

Text Description automatically generated with medium confidence

Chart, scatter chart Description automatically generated

Este valor muestra el estado del registro.

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;

este valor se establece en CLFS_LOG_STATE CLFS_LOG_SHUTDOWN =0x20

El otro valor malformado es fAttributes que corresponde al conjunto de banderas FILE_ATTRIBUTE asociadas con el archivo de registro base (como System y Hidden).

Graphical user interface Description automatically generated with low confidence

Text, letter Description automatically generated

Dado que el campo comienza un byte antes en 0xa y abarca dos bytes, el valor de fAttributes es 0x100.

A picture containing table Description automatically generated

Graphical user interface, text, application Description automatically generated

Finalmente, está el valor blocknameoffset que apunta al desplazamiento 0x1bb8, es decir, sumando 0x78 y 0x800 apunta al desplazamiento 0x2428 del archivo.

Text Description automatically generated

Text, letter Description automatically generated

Nótese que el desplazamiento hacia el Client Context es 0x1b30

Table Description automatically generated

Por lo tanto, el Client Context está en el desplazamiento 0x23a0.

Text Description automatically generated with medium confidence

Table Description automatically generated

Y justo 0x10 antes, está el valor correspondiente a blocknameoffset.

Text, letter Description automatically generated

Table Description automatically generated with low confidence

Que apuntaría a la cadena con el nombre

el último es blockattributeoffset que está 0xC antes del Client Context en 0x2394.

Table Description automatically generated

Estos dos últimos valores pertenecen a una estructura anterior al Client Context de 0x30 bytes de longitud, llamada _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

Text Description automatically generated

están a 0x20 y 0x24 bytes del inicio de la estructura _CLFSHASHSYM, por lo que en la estructura _CLFSHASHSYM el valor llamado blockNameOffset en el POC es el campo cbSymName y el blockAttributteoffset es el campo cbOffset.

A picture containing text Description automatically generated

Text Description automatically generated with medium confidence

Esos son los valores malformados, ahora necesitamos ver cómo afectan para cambiar nuestro SignaturesOffset del valor 0x50 a 0xFFFF0050.

Echemos un vistazo a la función CClfsBaseFile::AcquireClientContext(), que debería devolver el contexto del cliente.

Graphical user interface, text, application, email Description automatically generated

llama a CClfsBaseFile::GetSymbol con el cuarto argumento que será _CLFS_CLIENT_CONTEXT ** donde almacenará el puntero al Client Context.

Graphical user interface, text, application Description automatically generated

Dentro de la función CClfsBaseFile::GetSymbol pasamos el desplazamiento ccoffsetArray malformado a CClfsBaseFile::OffsetToAddr y obtenemos la dirección del contexto del cliente, establezcamos un punto de interrupción allí para que se detenga al llamar al archivo creado con CreatelogFile .

Graphical user interface, application Description automatically generated with medium confidence

Ahí está detenido con el argumento manipulado ccoffsetArray.

Graphical user interface, application Description automatically generated

Table Description automatically generated

La función CClfsBaseFile::OffsetToAddr devuelve el falso Client Context

A picture containing graphical user interface Description automatically generated

Y comprueba que el valor de cbOffset no sea cero ya que 0xC se encuentra antes de la estructura _CLFS_CLIENT_CONTEXT que está en RAX.

A screenshot of a computer Description automatically generated

A picture containing text Description automatically generated

Luego compara cbOffset con ccoffsetArray (que está en RSI), deben ser iguales, de lo contrario obtendremos un error.

A screenshot of a computer Description automatically generated

También comprueba que cbSymName sea igual a cbOffset+0x88, si no, también obtendremos un error.

Graphical user interface, text, application Description automatically generated with medium confidence

Y finalmente, compara el byte cidClient con cero

A picture containing diagram Description automatically generated

Si todas esas comprobaciones son exitosas, se guardará el client context.

A screenshot of a computer Description automatically generated

La salida de la función r14 apunta al Client Context

Graphical user interface, text, application Description automatically generated

Al salir de CClfsLogFcbPhysical::Initialize tendremos la dirección de CLFS_CLIENT_CONTEXT.

Text Description automatically generated

Ahora lee el valor de fAttributes (0x100)

Graphical user interface, text, application Description automatically generated

esta función pertenece a la clase CClfsLogFcbPhysical

Text Description automatically generated

Graphical user interface, text, application Description automatically generated

Que fue asignada aquí, y su tamaño es 0x15d0 y su etiqueta es “ClfC”

Text Description automatically generated with medium confidence

Vamos a crear una estructura para almacenar lo que estamos invirtiendo, la llamaremos: struct_CClfsLogFcbPhysical.

A picture containing table Description automatically generated

Nótese que en 0x2b0 guarda la dirección de la estructura CClfsBaseFilePersisted.

A picture containing application Description automatically generated

Después de guardar muchos valores en la estructura, va a una parte importante, prueba el eState con 0x20.

Graphical user interface, text, application Description automatically generated

Graphical user interface, text, application, table Description automatically generated

Dado que el valor manipulado era 0x20, la prueba devolverá 1.

Table Description automatically generated

Graphical user interface, text, application Description automatically generated

Vemos que en el constructor en la vtable está

Text Description automatically generated

Comprobará si el archivo es multiplexed.

Graphical user interface, application Description automatically generated

Entonces, va por el camino deseado, alcanzando CClfsLogFcbPhysical::ResetLog.

Graphical user interface, application Description automatically generated

Text, application, table Description automatically generated with medium confidence

Varios campos se inicializan a cero excepto uno que se inicializa a 0xFFFFFFFF00000000.

Graphical user interface Description automatically generated with low confidence

Aquí recupera el Client Context

Graphical user interface, application Description automatically generated with medium confidence

almacena el valor 0xFFFFFFFF00000000.

Graphical user interface, application Description automatically generated

Graphical user interface, application Description automatically generated

A picture containing calendar Description automatically generated

Escribe 0xFFFFFFFF en el desplazamiento 0x5c que es la parte alta de CLFS_LSN lsnRestart.ullOffset

Text Description automatically generated with medium confidence

Graphical user interface, text, letter Description automatically generated

Ahora ejecutamos la función ClfsEncodeBlockPrivate(), que es la responsable de sobrescribir el 0x50 con 0xFFFF0050 como hemos visto antes.

Ahí lee el valor de SignatureOffset = 0x50 que sigue siendo como lo pusimos en el archivo malformado y lo suma al inicio de CLFS_LOG_BLOCK_HEADER.

Text Description automatically generated

esto es un bucle que escribe 2 bytes, como el SignatureOffset en lugar de apuntar a un valor correcto que en un archivo normal es un valor alto, por ejemplo 0x3f8 que hace que escriba más adelante, aquí escribirá en el mismo CLFS_LOG_BLOCK_HEADER

La idea es cambiar el destino de escritura para intentar corromper el valor SignatureOffset.

Archivo Normal

Table Description automatically generated

En este punto, comenzará a iterar y escribir dos bytes.

Graphical user interface, application Description automatically generated

El contador debe alcanzar el valor 0x3d para salir del bucle.

Graphical user interface, application Description automatically generated

RCX está aumentando desde 0x200, ya estamos en el tercer ciclo, y su valor es 0x600

Graphical user interface, text, application Description automatically generated

en la iteración 0xe, RCX es 0x1a00

Graphical user interface, text Description automatically generated

A picture containing calendar Description automatically generated

Ese fue donde había escrito el 0xFFFFFFFF000000.

Graphical user interface, text, application Description automatically generated

Table Description automatically generated with medium confidence

Está leyendo los últimos dos bytes FFFF

Text Description automatically generated

Y los copiará en R8

A picture containing calendar Description automatically generated

A picture containing calendar Description automatically generated

Como hemos visto, este valor es crítico ya que permite omitir la comprobación y escribir fuera de los límites para corromper el puntero pContainer del archivo que sigue al memset() y escribir ceros en la parte superior y dejarlo apuntando a nuestra memoria controlada (HeapSpray).

En CClfsBaseFilePersisted::AllocSymbol que la misma suma que va a obtener el destino del memset que es cbSymbolZone + dirección final de CLFS_BASE_RECORD_HEADER lo compara antes contra Base_block + 0xFFFF0050, por lo que tiene valores corrompidos en ambos lados de la ecuación.

CbSymbolZone= 0x1114B

Es el valor malformado que sumado a la dirección final de CLFS_BASE_RECORD_HEADER hará que escriba fuera de los límites y el otro miembro de la comparación que debería ser la dirección del Base Block + SignatureOffset, permanece SignatureOffset =0xFFFF0050 lo que permite que esta comprobación pase y escriba fuera de los límites en el memset() y ponga a cero la parte superior del puntero que permanecerá apuntando a nuestro HeapSpray.

Graphical user interface, application, table Description automatically generated

Dado que RCX es menor que RDX.

Graphical user interface, application Description automatically generated

Como hemos visto antes. (Los valores pueden diferir porque pertenecen a una ejecución anterior)

Corromperá el puntero, estableciendo los bytes más altos a 0

Table Description automatically generated

Dejándolo apuntando a un área de memoria que controlamos mediante HeapSpray

A screenshot of a computer Description automatically generated with low confidence

Table Description automatically generated

Entonces, cuando se activa la vulnerabilidad, llegamos a CClfsBaseFilePersisted::RemoveContainer

A picture containing graphical user interface Description automatically generated

Allí estará el puntero ya corrompido y se puede explotar como vimos anteriormente.

Graphical user interface, application Description automatically generated

En este punto tenemos el error explotado, lleva a controlar las funciones que permiten leer el token SYSTEM y escribir en nuestro propio proceso para lograr la escalada de privilegios local.

Esperamos que les sea útil, si tienen alguna duda pueden contactarnos en [email protected] y [email protected]

¡Disfruten!

Descargar herramienta