By Phillip Langlois and Edward Torkington

Introduction

In November 2019, we published a blog post covering an elevation-of-privilege vulnerability we found in Windows whilst conducting research into Windows Component Object Model (COM) services.

During the course of this research, we discovered a number of vulnerabilities in several COM services that we reported to Microsoft. In this particular post, we will cover one of these vulnerabilities in detail. The motivation for doing so is twofold; firstly, at a high level the root cause of this vulnerability shares many similarities with a number of others that we discovered and is itself of interest. Secondly, Microsoft’s initial attempt to patch the vulnerability highlighted both the difficulty of successfully fixing this kind of bug and, perhaps of even more interest, some inaccuracies in Microsoft documentation that may have implications for other operating system components.

Some basic background information about COM may be found in our first post and we will not cover any COM background material here.

The Component Based Servicing Session Object

The Windows Module Installer service is enabled by default on Windows 10 and executes as the user NT AUTHORITY\SYSTEM . A number of COM objects execute as local servers in this service, as shown by OleViewDotNet in the screenshot below:

The following screenshot displays the launch permissions applied to the service and shows that users in the group NT AUTHORITY\INTERACTVE can launch the COM servers it hosts:

Servers executing in the context of the Windows Modules Installer service are therefore of interest when investigating possible privilege elevation vectors and this includes the Component Based Servicing Session COM object. This object implements a number of non-standard interfaces, as shown in the screenshot below:

In particular, this object implements various iterations of the undocumented ICbsSession interface. On Windows 10 at the time of writing, for example, this object supports the ICbsSession10 interface and OleViewDotNet (with symbols configured) gives us the following interface definition:

Arbitrary File Disclosure (Part 1)

Without performing any in-depth analysis of the code, it was clear that some of the methods exposed by the ICbsSession10 interface were of interest. Furthermore, it seemed likely that the Initialize method would need to be called successfully prior to calling other methods.

In general, understanding how to correctly call such a relatively complex method without documentation requires some reverse engineering of the code behind. In this case, however, we noticed that calling Initialize with any data resulted in the service writing entries to the CBS.log file in the %WINDIR%\Logs\CBS folder. In particular, it was possible to iteratively determine the purpose of most of the arguments to Initialize (for example) by calling the method, examining the latest entries in the log file (and associated activity in Process Monitor), fixing reported errors and trying again. The precise details do not add a great deal to this exposition and we will not cover them here. Ultimately, we were able to determine the following working definitions for the Initialize and CreatePackage methods:

HRESULT Initialize( [in] int p0, [in] wchar_t* ClientId, [in] wchar_t* BootDrive, [in] wchar_t* WindowsDir ); HRESULT CreatePackage( [in] int p0, [in] int p1, [in] wchar_t* PackageFolder, [in] wchar_t* p3, [out] IUnknown** p4 );

Analysis confirmed that the Initialize method must be called successfully prior to executing other methods from the interface. The p0 and ClientId parameters can be set to arbitrary values, and BootDrive can be set to the string C:\ . The WindowsDir parameter must be set to a folder containing certain Windows components, however, and the parent folder of the WindowsDir folder must also contain a suitable Users folder. In practice, it is possible to construct a suitable folder structure containing the required files from a base installation of Windows 10. Note, however, that it is not possible to specify the actual Windows folder, since this results in a sharing violation when the service attempts to open certain registry hives.

Most of the parameters to CreatePackage do not matter for the purposes of the vulnerability we will shortly describe; setting p0 to the same value as used for the Initialize call, p1 to 1 and p3 to an arbitrary string works fine ( p4 is an output parameter that we also do not care about). When CreatePackage is called in this way after a successful call to Initialize , the service attempts to copy the file update.mum from the specified PackageFolder folder to a temporary folder in the CbsTemp folder located in the folder specified by WindowsDir in the Initialize call. This copy was performed by the service without impersonating the caller.

As described previously, a low-privilege user is able to create the folder structure required by the Initialize method. Such a user is also able to use the CreateSymLink tool to create a folder containing a symbolic link called update.mum that points to any file on the local file system. When such a folder is passed to the CreatePackage method, the target of the symbolic link is copied and this copy is then readable by the user, leading to a simple arbitrary file disclosure vulnerability.

This vulnerability was reported to Microsoft in August 2019 and assigned CVE-2019-1381. A patch was released in November 2019.

Arbitrary File Disclosure (Part 2)

Process Monitor showed that prior to the patch, the file copy operation was performed in a TIWorker.exe process by the CopyManifestFilesAndVerifyContent method implemented in the module CbsCore.dll . After applying the patch and attempting the exploit, we observed additional entries in the CBS.log file similar to those shown below:

2019-11-15 10:51:41, Info CBS Build: 18362.1.x86fre.19h1_release.190318-1202 2019-11-15 10:51:41, Info CBS Virtual Edition: Microsoft-Windows-EducationEdition will be mapped to: Microsoft-Windows-ProfessionalEdition 2019-11-15 10:51:41, Info CBS Session: 2104_24958890 initialized by client string1, external staging directory: (null), external registry directory: (null) 2019-11-15 10:51:41, Info CBS Symlink found, not copying file. [HRESULT = 0x80070002 - ERROR_FILE_NOT_FOUND] 2019-11-15 10:51:41, Info CBS Failed to copy catalog:\\?\C:\users\lowpriv\CBSSTEST\update.cat to private session store: \\?\C:\users\lowpriv\CBSSLPEdata\windows\CbsTemp\2104_24958890\ [HRESULT = 0x80070002 - ERROR_FILE_NOT_FOUND] 2019-11-15 10:51:41, Info CBS Failed to copy manifest: \\?\C:\users\lowpriv\CBSSTEST\update.mum to private store and verify the content. [HRESULT = 0x80070002 - ERROR_FILE_NOT_FOUND] 2019-11-15 10:51:41, Info CBS Failed to initialize internal package [HRESULT = 0x80070002 - ERROR_FILE_NOT_FOUND] 2019-11-15 10:51:41, Error CBS Failed to create internal package [HRESULT = 0x80070002 - ERROR_FILE_NOT_FOUND] 2019-11-15 10:51:41, Info CBS Failed to create windows update package [HRESULT = 0x80070002 - ERROR_FILE_NOT_FOUND] 2019-11-15 10:51:41, Info CBS Failed to CreatePackage using worker session [HRESULT = 0x80070002] 2019-11-15 10:51:41, Info CBS Failed to create internal CBS package [HRESULT = 0x80070002]

The highlighted line appears to indicate that the service has identified that we have provided a symbolic link rather than an ordinary file and has therefore aborted the operation. At this point, we thought we understood how the patch had addressed the issue, however for completeness we wanted to check the modified code to be certain that there was no bypass possible. Returning to Process Monitor again to help us find the modified code, we actually noticed something surprising – our original exploit code was still causing the service to copy our target file!

So what was going on here? The file copy operation was now being performed by the PrivCopyFileToTargetFolder method from CbsCore.dll , and this method was called by the previously identified CopyManifestFilesAndVerifyContent method. A decompilation of PrivCopyFileToTargetFolder is shown below:

We can clearly see the call to CBSWdsLog that generates the log file entry we observed and at first glance, this code seems fine. The logic is as follows:

Call GetFileAttributesW on the file path passed to the service

on the file path passed to the service Check bit 10 of the result This is the FILE_ATTRIBUTE_REPARSE_POINT flag that should be set for a symbolic link

Execute the copy file operation if this bit is not set (via a helper function)

Otherwise, log the error message

Looking more closely, however, we noticed that the error message will get logged whenever the helper is not executed. In particular, the error will get logged if the specified file does not exist (since GetFileAttributesW will return an error condition). Returning to the log file, we noticed the following entries:

2019-11-15 10:51:41, Info CBS Failed to copy catalog:\\?\C:\users\lowpriv\CBSSTEST\update.cat to private session store: \\?\C:\users\lowpriv\CBSSLPEdata\windows\CbsTemp\2104_24958890\ [HRESULT = 0x80070002 - ERROR_FILE_NOT_FOUND] 2019-11-15 10:51:41, Info CBS Failed to copy manifest: \\?\C:\users\lowpriv\CBSSTEST\update.mum to private store and verify the content. [HRESULT = 0x80070002 - ERROR_FILE_NOT_FOUND]

Again looking more closely, we see that the first entry is actually relating to the file update.cat , not update.mum as we expected, and correctly indicates that the file update.cat could not be found. The CopyManifestFilesAndVerifyContent method actually calls PrivCopyFileToTargetFolder in three places. The first of these attempts to copy the file update.mum from the specified PackageFolder ; the second call attempts to copy update.cat . The first log entry shown above is written by the CopyManifestFilesAndVerifyContent method when the second call to PrivCopyFileToTargetFolder fails and it is this call to PrivCopyFileToTargetFolder that causes the symbolic link error to be written to the log file.

The second log entry above is actually written by the CCbsPackage::PrepareInitialize method that calls CopyManifestFilesAndVerifyContent and we note that the messages are very similar. As we ourselves discovered, upon viewing the second entry (which references update.mum ) and the symbolic link error it is very easy to assume that the code is functioning correctly when passed a symbolic link.

So what happened when PrivCopyFileToTargetFolder was called to copy update.mum ? Well it turns out that GetFileAttributesW returns information about the target of the symbolic link, not the symbolic link itself, and the target file was copied without error.

So why was the code patched in this manner? The following text is taken directly from the MSDN documentation for the GetFileAttributesW function:

Symbolic link behavior—If the path points to a symbolic link, the function returns attributes for the symbolic link.

Additional documentation is also available covering the behaviour of a number of file system functions when passed a path to a symbolic link. The following text is taken directly from this page:

GetFileAttributes If the path points to a symbolic link, the function returns attributes for the symbolic link.

When a symbolic link is created using the mklink tool, GetFileAttributes does indeed return attributes for the link and this includes the FILE_ATTRIBUTE_REPARSE_POINT flag. When a symbolic link is created with the CreateSymLink tool, however, the documentation is incorrect and attributes are returned for the target of the link. The key here is that Windows actually supports a number of different types of symbolic link (see for example James Forshaw’s blog post) and the documentation does not apply to all of these. The broken patch therefore appears to be a combination of faulty logic in the code and use of an API which has incompletely/incorrectly documented behaviour.

The issues with the initial patch were reported to Microsoft two days after the patch was released and assigned CVE-2020-0859. A further patch was released in March 2020.

Final Patch

Typically, fixing information disclosure vulnerabilities in services such as this can be more complicated than one might expect. The difficulty arises primarily because of the need to execute code with high privileges in order to carry out the required functionality but on behalf of users with limited rights, preventing the service from simply impersonating the low privilege user for the duration of the task. This does not appear to be the case for the Component Based Servicing Session object however, and Microsoft ultimately chose to fix the vulnerability described above by impersonating the calling user prior to calling the CopyManifestFilesAndVerifyContent method. This appears to successfully mitigate the issue.

Incorrect Documentation and Future Work

The documentation referenced above covers the behaviour of a number of file system related APIs when processing symbolic links. In addition to GetFileAttributes , testing indicates that this documentation is also incorrect for DeleteFile when symbolic links are created with CreateSymLink and it is likely that other APIs are similarly affected.

Having discovered a vulnerability caused in part by this incorrect documentation, we were curious to see if other Microsoft services made similar assumptions about the behaviour of these APIs. In general, this seems to be a relatively difficult question to answer without access to source code. In the case of GetFileAttributes , however, it is straightforward to see when code is attempting to check for the presence of a reparse point or symbolic link by looking for references to the FILE_ATTRIBUTE_REPARSE_POINT flag after the API call.

Making use of radare2 , we were able to quickly identify a large number of calls to GetFileAttributes in standard Microsoft modules and then disassemble the code around each call. Simple post processing of this data then allowed us to identify over 60 instances of code implementing similar to logic to that shown above. Furthermore, in a number of cases the identified modules were associated with services executing with high privileges.

In general, given any particular example of such code it is non-trivial to determine how a low privilege user might exercise the code. We performed a very quick triage of the more interesting examples, but did not discover additional vulnerabilities. This analysis was time limited, however, so we do not discount the possibility that such vulnerabilities remain to be found.

Of course given access to source code, this problem would likely be considerably easier. In addition to reporting the flawed patch, we therefore raised a separate issue with Microsoft to highlight the incorrect documentation and the possibility that there could be vulnerabilities related to this in sensitive code. At the time of writing, Microsoft have accepted that the documentation is confusing but have no immediate plans to update it. The documentation will eventually be updated to clarify the behaviour but no patch updates will be released.

Radare2 Script and Sample Output

The following radare2 script was used to identify possibly incorrect use of the GetFileAttributes method:

import sys import os import r2pipe import re apis = ["GetFileAttributes"] directory = os.fsencode(sys.argv[1]) p = re.compile(" bt .*0xa

") for file in os.listdir(directory): filename = sys.argv[1] + "\\" + os.fsdecode(file) if filename.endswith(".dll") or filename.endswith(".exe"): f = open("GetFileAttributes.txt", "a+", encoding='UTF-8') try: r2 = r2pipe.open(filename) r2.cmd('aaa') found = False text = "" for api in apis: results = r2.cmdj('axtj @ `ii~%s[1]`' % api) for line in results: presult = r2.cmd("pd 50 @{}".format(line["from"])) presult = presult.replace("\r", "") if (p.search(presult)): found = True text += presult if (found): f.write("----------------------------------------------------------

") f.write("Filename: %s

" % filename) f.write("----------------------------------------------------------

") f.write(text + "





") f.close() except Exception as e: f.write(repr(e)) f.write(sys.exc_info()[0]) f.close() r2.cmd('quit')

An extract of the output from this script is shown below, highlighting the call to GetFileAttributes and the test for the FILE_ATTRIBUTE_REPARSE_POINT flag:

---------------------------------------------------------- Filename: c:\windows\system32\\AgentService.exe ---------------------------------------------------------- │ ; CODE XREF from fcn.14008e224 (0x14008e262) │ 0x14008e267 ff15bbb90300 call qword sym.imp.KERNEL32.dll_GetFileAttributesW ; [0x1400c9c28:8]=0x112644 reloc.KERNEL32.dll_GetFileAttributesW ; "D&\x11" ; DWORD GetFileAttributesW(LPCWSTR lpFileName) │ 0x14008e26d 448bf0 mov r14d, eax │ 0x14008e270 83f8ff cmp eax, 0xffffffff │ ┌─< 0x14008e273 7513 jne 0x14008e288 │ │ 0x14008e275 4d8bc7 mov r8, r15 │ │ 0x14008e278 488bd7 mov rdx, rdi │ │ 0x14008e27b 488bce mov rcx, rsi │ │ 0x14008e27e e819f7ffff call fcn.14008d99c │ ┌──< 0x14008e283 e911010000 jmp 0x14008e399 │ ││ ; CODE XREF from fcn.14008e224 (0x14008e273) │ │└─> 0x14008e288 4533e4 xor r12d, r12d │ │ 0x14008e28b 410fbae60a bt r14d, 0xa │ │┌─< 0x14008e290 0f83c9000000 jae 0x14008e35f │ ││ 0x14008e296 48837f1808 cmp qword [rdi + 0x18], 8 ; [0x18:8]=-1 ; 8 │ ┌───< 0x14008e29b 7205 jb 0x14008e2a2 │ │││ 0x14008e29d 488b17 mov rdx, qword [rdi] │ ┌────< 0x14008e2a0 eb03 jmp 0x14008e2a5 │ ││││ ; CODE XREF from fcn.14008e224 (0x14008e29b) │ │└───> 0x14008e2a2 488bd7 mov rdx, rdi │ │ ││ ; CODE XREF from fcn.14008e224 (0x14008e2a0) │ └────> 0x14008e2a5 48c745e80700. mov qword [var_18h], 7 │ ││ 0x14008e2ad 4c8965e0 mov qword [var_20h], r12 │ ││ 0x14008e2b1 66448965d0 mov word [var_30h], r12w │ ││ 0x14008e2b6 66443922 cmp word [rdx], r12w │ ┌───< 0x14008e2ba 7505 jne 0x14008e2c1 │ │││ 0x14008e2bc 4d8bc4 mov r8, r12 │ ┌────< 0x14008e2bf eb0e jmp 0x14008e2cf │ ││││ ; CODE XREF from fcn.14008e224 (0x14008e2ba) │ │└───> 0x14008e2c1 4983c8ff or r8, 0xffffffffffffffff │ │ ││ ; CODE XREF from fcn.14008e224 (0x14008e2cd) │ │┌───> 0x14008e2c5 49ffc0 inc r8 │ │╎││ 0x14008e2c8 6646392442 cmp word [rdx + r8*2], r12w │ │└───< 0x14008e2cd 75f6 jne 0x14008e2c5 │ │ ││ ; CODE XREF from fcn.14008e224 (0x14008e2bf) │ └────> 0x14008e2cf 488d4dd0 lea rcx, [var_30h] │ ││ 0x14008e2d3 e8f87ef7ff call fcn.1400061d0

Share this: Twitter

Reddit

LinkedIn

Facebook

