Binary File Write via Microsoft Speech API
This page describes how to use the Microsoft Speech API to write binary files from office documents.
TL;DR
Final proof of concept to make your Word document write binary without the infamous and widely-signatured adodb stream:
https://github.com/0xbad53c/VBA-Alternative-Binary-File-Write/blob/main/macro.vba
Introduction
While researching ways to write binary files via office macros, I stumbled accross a hidden gem: The Microsoft Speech API. Apparently, this API exposes COM objects, which enables you to call it from VBA and write WAV audio files. This article covers how to work around that limitation and avoid the WAV header, enabling you to write fully-intact binary files (e.g. DLLs or EXEs) to disk without the need for the popular ADODB object.
Discovery
The first step was to identify interesting COM objects. Several interesting targets stood out, but this article will focus on SAPI.SpFileStream.1. The identified object class appeared to have a method to write to a file.
To learn how to dump COM objects, refer to ired.team.
Writing Content
According to the Microsoft documentation, the Open method of a spFileStream object takes three arguments and can be used to write sound files:
The FileName of to the file to write;
The SpeechStreamFileMode, which can be a value of 0 to 3 (SSFMOpenForRead, SSFMOpenReadWrite, SSFMCreate or SSFMCreateForWrite);
A DoEvents boolean to decide if the written file should be played;
Next, the Write method can be used to write a string to the targeted file. Let's confirm this with a simple vba macro:
Magically, a test.exe file of 88 bytes appears.
Strange, we only wrote ~20 bytes of ASCII. Let's open the file in notepad.
It appears that Microsoft autmatically adds a WAVE header to the file. This is not useful to us, as we would like to drop binaries to disk.
Seek No Further
Pun intended. The SpFileStream class also includes an interesting Seek method. This is a way to move the file pointer forwards or backwards in the "audio" stream. The function takes 2 arguments:
The position, which is the number of bytes to move forward in the stream;
an SpeechStreamSeekPositionType parameter, which indicates the position to start from. This is an integer from 0 to 2 (SSSPRelativeToStart, SSSPRelativeToCurrentPosition or SSSPRelativeToEnd)
What if we would tell the program to move the pointer all the way to the start before we write the file? Would it overwrite the WAVE header? We can try this with the following VBA code:
Sadly, we still end up with 88 bytes of data. Somehow seek did not go to the start of the header, but rather the start of the data.
After this failure, I wrongly concluded that it could not be done and gave it a rest for a couple of months.
Create and Overwrite
I revisited the subject after a while and started playing around with the various options of the Open method. So far we were using only SSFMCreateForWrite (3) as SpeechStreamFileMode, while there is also SSFMOpenReadWrite (1). The latter does not work when the test.exe file does not exist yet, but there is nothing stopping us from creating it first, right?
The following VBA code snippet first creates the test.exe file, writes an empty string to it and then attempts to overwrite the data:
Interesting enough, we succeeded in overwriting the header!
At this point, we can write arbitrary content to a WAV file, which should be a binary audio file.
Final Proof of Concept
Finally, we can put the theory to practice and download an arbitrary executable and write it to disk using SpFileStream. Let's compile a simple Hello World message box program for this test.
The executable is 10.5Kb after compilation and the message box pops upon execution.
Next, we can craft a macro that downloads the executable via a Microsoft.XMLHTTP object and writes it similar to the last proof of concept.
After running the macro, the file was downloaded from our Python http.server.
The 10.5Kb executable was successfully written as test.exe and could be executed to prove it still worked!
Detection
In most environments, Office programs do not need to write executable file formats on disk. Therefore, build a baseline in your environment to alert or block writing of DLLs, EXEs or other dangerous file formats. Here is an example elastic query to detect DLL or exe writes from Microsoft Office applications, based on Sysmon event 11 (file creation):
This query can be expanded to include all multiple executable file formats, such as .js, .vbs, .hta etc. This highly depends the baseline to determine what is considered anomalous in your environment.
Additionally, you could monitor for WINWORD.EXE loading sapi.dll, but this is prone to false positives as there is a text to speach feature to read the document aloud.
If you know of other detection mechanisms, feel free to let me know via Twitter DM!
Conclusion
In this article, we experimented with a new way to write binary files from Office macros and other Windows scripting languages. Threat actors can abuse the Microsoft Speech API's SpFileStream object to write binaries to disk, which can be a stepping stone to other attacks, such as DLL sideloading.
Last updated