Getting to the Bottom of CVE-2018-0825 Heap Overflow Buffer
2020-07-05 | 12 min read
By Constantin-Cosmin Craciun | Microsoft recently released the Tuesday Patch that aims to fix a series of vulnerabilities, 14 of which are evaluated as Critical. The top of the list is CVE-2018-0825, a Remote Code Execution vulnerability in Windows dynamic library StructuredQuery.dll. According to the official vendor advisory, this vulnerability affects all Microsoft Windows products that are currently maintained. As this vulnerability has not been publicly disclosed yet, the application and Threat Intelligence (ATI) Research Team decided to take a closer look to what this issue is about and see how Microsoft is dealing with this kind of 'Patch Now' vulnerability.
Environment and Toolset:
Microsoft Windows 7 x86 SP1 was chosen as the target. For this task, the following tools were used:
- Ida Pro 6.9 + Hex-Rays Decompiler on Fedora 25
- Windows 10 x64 as debugging server
- BinDiff plugin
Note: BinDiff does not officially support Fedora, so to integrate it with the running instance of Ida Pro, we had to create a RPM package from the officially distributed .deb package.
Summarized steps for performing this research:
1. Set up the work environment.
2. Obtain two versions of target binary (StructuredQuery.dll), the unpatched and the patched one.
3. Open both versions in Ida Pro, saving the correspondent .idb analysis files for each one.
4. Run BinDiff against .idb files, identify differences, and locate the vulnerability.
5. Test the vulnerability against an unpatched version of Microsoft Windows.
1. Set up the work environment
Ida Pro 6.9, including Hex-Rays Decompiler were already installed on one of our Fedora 25 machines. BinDiff does not officially support Fedora, so to install and integrate it with Ida Pro, we downloaded the Debian package and converted it to RPM, then installed it on Fedora using instructions found here.
When Windows binaries are analyzed in Ida Pro running on Linux, the disassembler needs a Windows debugging server for downloading symbols. Having function names rather than unintelligible labels or addresses makes life a lot easier. We used a Windows 10 x64 box running win32_debugger.exe with IP connectivity to the Fedora box. Win32_debugger.exe is delivered with Ida Pro (non-free version), so copying it from Fedora to Windows 10 and running it did the job.
2. Obtain two versions of target binary
First, we checked the update status of the Windows 7 x86 SP1 target box. In the Installed Updates windows, last update was installed on 01/19/2018. Microsoft released the patch in February, so this box is a good place to pick the vulnerable library.
StructuredQuery.dll resides in %WINDIR%\System32 so we copied it to the Fedora box for analyzing in Ida Pro.
For the patched version of the library, a few steps had to be performed:
b. Having the update package ID, we can download it from the Microsoft Update Catalog (http://www.catalog.update.microsoft.com).
We end up with a file named
that can be expanded (starting with Windows Vista) using expand tool:
expand –f:* windows6.1-kb4074587-x86_4acde011f9386e8555109c8a06d406bbcee20a99.msu C:\\expanded\\
The Expand tool created a bunch of files and directories in the output folder. A quick search for 'StructuredQuery.dll' returned the expected result:
Looking at the properties of both library files (old picked one and the expanded one) we can see how the versions are different, even though the file size is identical:
Copying the file to the Fedora box for analyzing completed this second step.
3. Open binaries in Ida Pro
For differential analysis, BinDiff needs .idb file results after initial analysis of the binary made by Ida Pro. For easier tracking, we renamed old binary as StructuredQuery_unpatched.dll and the new one as structuredquery_patched.dll.
a. Open structuredquery_patched.dll in Ida Pro and let it download debugging symbols from the debugging server. After initial analysis is complete, save and close the Ida Pro session.
b. Repeat operation for StructuredQuery_unpatched.dll, but do not close session this time.
4. Run BinDiff against .idb files
After initial analysis for StructuredQuery_unpatched.dll is complete, select Ctrl + 6 to open the BinDiff plugin.
Select Diff Database and browse to the previously saved .idb file.
After BinDiff analysis is finished, we get the following results:
The best way to look at these is by ordering result ascending by similarity. In this case, we can see a total of 15 functions that differ between these StructuredQuery.dll versions. The function with the most differences is StructuredQuery1::ReadPWSTR(IStream*, ushort **). This gave us a clue where the patch for CVE-2018-0825 might be. The pseudocode versions of the same function in both libraries are listed next, as Hex-Rays decompiler reversed them:
A summary of what ReadPWSTR() does:
a. Reads four bytes from pstm (pointer to IStream) and stores the value to pv.
b. CoTaskMemAlloc() is called to allocate a memory buffer of size 2 * pv.
c. If allocation succeeds, calls IStream_Read() again to read (2 * pv -2) bytes further from pstm and stores then into newly allocated buffer. Looks like a proper place for a buffer overflow to occur.
Now let’s take a look at the differences. We noticed a few changes in the newer version:
- pv variable is declared as unsigned int rather than int.
- a new SIZE_T cb local variable was introduced.
- ULongLongToULong() function is called with the following arguments: (2 * pv), &cb (note the inline casting of constant 2 to a unsigned integer64); MSDN provides details about this function, but the name is self-explanatory.
- cb variable is passed as a parameter to CoTaskMemAlloc(), instead of 2 * pv.
It is obvious that all these changes are related, the patch aiming to validate the pv value before being passed as an argument to CoTaskMemAlloc().
According to MSDN, CoTaskMemAlloc() takes a SIZE_T argument as the size of the memory block to be allocated, in bytes. The IStream_Read() function takes an ULONG argument representing the number of bytes of data that the function should attempt to read from the input stream and write to the destination buffer. On 32-bit systems, both of these are four bytes long. Remember that in the unpatched version, pv was declared as signed int, so for a value equal to 0x80000000, the following will happen:
- (SIZE_T)(2 * pv) = 0 => CoTaskMemAlloc(0) is called, a buffer of length 0 will be allocated.
- (ULONG)( 2 * pv - 2) = 0xfffffffe => IStream_Read(pstm, <dest_buffer>, 0xfffffffe) is called, so a stream of 0xfffffffe length is written to a o length buffer.
The details we have at this point lead us to the following theory: In the unpatched version of StructuredQuery.dll there is an integer overflow vulnerability that leads to a buffer overflow that can allow attackers to run arbitrary code on the vulnerable system.
This is what we are going to test in the next step.
5. Test the vulnerability
For the test, we created a proof of concept (PoC) win32 console application (structured_query_tests.cpp) and a malicious input file (poc.dat). Both the source code and a snippet of the content of poc.dat are listed next:
PoC malicious input file
Structured_query_tests.cpp contains my_ReadPWSTR() function, an identical implementation of the unpatched version of ReadPWSTR() from StructuredQuery.dll.
The Poc.dat file contains 4 bytes at the beginning that will be read at the time of first call of IStream_Read (line 31) and stored into the pv variable, which later is used in a second call to IStream_Read that will trigger the vulnerability (note the little-endian byte order).
Our assumption is confirmed when we run structured_query_tests.exe. Here is what the WinDbg console looks like:
So the second call to IStream_Read() (line 37) read 0x80000000 bytes from the poc.dat file and wrote them into a 0-length allocated buffer, over-writing the following memory blocks and resulting in a heap buffer overflow.
Conclusion and future work
The vulnerability labeled as Critical (CVE-2018-0825) addressed by the latest Windows Security Update patch released by Microsoft is a Heap Buffer Overflow type, caused by an initial integer overflow. This is due to improper use of variable types. Heap Buffer Overflow vulnerabilities are known to be exploitable, so an attacker could potentially run arbitrary code on the vulnerable machine by identifying a proper attack vector. A quick search in the running processes of a Win 10 system using Process Explorer identified almost 20 unique processes holding a handle to StructuredQuery.dll, including but not limited to: OUTLOOK.exe, EXCEL.exe, explorer.exe, chrome.exe. The area of searching for an attack vector seems generous and this will be the future work of the ATI Research Team regarding this widely exposed vulnerability.