Reverse Engineering the Office 3D Component
Reverse engineering plays a critical role in uncovering and understanding software vulnerabilities, as it allows cybersecurity experts to dissect and analyze code — providing valuable insights into how potential weaknesses and security flaws can be exploited or mitigated.
Office 3D Parsing
The dynamic library MSOSPECTRE.DLL (shown in Figure 5) is responsible for parsing 3D file formats in Microsoft 365 apps. Our vulnerability research on the SketchUp file format was conducted in version 16.0.16026.20000, which was released in January 2023.
Figure 5: MSOSPECTRE.DLL is responsible for parsing 3D file formats in Microsoft 365 apps
During our analysis of MSOSPECTRE.DLL using IDA Pro, a series of functions with names prefixed with “SU” caught our attention, as depicted in Figure 6. It’s worth noting that these functions are, in fact, SketchUp C APIs sourced from the SketchUp SDK.
Figure 6: A bunch of functions with name prefixed with “SU” in IDA Pro
By combining reverse engineering MSOSPECTRE.DLL with dynamic debugging, we determined the function Spectre::Transcoder::ImporterSKP::ImportToAsset3D is responsible for parsing an SKP file in Microsoft 365 apps. Figure 7 provides a code snippet of its pseudo code.
Figure 7: The function Spectre::Transcoder::ImporterSKP::ImportToAsset3D
This function calls a sequence of SketchUp APIs and certain wrapper functions that, in turn, invoke additional SketchUp APIs, as demonstrated below.
Now, we’ll examine the implementation for each of these wrapper functions.
1. Spectre::Transcoder::ImporterSKP::CountEntities (Shown in Figure 8)
- Spectre::Transcoder::ImporterSKP::CountComponentInstance (Shown in Figure 9)
Figure 8: Spectre::Transcoder::ImporterSKP::CountEntities
Figure 9: Spectre::Transcoder::ImporterSKP::CountComponentInstance
2. Spectre::Transcoder::ImporterSKP::ExportEntities (Shown in Figure 10)
Figure 10: Spectre::Transcoder::ImporterSKP::ExportEntities
3. Spectre::Transcoder::ImporterSKP::ExportComponentInstance (Shown in Figure 11)
Figure 11: Spectre::Transcoder::ImporterSKP::ExportComponentInstance
4. Spectre::Transcoder::ImporterSKP::ExportFaces (Shown in Figure 12)
Figure 12: Spectre::Transcoder::ImporterSKP::ExportFaces
5. Spectre::Transcoder::ImporterSKP::GetMaterial (Shown in Figure 13)
Figure 13: Spectre::Transcoder::ImporterSKP::GetMaterial
6. Spectre::Transcoder::SkpUtils::GetTextureId (Shown in Figure 14)
Figure 14: Spectre::Transcoder::SkpUtils::GetTextureId
7. Spectre::Transcoder::ImporterSKP::GetTexture (Shown in Figure 15)
Figure 15: Spectre::Transcoder::ImporterSKP::GetTexture
8. Spectre::Transcoder::ImporterSKP::AddFacesGeometry (Shown in Figure 16)
Figure 16: Spectre::Transcoder::ImporterSKP::AddFacesGeometry
Throughout our reverse engineering process, we successfully analyzed all of the relevant functions involved in parsing SKP files within the Office 3D component. In particular, we discovered Microsoft leveraged a series of SketchUp C APIs to implement the functionality to parse an SKP file.
SketchUp provides an SDK, including a C API. The online C API documentation contains reference material for all functions, data structures, and enumerations in both the SketchUp C API and the SketchUp Importer/Exporter interface. This was helpful to create and refine our SketchUp fuzzing harness.
Numerous SketchUp APIs were used in the fuzzing harness. We will highlight some key functions below.
- void SUInitialize (): Initializes the slapi interface. Must be called before calling any other API functions.
- void SUTerminate (): Signals termination of the slapi interface. Must be called when done using API functions.
- enum SUResult SUModelCreateFromBufferWithStatus(SUModelRef *model, const unsigned char *buffer, size_t buffer_size, enum SUModelLoadStatus *status): Creates a model from a SketchUp skp file buffer.
Figure 17: The function SUModelCreateFromBufferWithStatus
The function SUModelCreateFromBufferWithStatus takes 4 parameters, where the 2nd and 3rd parameters are input parameters, and the 1st and 4th parameters are output parameters.
Creating a harness for fuzzing
Now, we’ll explore how to develop a harness for fuzzing the Office 3D component MSOSPECTRE.DLL. Figure 18 provides an overview of the source code of our SketchUp harness.
Figure 18: An overview of the source code for our SketchUp harness
In this code, we declared various SketchUp APIs and implemented several wrapper functions, which were derived from reverse engineering the Office 3D component.
Figure 19 provides a code snippet for our SketchUp fuzzing harness. In the main function, the first step loads MSOSPECTRE.DLL using the LoadLibrary API, which returns the base address of MSOSPECTRE.DLL in memory. Then the function SUInitialize is called to initialize the SketchUp API functions. Once initialization occurs, the function fuzzme() is called. To ensure proper cleanup, the function SUTerminate is called.
Figure 19: A code snippet of our SketchUp fuzzing harness
The function fuzzme() implements the functionality found in the method Spectre::Transcoder::ImporterSKP::ImportToAsset3D in MSOSPECTRE.DLL as shown in Figure 20.
Figure 20: The function fuzzme() in harness
With our SketchUp harness complete, the next important step is setting up a fuzzer to effectively test it as follows:
1. Gather thousands of samples of SKP files from various websites. For example:
2. To make the fuzzing process efficient, narrow down this collection to a smaller group of SKP files using winafl-cmin.py.
Note: While integrating this harness into WinAFL, we encountered a timeout issue. This might have been due to the slightly longer time it takes to parse an SKP file within this harness.
Despite the timeout challenge, we adopted a dumb file format fuzzer, which proved to be a workable solution. Ultimately, this approach led to impressive results, uncovering a total of 20 unique vulnerabilities illustrated in Figure 21 in just one month. These findings include various types of vulnerabilities such as use-after-free, heap buffer overflow, integer overflow, out-of-bounds write, type confusion, stack buffer overflow, etc.
Figure 21: Vulnerabilities discovered by ThreatLabz through SketchUp harness
SKP file format
Through reverse engineering, we also uncovered that SKP files support two distinct data types:
- MFC type
- VFF type
As shown in Figure 22, the function SketchUpModelReader::ReadModel is used to read data from an SKP file. If a VFF header is present in the SKP file, it calls SketchUpModelReaderVFF::ReadModel to handle VFF type data. Otherwise, it calls SketchUpModelReaderMFC::ReadModel to handle MFC type data.
Figure 22: The function SketchUpModelReader::ReadModel in MSOSPECTRE.DLL
We created a diagram in Figure 23 for the MFC data type structure thanks to our analysis of SketchUpModelReaderMFC::ReadModel. The structure starts with a SketchUp header, then a GUID, followed by a versionMap, and other specific data. Inside the versionMap and data sections, there are often bytes that start with FF FE FF, followed by a length field (1 byte), and then a Unicode string. After the Unicode string, there is specific data.
Figure 23: The SKP file structure of MFC data type
Next, we will explore the structure of an SKP file with the VFF data type. As illustrated in Figure 24, the structure starts with a SketchUp header, followed by a VFF header, and then a ZIP file. The VFF header contains 13 bytes of data, which includes the magic bytes ‘VFF’, followed by a length field indicating the length of the remaining VFF header, and then 4 bytes (currently unknown), and then a 4-byte checksum.
Figure 24: The SKP file structure of the VFF data type
Figure 25 shows a stack backtrace for a crash that indicates that the data type within the SKP file is the MFC type.
Figure 25: The stack backtrace of an SKP PoC file with an MFC type
The crash occurs during the parsing of a JPEG file embedded with the SKP file. What makes this finding particularly interesting is that another third-party library, FreeImage, is used to parse the image file embedded within an SKP file.
When comparing a normal SKP file to the minimized SKP PoC file, only 1 byte is mutated within a JPEG image that’s part of the ‘materials’ structure in the SKP file (as shown in Figure 26).
Figure 26: Comparison between a normal SKP file and the minimized SKP PoC file
As shown in Figure 27, the function MSOSPECTRE!FreeImageUtils::CreateBmpFromMemory parses the image file embedded within an SKP file. Figure 27 illustrates its pseudo code that calls a series of FreeImage APIs.
Figure 27: The function MSOSPECTRE!FreeImageUtils::CreateBmpFromMemory
FreeImage is an open-source library designed for developers who want to provide support for various popular graphic image formats including PNG, BMP, JPEG, TIFF, WEBP, and more. FreeImage has the capability to handle 30 different types of image file formats, as illustrated in Figure 28. Interestingly, the last update to this library was made available in 2018. The extensive range of supported formats presents a significant attack surface for potential fuzzing efforts.
Figure 28: FreeImage supports 30 image file formats
We developed a harness named freeimage_harness.exe designed for fuzzing FreeImage by implementing the functionality of the function MSOSPECTRE!FreeImageUtils::CreateBmpFromMemory in MSOSPECTRE.DLL.
Once we had the FreeImage harness ready, setting up a fuzzer was a straightforward task. We then took the following steps:
- Gather thousands of samples for the 30 different image file formats supported by the library.
- Minimize the collected samples for each image format using winafl-cmin.py.
- Seamlessly integrate the FreeImage harness into WinAFL.
The result was very successful, discovering 97 unique vulnerabilities in just two months.
Next, we will take a look at the process of reproducing these vulnerabilities within Microsoft 365 apps through a SketchUp file. We created a SketchUp template file based on the MFC data type as shown in Figure 29.
Figure 29: A SketchUp template file with the MFC data type
The following steps were performed:
- Obtain the size of the crafted image file (PoC file for FreeImage).
- Replace the image data in the SKP template file with the specially crafted image file.
- Revise the size field (4 bytes), which is situated just before the image data.