En noviembre de 2025, una vulnerabilidad clasificada como crítica por Microsoft y documentada inicialmente por Zscaler ThreatLabz sacudió la confianza en uno de los formatos de imagen más ubicuos de la web: el JPG. La CVE‑2025‑50165, alojada en la biblioteca WindowsCodecs.dll, prometía la capacidad de ejecutar código remoto con solo abrir un archivo JPG especialmente manipulado. Sin embargo, tras un análisis forense de causa raíz realizado por investigadores de ESET, el escenario real de explotación resultó ser considerablemente más complejo de lo que sugería la descripción inicial. Este artículo desglosa el hallazgo, el mecanismo del fallo, las condiciones necesarias para desencadenarlo y una evaluación ponderada de su verdadero riesgo para los usuarios de Windows.
El descubrimiento: una vulnerabilidad crítica en el componente de imágenes de Windows
El 20 de noviembre de 2025, Zscaler ThreatLabz publicó un informe detallando CVE‑2025‑50165, una vulnerabilidad de ejecución remota de código presente en WindowsCodecs.dllcodecodecodecodecode, la biblioteca principal de Windows para el manejo de formatos de imagen comunes como JPG, PNG, GIF y BMP. Microsoft, en su propia descripción, señaló que el fallo se originaba en la desreferencia de un puntero a función no inicializado dentro de la función jpeg_finish_compresscodecodecodecodecode, perteneciente a la biblioteca libjpeg‑turbo (versión 3.0.2) integrada en el sistema. La gravedad fue calificada como crítica, pero la probabilidad de explotación se consideró baja. Esta aparente contradicción despertó el interés de los investigadores de ESET, quienes se propusieron aclarar las condiciones exactas del fallo.
El equipo de ESET descargó y analizó la versión vulnerable de WindowsCodecs.dllcodecodecodecodecode (10.0.26100.4768, SHA‑1: 5887D96565749067564BABCD3DC5D107AB6666BD) y la comparó con la primera versión parcheada (10.0.26100.4946, SHA‑1: 4EC1DC0431432BC318E78C520387911EC44F84FC). Utilizando la herramienta de diffing binario Diaphora, identificaron los cambios clave que permitieron comprender la causa raíz del problema.
El punto de fallo: por qué un JPG de 12 bits desencadena el crash
La función jpeg_finish_compresscodecodecodecodecode es llamada cuando una secuencia de imagen JPG se comprime o se vuelve a codificar. Dentro de esta función, el crash ocurre en la dirección jpeg_finish_compress+0xCCcodecodecodecodecode, correspondiente a la línea donde se desreferencia un puntero a función almacenado en el campo compress_data_12codecodecodecodecode de una estructura interna. Para que este camino de código se ejecute, el miembro data_precisioncodecodecodecodecode de la estructura jpeg_compress_structcodecodecodecodecode debe estar establecido en 12. En términos simples, WindowsCodecs.dllcodecodecodecodecode falla cuando intenta codificar una imagen JPG con una profundidad de color de 12 bits (bit depth), en lugar de los 8 bits convencionales.
El análisis con Diaphora reveló que la función jinit_c_rawtranscode_coef_controller_turbocodecodecodecodecode — que había sido inlineada en la versión vulnerable — no inicializaba los punteros compress_data_12codecodecodecodecode ni compress_data_16codecodecodecodecode. En la versión parcheada, ambos punteros se asignan a una función stub llamada rawtranscode_compress_output_16codecodecodecodecode, que simplemente delega en una función genérica rawtranscode_compress_outputcodecodecodecodecode. Esto indica que no existe una implementación específica para manejar imágenes JPG de 12 o 16 bits; el parche se limita a evitar la desreferencia de un puntero sin inicializar.
Reproduciendo el crash: imágenes de 12 y 16 bits confirman el fallo
ESET reprodujo el crash utilizando el ejemplo de re‑codificación JPG proporcionado por Microsoft en su documentación de WIC (Windows Imaging Component). Alimentando ese programa con una imagen de prueba de 12 bits disponible en el repositorio de libjpeg‑turbo (testorig12.jpgcodecodecodecodecode), la aplicación se bloqueó exactamente en la misma dirección señalada por Zscaler. La memoria apuntada por el puntero no inicializado contenía el valor repetido 0xBAADF00Dcodecodecodecodecode, una marca mágica que el CRT de Windows asigna a memoria recién asignada por HeapAlloccodecodecodecodecode para indicar que no ha sido inicializada.
La misma prueba con una imagen JPG de 16 bits también provocó un crash, esta vez al desreferenciar el puntero compress_data_16codecodecodecodecode. Esto confirmó que ambas rutas de código — para 12 y 16 bits — eran vulnerables.
Condiciones exactas para desencadenar el fallo
La pregunta clave que se hicieron los investigadores fue: ¿bajo qué circunstancias reales puede un usuario o un proceso llamar a jpeg_finish_compresscodecodecodecodecode con un JPG de alta precisión? La respuesta no es trivial. La función vulnerable solo se invoca cuando una imagen se vuelve a codificar (re‑encode), no durante la simple decodificación y visualización. Esto ocurre, por ejemplo, al guardar una imagen editada, al crear una miniatura (thumbnail) o al realizar operaciones de metadatos que requieren re‑compresión. La aplicación Microsoft Photos, por ejemplo, llama a jpeg_finish_compresscodecodecodecodecode al generar miniaturas, como demostró ESET mediante un punto de interrupción (breakpoint).
Por lo tanto, para que un sistema sea vulnerable, deben cumplirse tres condiciones:
- Que utilice una versión afectada de
WindowsCodecs.dllcodecodecodecodecode. - Que la aplicación huésped no falle ni aborte al decodificar un JPG de 12 o 16 bits antes de intentar re‑codificarlo.
- Que la aplicación permita (voluntaria o automáticamente) la re‑codificación de la imagen.
Esta cadena de eventos reduce drásticamente la superficie de ataque en comparación con una vulnerabilidad que se dispara solo con la apertura del archivo.
¿Estaba también vulnerable el código fuente de libjpeg‑turbo?
ESET examinó el repositorio de libjpeg‑turbo y encontró que el commit e0e18decodecodecodecodecode, fechado el 18 de diciembre de 2024 e incluido en la versión 3.1.1, resolvía exactamente este tipo de problemas: las estructuras ahora se inicializan con ceros y se verifica que los punteros no sean nulos antes de usarlos. Sin embargo, ninguna de estas medidas de seguridad estaba presente en la versión 3.0.2 incorporada en WindowsCodecs.dllcodecodecodecodecode. El mensaje del commit también advierte que el fallo puede ocurrir tanto en la compresión como en la descompresión, pero solo si una aplicación modifica el campo data_precisioncodecodecodecodecode después de llamar a jpeg_start_compresscodecodecodecodecode o jpeg_start_decompresscodecodecodecodecode. La API de Windows Imaging Component no expone este comportamiento, lo que hace improbable que una aplicación legítima pueda alterar ese estado interno.
Evaluación real de la explotabilidad: por qué Microsoft la calificó como poco probable
El análisis de ESET concluye que la explotabilidad de CVE‑2025‑50165 es, efectivamente, reducida. Para que un atacante logre ejecución remota de código, necesitaría:
- Provocar que una aplicación vulnerable re‑codifique un JPG de 12 o 16 bits.
- Tener capacidad de filtrar direcciones de memoria (address leak) para conocer la disposición del heap.
- Ejercer un control fino sobre la asignación de memoria para colocar un payload en la ubicación donde se espera el puntero a función.
Estas condiciones hacen que la explotación en escenarios reales sea compleja, aunque no imposible. ESET coincide con la evaluación de Microsoft: la vulnerabilidad es crítica en teoría (podría permitir ejecución de código) pero su explotabilidad es poco probable debido a los requisitos previos. No obstante, advierte que aplicaciones específicas que realicen procesamiento masivo de miniaturas o re‑codificación automática de imágenes podrían ser vectores de ataque potenciales, especialmente si el atacante consigue que el usuario abra una imagen maliciosa en un explorador de archivos o visor que genere thumbnails.
Implicaciones para el ecosistema Windows: lecciones sobre el uso de bibliotecas de terceros
Más allá de la vulnerabilidad concreta, este caso subraya un problema recurrente en la seguridad del software: la integración de bibliotecas de código abierto sin mantenerlas actualizadas. WindowsCodecs.dllcodecodecodecodecode utiliza libjpeg‑turbo 3.0.2, lanzada en enero de 2024, pero el commit correctivo (3.1.1) es de diciembre de 2024 y no fue incorporado hasta el parche de noviembre de 2025. Ese desfase de casi un año dejó expuestos a los sistemas Windows.
El hallazgo también demuestra la importancia de las técnicas de patch diffing y análisis de causa raíz. ESET utilizó Diaphora para comparar las versiones binarias y localizar exactamente las funciones modificadas, lo que permitió no solo reproducir el crash sino también comprender por qué ocurría y qué condiciones eran necesarias. Este nivel de detalle es esencial para que los equipos de seguridad puedan evaluar correctamente el riesgo y priorizar la aplicación de parches.
Finalmente, el caso de CVE‑2025‑50165 refuerza la necesidad de que los desarrolladores de aplicaciones que utilizan WIC consideren qué operaciones realizan sobre imágenes de alta profundidad de bits. Aunque Windows ya ha corregido el fallo a partir de la versión 10.0.26100.4946, cualquier aplicación que implemente su propio manejo de JPG de 12 o 16 bits debería verificar que los punteros a función estén inicializados antes de usarlos, o bien limitarse a trabajar exclusivamente con el estándar de 8 bits.
En retrospectiva, la vulnerabilidad CVE‑2025‑50165 es un recordatorio de que incluso un formato tan maduro y estudiado como JPG puede albergar fallos críticos en los rincones menos transitados de su implementación. La combinación de una biblioteca desactualizada, punteros no inicializados y un camino de código poco común creó una ventana de riesgo real, aunque de explotación difícil. Los usuarios de Windows que ya han instalado las actualizaciones de seguridad de noviembre de 2025 están protegidos. Para el resto, la simple acción de abrir una imagen JPG no debería desencadenar el fallo, pero cualquier escenario que implique guardar o generar miniaturas de imágenes de alta precisión sigue siendo un vector potencial hasta que se aplique el parche.