A critical flaw in Windows Imaging Component

A critical flaw in Windows Imaging Component

ESET researchers examined CVE‑2025‑50165, a serious Windows vulnerability described to grant remote code execution by merely opening a specially crafted JPG file – one of the most widely used image formats. The flaw, found and documented by Zscaler ThreatLabz, piqued our interest, as Microsoft assessed its severity as critical but deemed its exploitability as less likely. Our root cause analysis allowed us to pinpoint the exact location of the faulty code and reproduce the crash. We believe that the exploitation scenario is harder than it appears to be.

Key points of this blogpost:

  • ESET researchers offer a deep dive analysis of the CVE‑2025‑50165 vulnerability, illustrated with pseudocode snippets.
  • We provide our method to reproduce the crash using a simple 12-bit or 16-bit JPG image, and an examination of the initial released patch.
  • CVE-2025-50165 is a flaw in the encoding and compressing process of a JPG image, not in its decoding.
  • Our conclusion explores and reassesses the exploitability and the attack scenario of this flaw.

Overview

On November 20th, 2025, Zscaler ThreatLabz published an article documenting the discovery of CVE‑2025‑50165, a high-impact remote code execution vulnerability present in WindowsCodecs.dll. This library is Windows’ main interface library responsible for handling most common image file formats, such as JPG, PNG, GIF, BMP, etc. Zscaler’s findings, as well as the description provided by Microsoft, reveal that the flaw stems from the dereference of an uninitialized function pointer in WindowsCodecs.dll, which is part of the Windows Imaging Component. The former managed to track down the dereference issue inside the jpeg_finish_compress function, which happens to be called when a JPG image stream is compressed and (re-)encoded. When it comes to image-handling vulnerabilities, one would first think of parsing and decoding bugs that happen as soon as the image is rendered. So this rather specific vulnerable code path left us with some questions we wanted to answer:

  • What are the exact conditions to take the vulnerable code path leading to the dereference of the uninitialized function pointer?
  • When is jpeg_finish_compress called?
  • Why is the function pointer not initialized?

Given how common JPG images are on the web, we wanted answers, so we started by investigating the code that caused the crash.

Crash site

According to the CVE‑2025‑50165 entry description, WindowsCodecs.dll versions from 10.0.26100.0 and before 10.0.26100.4946 are affected. We analyzed the vulnerable version 10.0.26100.4768 (SHA-1: 5887D96565749067564BABCD3DC5D107AB6666BD), and then performed a binary comparison with the first patched version 10.0.26100.4946 (SHA-1: 4EC1DC0431432BC318E78C520387911EC44F84FC). After downloading the corresponding symbols, we looked at the crashing function, jpeg_finish_compress. According to a version string present in WindowsCodecs.dll – libjpeg-turbo version 3.0.2 (build 20250529) – the DLL relies on a rather old implementation of the libjpeg-turbo library (released on January 24th, 2024) to handle JPG images. The publicly available repository allowed us to map most of the relevant binary code and structures to their source code equivalents. For instance, the compiled version of jpeg_finish_compress is very similar to its source code equivalent available here, as shown in Figure 1.

Figure 1. Hex-Rays IDA decompiler output of the jpeg_finish_compress function is similar to the cited source code

Based on Zscaler’s findings, the crash happens at jpeg_finish_compress+0xCC, which corresponds to line 48 in Figure 1, when dereferencing a function pointer located at offset 0x10 of an unknown structure (pub). According to libjpeg-turbo source code, this corresponds to a function pointer named compress_data_12. In order to reach this specific path, the data_precision member of jpeg_compress_struct needs to be set to 12. This corresponds to the bit depth, or in other words the number of bits used to describe colors. Essentially, WindowsCodecs.dll crashes when it tries to encode a 12‑bit precision JPG image.

Patch diffing and root cause analysis

Using Diaphora, a binary diffing tool, we performed a diff between the vulnerable version 10.0.26100.4768 and the patched version 10.0.26100.4946, as shown in Figure 2.

Figure 2. partially matched and unmatched functions between both libraries
Figure 2. Diaphora successfully highlighted the partially matched and unmatched functions between both libraries

Surprisingly, the crashing function jpeg_finish_compress mentioned in the article is not present. However, two encoding-related functions were changed: rawtransencode_master_selection and jinit_c_rawtranscode_coef_controller_turbo. The diff between the vulnerable and patched versions of rawtransencode_master_selection is shown in Figure 3.

Figure 3. Diff between vulnerable and patched versions of rawtransencode_master_selection
Figure 3. Diff between the vulnerable (left) and patched (right) versions of rawtransencode_master_selection

The only relevant difference seems to be that the function jinit_c_rawtranscode_coef_controller_turbo, which was previously inlined in the body of the function rawtransencode_master_selection, is now separated. Looking at the patched version of the jinit_c_rawtranscode_coef_controller_turbo function reveals that the previously uninitialized structure member compress_data_12 is now set to point to a function named rawtranscode_compress_output_16, as shown in Figure 4.

Figure 4. Patched version of jinit_c_rawtranscode_coef_controller_turbo
Figure 4. Patched version of jinit_c_rawtranscode_coef_controller_turbo

Note that the field compress_data_16, which was also not initialized in the vulnerable version, is also set to point to rawtranscode_compress_output_16 in the patched version. This function is simply a stub function that calls rawtranscode_compress_output, which may indicate that there’s no specific code to handle either 12-bit or 16-bit precision JPG images.

Reproducing the crash

As mentioned in Zscaler’s article, one can compile the code snippet proposed by Microsoft (https://learn.microsoft.com/en-us/windows/win32/wic/-wic-codec-jpegmetadataencoding#jpeg-re-encode-example-code) to decode and re-encode a JPG image.

Once this program is compiled, the crash can be reproduced by providing either a 12-bit or a 16-bit JPG file. Going through the samples from the libjpeg-turbo repository, a 12-bit precision sample image is available for download at https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/testimages/testorig12.jpg. Feeding this image to the re-encoding example application resulted in a crash at the exact same location mentioned in Zscaler’s article. Figure 5 shows the context of the crash during a debugging session.

Figure 5. The re-encoding example application crashes
Figure 5. The re-encoding example application crashes during the compression routine when handling a 12-bit JPG image

The repeated hex value 0xBAADF00D pointed to by the memory address is a magic value used by the C runtime (CRT) heap when a program calls HeapAlloc to allocate memory. It marks the memory as uninitialized (see https://www.nobugs.org/developer/win32/debug_crt_heap.html).

As indicated previously, both analyzed versions of WindowsCodecs.dll appear to be able to handle 16-bit precision JPG images. But when testing such images, the re-encoding application crashes when dereferencing the compress_data_16 function pointer, as observed in Figure 6.

Figure 6. The re-encoding example application crashes during the compression routine
Figure 6. The re-encoding example application crashes during the compression routine when handling a 16-bit JPG image

Having reproduced the crash, we wondered whether this specific vulnerability was also present in the source code of the libjpeg-turbo library.

Exploring the source code

Going through the commits of libjpeg-turbo revealed that similar issues were resolved on December 18th, 2024, with commit e0e18de, introducing version 3.1.1. Essentially, the commit makes sure that structures are zero-initialized and that an error is raised if a pointer is NULL. It turns out that all the zero-initializations and checks introduced by this commit are absent in the vulnerable and patched versions of WindowsCodecs.dll.

The patch message also hints at other potential vulnerable code paths and, more importantly, that crashes may also happen in the decompression process when manipulating a JPG image, as highlighted by the diff of file jdapistd.c, illustrated in Figure 7.

Figure 7. Diff of decompression routines
Figure 7. Diff of decompression routines implemented in jdapistd.c

As the commit description clearly specifies, a calling application would crash (due to the dereference of an uninitialized function pointer) only if it erroneously changes the data_precision field after calling the jpeg_start_compress or jpeg_start_decompress routines. This creates a rather specific and likely unrealistic scenario where an application using WindowsCodecs.dll would alter the state of internal structures. While such applications may exist, it does not appear that the Windows Imaging Component API allows such behavior.

Exploitability

As revealed by our root cause analysis, the core issue of CVE‑2025‑50165 resides in WindowsCodecs.dll’s handling of JPG images with a data precision value other than the conventional and standard 8-bit. The two precision-specific function pointers (compress_data_12 and compress_data_16) were uninitialized during the compression process, creating two vulnerable code paths that seem to be reachable only when (re-)encoding a JPG image. Simply opening, and therefore decoding and rendering, a specially crafted image will not trigger the vulnerability. However, the vulnerable function jpeg_finish_compress could be called if the image is saved or if a host application, such as the Microsoft Photos application, creates thumbnails of images, as shown in Figure 8.

Figure 8. The vulnerable jpeg_finish_compress function is called
Figure 8. The vulnerable jpeg_finish_compress function is called during the creation of a thumbnail for an image

In order for a program to be considered vulnerable, it needs the following characteristics:

  • makes use of a vulnerable version of WindowsCodecs.dll,
  • does not crash or abort while decoding a 12-bit or 16-bit JPG file, and
  • allows the image to be re-encoded.

Moreover, as mentioned by Zscaler researchers, an address leak and enough control over the heap are mandatory to exploit this vulnerability.

Conclusion

Although JPG is older, widely used, and perhaps the most popular digital image format in fuzz testing, vulnerabilities can still be found in some codecs. This study of CVE‑2025‑50165 also highlights the importance of keeping up with security updates when using third-party libraries.

Root cause analysis along with patch diffing proved a very powerful combination that allowed us to answer our initial questions. We found out that the bug can be triggered when WindowsCodecs.dll encodes a 12-bit or a 16-bit precision JPG stream as both precision-specific function pointers were neither initialized nor checked before being dereferenced. Additionally, we figured out that this process happens when such an image is saved or when a thumbnail is created from it.

This investigation led us to a similar conclusion as Microsoft’s regarding the exploitability of this vulnerability. Indeed, as WindowsCodecs.dll is a library, a host application would be considered vulnerable if it allows JPG images to be (re-)encoded, and exploitable only if an attacker has enough control over the application (address leak, heap manipulation). Putting it all together, it seems indeed that exploitation is unlikely.

Finally, it’s worth mentioning that, as of this writing and according to our tests, newer versions of WindowsCodecs.dll (such as 10.0.22621.6133, SHA-1: 3F3767D05E5A91184005D98427074711F68D9950) implement the different changes mentioned in libjpeg-turbo’s commit, effectively addressing the lack of initialization and function pointer verification.

A critical flaw in Windows Imaging Component



Source link