Vulnerabilidad de heap buffer overflow en Notepad++: análisis y reproducción

Ya sabéis que Notepad++ es un conocido editor de código fuente abierto que se ejecuta en Windows y admite múltiples lenguajes de programación. Recientemente, se han descubierto varias vulnerabilidades de seguridad en este software y, entre ellas, la vulnerabilidad de desbordamiento del búfer de heap CVE-2023-40031, que tiene una puntuación de 7,8 puntos (CVSS3, puntuación total de 10 puntos).
La vulnerabilidad se encuentra en la función Utf8_16_Read::convert. Al convertir UTF-16 a UTF-8, el tamaño del heap UTF-8 convertido se calcula mal, lo que provoca que se sobrescriba el espacio de memoria fuera del búfer, lo que puede provocar la ejecución arbitraria de código.

A continuación veremos el buen análisis realizado por los chinos de Beacon Tower Laboratory en el siguiente entorno:

  • Versiones de Notepad++ afectadas: <=8.5.6
  • Entorno de pruebas:bSistema operativo: Win7 sp1.
  • Herramientas de análisis: IDA, WinDbg, OLLYDBG.

Reprodución de la vulnerabilidad:

Según el aviso publicado por investigadores de seguridad (https://securitylab.github.com/advisories/GHSL-2023-092_Notepad__/), podemos usar este script en Python para generar el archivo para la POC:

Para probarlo, instalamos la versión 8.5.2 del programa notepad++ (https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v8.5.2/npp.8.5.2.Installer.exe). 

El archivo POC generado se abrió en el programa notepad++, pero no se produjo ningún bloqueo, posiblemente porque los datos desbordados no afectaron el funcionamiento normal del programa. Como se muestra abajo:  

Debemos utilizar el componente gflags.exe en la herramienta de depuración Windbg para habilitar las opciones relacionadas con la inspección del heap, como se muestra en la siguiente imagen:

Luego volveremos a abrir el programa notepad++, usando el debugger Windbg para adjuntarlo y ejecutarlo, y finalmente abriremos el archivo poc generado en el programa notepad++. 

Veréis que la excepción se detecta inmediatamente: Se produce una excepción en el heap con la dirección 0x17F4AFF8 y un tamaño de 2 bytes. La dirección de excepción es 0x17F4AFFA, que ha excedido el rango de direcciones del heap actual y se desborda:

Al observar la pila de llamadas de funciones, encontramos que la excepción ocurrió en la función HeapFree, lo que significa que la memoria se destruyó cuando se liberó el heap. La pila de llamadas de funciones, como se muestra en la siguiente imagen:

Análisis de vulnerabilidad

Según la recurrencia anterior, se sabe que se ha producido un desbordamiento del heap, pero aún no se ha encontrado la causa del desbordamiento. Generalmente, puede buscar hacia arriba según la pila de llamadas a funciones. Sin embargo, este es un software de código abierto y podemos descargar y compilar el código fuente, por lo que combinado con la tecnología AddressSanitizer (ASan), la causa del desbordamiento se puede localizar con mayor precisión. En los informes públicos de la POC, se ha utilizado esta manera para localizar la causa del desbordamiento. El desbordamiento ocurre en la función Utf8_16_Read::convert:

El análisis preliminar muestra que la línea 162 de esta función usa new para solicitar un búfer del heap. El tamaño se calcula a través de la longitud de la línea 155. La línea 175 copia datos al búfer aplicado. El punto de desbordamiento puede estar aquí, como se muestra a continuación:


Usaremos IDA para descompilar notepad ++.exe, luego Ollydbg para depurarlo dinámicamente, abrir el archivo poc para realizar un seguimiento y analizar la función en detalle. 

Después de un período de depuración y análisis, se puede ver que Notepad++ lee el contenido de un tamaño específico del archivo cada vez. Este tamaño es 128 * 1024 + 4 = 0x20004, como se muestra en la siguiente figura:


Luego determinamos la codificación del contenido del archivo. Los primeros dos bytes del archivo poc son 0xFE y 0xFF, lo que indica que su codificación de contenido es UTF-16 big endian:


Finalmente, se entrará en la función Utf8_16_Read::convert para convertir el contenido codificado UTF-16 a codificación UTF-8. Durante la conversión, se vuelve a calcular el tamaño del contenido convertido. La fórmula de cálculo es newSize = len + len / 2 + 1. len es el tamaño del contenido antes de la conversión, es decir, el nuevo tamaño es 1 más de 3/2 veces el tamaño original byte. Al leer el archivo poc, el tamaño leído por primera vez es 0x20004:


El tamaño del nuevo búfer después del cálculo se calcula en 0x30007:


Luego se realiza la conversión de codificación y almacena el contenido convertido en un nuevo búfer sin desbordamiento:

Cuando el contenido restante del poc se lee por segunda vez, solo queda 1 byte, pero el contenido del búfer leído la última vez no se borra (punto clave 1):


Posteriormente calculamos el tamaño del nuevo búfer como 2 (punto clave 2):


Luego realizamos la conversión de codificación. En este momento, dado que solo hay un byte y UTF-16 tiene al menos 2 bytes, se leerá un byte hacia atrás en el búfer (punto clave 3).En este momento, el contenido UTF-16 es 0xFF, 0xFF. Después de la conversión, es 0xEF, 0xBF, 0xBF, un total de 3 bytes. Si el nuevo búfer excede el tamaño de 2 bytes, se produce un desbordamiento:


En resumen, cuando el tamaño del archivo poc es un número impar, al convertir UTF-16 a UTF-8, se producirá un error al calcular el tamaño del búfer después de la conversión y, al mismo tiempo, la posición final del contenido del archivo. También se tratará incorrectamente, lo que provocará un desbordamiento del búfer en el heap después de la conversión. Un byte, que sobrescribe otra porción de memoria, puede provocar la ejecución de código arbitrario.

Explotación

El desbordamiento de un byte se denomina off-by-one. Generalmente, el uno por uno se considera difícil de explotar. Una idea común es utilizar esta vulnerabilidad para modificar el tamaño de los heaps adyacentes para provocar una superposición entre las estructuras de bloques, filtrando o sobrescribiendo así otros datos de bloques, lo cual es más común en los CTFs. Si se quiere conseguir un rce directamente, se debe profundizar combinando otros métodos.

Referencias

Comentarios