by Mitja Kolsek, the 0patch Team
While we're busy ironing out the wrinkles before 0patch finally exits its adolescence (i.e., Beta) and becomes a fully responsible adult able to pay for its own rent, we did find some time to produce... not one, ... not two, ... but three 0day micropatches in the past few days. That's right, at this very moment you can get three 0days on your Windows computer micropatched for free! All you have to do is register a free 0patch account and install 0patch Agent.
Let's quickly go through each of these 0days and see what they allow attackers to do, and how we micropatched them. Then we can return to the wrinkle-ironing mode and bring you the best patching experience that we possibly can.
0day #1: the "angrypolarbearbug"
[Update 5/16/2019: Microsoft has fixed this issue with May 2019 Updates and assigned it CVE-2019-0863.]
This 0day, dubbed "angrypolarberbug" by its author SandboxEscaper who published it last month, allows a local unprivileged process to get any chosen file on the system overwritten with the content of a Windows Error Reporting XML file. The attacker has very little control over the content of this XML file so the demonstration provided by SandboxEscaper was a local denial of service by corrupting a critical system file pci.sys, which prevents the system from booting. One can imagine potentially finding some other file to overwrite that would lead to execution of attacker's code under higher privileges such as SYSTEM or Administrator - but to our knowledge, such example has not been published.
The crux of this vulnerability is in the fact that the C:\ProgramData\Microsoft\Windows\WER\Temp\ folder, where the Windows Error Reporting Service is creating a temporary XML file, has inheritable permissions that include read, write and delete access for Authenticated Users (which includes the local attacker). This means that whenever anyone creates a new file there without specifying its permissions, any process on the system, including a low-privileged malicious process, will be able to replace that file with another file. And that's what SandboxEscaper's proof-of-concept does: it waits for the XML file to appear, and then quickly replaces it with a hard link to the chosen target file (e.g., pci.sys). When Windows Error Reporting Service subsequently re-opens this XML file for writing, it actually opens the linked-to file, and writes to that file.
Our micropatch makes a small change: when Windows Error Reporting Service creates the XML file, it now specifies permissions that are otherwise exactly the same as before, except that Authenticated Users don't have delete permissions on it. This keeps error reporting working while preventing the exploit from deleting the XML file (and thus also from creating a hard link in its place).
We currently have this micropatch for fully updated 64-bit Windows 10 version 1803. (Contact support@0patch.com to express your interest in porting to other versions.) Note that this issue doesn't seem to affect Windows 7, where error reporting works a bit differently.
You can see this micropatch in action in the following video.
;Micropatch for wer.dll version 10.17134.471
;
;How it works:
; a vulnerable call CreateFileW responsible for creating a temporary report XML file
; which inherits loose C:\ProgramData\Microsoft\Windows\WER\Temp\ permissions is replaced by
; a call to ConvertStringSecurityDescriptorToSecurityDescriptor which creates a new security
; descriptor from ACE string that gets supplied to a new CreateFileW call.
; The new security descriptor has no DELETE permissions for AuthenticatedUsers group
; on report XML so a regular user can no longer change it to a hard link.
MODULE_PATH "..\AffectedModules\wer.dll_10.17134.471_64bit\wer.dll"
PATCH_ID 344
PATCH_FORMAT_VER 2
VULN_ID 4657
PLATFORM win64
patchlet_start
PATCHLET_ID 1
PATCHLET_TYPE 2
PIT kernel32.dll!CreateFileW,advapi32.dll!ConvertStringSecurityDescriptorToSecurityDescriptorA,kernel32.dll!LocalFree
PATCHLET_OFFSET 0x00059bd7
JUMPOVERBYTES 11
N_ORIGINALBYTES 2
code_start
mov qword [rsp+10h], r8 ; dwShareMode
mov qword [rsp+8h], rdi ; storing a global variable
mov qword [rsp], rcx ; lpFileName
call arg0_StringSecurityDescriptor
; args for ConvertStringSecurityDescriptorToSecurityDescriptor
; we changed (A;;0x13019f;;;AU) to (A;;GRSD;;;AU) - meaning
; AuthenticatedUsers can Read and Delete only
db "D:(A;;FA;;;BA)(A;;GRGW;;;AU)(A;;0x13019f;;;SU)(A;;0x13019f;;;LS)(A;;0x13019f;;;NS)(A;;0x13019f;;;WR)(A;;0x13019f;;;AC)(A;;0x13019f;;;S-1-15-2-2)",0
arg0_StringSecurityDescriptor:
pop rcx ; rcx=arg0_StringSecurityDescriptor
mov rdx, 01h ; arg1: StringSDRevision=SDDL_REVISION_1
;arg2: this arg is part of SECURITY_ATTRIBUTES struct so we have to create this first
; sa requires 18h of space
sub rsp, 20h ; but we're allocating 8 more than required nLength to keep stack alignment
lea r8, [rsp+8h] ;arg2: SecurityDescriptor=&sa.lpSecurityDescriptor
;init sa:
mov dword [rsp],18h ;sa.nLength = sizeof(SECURITY_ATTRIBUTES);
mov dword [rsp+10h],1h ;sa.bInheritHandle=FALSE
xor r9d,r9d ; SecurityDescriptorSize=NULL
sub rsp, 20h ; allocate homespace
call PIT_ConvertStringSecurityDescriptorToSecurityDescriptorA
;copy CreateFileW args 5,6 and 7 to a new stack frame
mov rax, [rsp+60h] ; dwCreationDisposition
mov qword [rsp],rax
mov rax, [rsp+68h] ; dwFlagsAndAttributes
mov qword [rsp+8h],rax
mov rax, [rsp+70h] ; hTemplateFile
mov qword [rsp+10h],rax
;obtain CreateFileW args 1,2,3,4
mov rcx, [rsp+40h] ; lpFileName
mov edx, 0C0000000h ; dwDesiredAccess
mov r8, [rsp+50h] ; dwShareMode
lea r9,[rsp+20h] ;lpSecurityAttributes
sub rsp, 20h ; alloc homespace
call PIT_CreateFileW
mov [rsp+28h],rax ; store result
;free the security descriptor:
mov rcx,[rsp+48h] ; sa.lpSecurityDescriptor
call PIT_LocalFree ;LocalFree(sa.lpSecurityDescriptor)
mov rax,[rsp+28h] ;restore result
mov rdi,[rsp+68h] ;restore the global variable
add rsp, 60h ;restore stack
code_end
patchlet_end
;
;How it works:
; a vulnerable call CreateFileW responsible for creating a temporary report XML file
; which inherits loose C:\ProgramData\Microsoft\Windows\WER\Temp\ permissions is replaced by
; a call to ConvertStringSecurityDescriptorToSecurityDescriptor which creates a new security
; descriptor from ACE string that gets supplied to a new CreateFileW call.
; The new security descriptor has no DELETE permissions for AuthenticatedUsers group
; on report XML so a regular user can no longer change it to a hard link.
MODULE_PATH "..\AffectedModules\wer.dll_10.17134.471_64bit\wer.dll"
PATCH_ID 344
PATCH_FORMAT_VER 2
VULN_ID 4657
PLATFORM win64
patchlet_start
PATCHLET_ID 1
PATCHLET_TYPE 2
PIT kernel32.dll!CreateFileW,advapi32.dll!ConvertStringSecurityDescriptorToSecurityDescriptorA,kernel32.dll!LocalFree
PATCHLET_OFFSET 0x00059bd7
JUMPOVERBYTES 11
N_ORIGINALBYTES 2
code_start
mov qword [rsp+10h], r8 ; dwShareMode
mov qword [rsp+8h], rdi ; storing a global variable
mov qword [rsp], rcx ; lpFileName
call arg0_StringSecurityDescriptor
; args for ConvertStringSecurityDescriptorToSecurityDescriptor
; we changed (A;;0x13019f;;;AU) to (A;;GRSD;;;AU) - meaning
; AuthenticatedUsers can Read and Delete only
db "D:(A;;FA;;;BA)(A;;GRGW;;;AU)(A;;0x13019f;;;SU)(A;;0x13019f;;;LS)(A;;0x13019f;;;NS)(A;;0x13019f;;;WR)(A;;0x13019f;;;AC)(A;;0x13019f;;;S-1-15-2-2)",0
arg0_StringSecurityDescriptor:
pop rcx ; rcx=arg0_StringSecurityDescriptor
mov rdx, 01h ; arg1: StringSDRevision=SDDL_REVISION_1
;arg2: this arg is part of SECURITY_ATTRIBUTES struct so we have to create this first
; sa requires 18h of space
sub rsp, 20h ; but we're allocating 8 more than required nLength to keep stack alignment
lea r8, [rsp+8h] ;arg2: SecurityDescriptor=&sa.lpSecurityDescriptor
;init sa:
mov dword [rsp],18h ;sa.nLength = sizeof(SECURITY_ATTRIBUTES);
mov dword [rsp+10h],1h ;sa.bInheritHandle=FALSE
xor r9d,r9d ; SecurityDescriptorSize=NULL
sub rsp, 20h ; allocate homespace
call PIT_ConvertStringSecurityDescriptorToSecurityDescriptorA
;copy CreateFileW args 5,6 and 7 to a new stack frame
mov rax, [rsp+60h] ; dwCreationDisposition
mov qword [rsp],rax
mov rax, [rsp+68h] ; dwFlagsAndAttributes
mov qword [rsp+8h],rax
mov rax, [rsp+70h] ; hTemplateFile
mov qword [rsp+10h],rax
;obtain CreateFileW args 1,2,3,4
mov rcx, [rsp+40h] ; lpFileName
mov edx, 0C0000000h ; dwDesiredAccess
mov r8, [rsp+50h] ; dwShareMode
lea r9,[rsp+20h] ;lpSecurityAttributes
sub rsp, 20h ; alloc homespace
call PIT_CreateFileW
mov [rsp+28h],rax ; store result
;free the security descriptor:
mov rcx,[rsp+48h] ; sa.lpSecurityDescriptor
call PIT_LocalFree ;LocalFree(sa.lpSecurityDescriptor)
mov rax,[rsp+28h] ;restore result
mov rdi,[rsp+68h] ;restore the global variable
add rsp, 60h ;restore stack
code_end
patchlet_end
0day #2: the "readfile" (subsequently assigned
CVE-2019-0636)
[Update 2/14/2019: Microsoft has fixed this issue with February 2019 Updates and assigned it CVE-2019-0636.]The "readfile" 0day was also published by SandboxEscaper last month. This one allows an unprivileged process running on a Windows computer to obtain the content of arbitrary file, even if permissions on such file don't allow it read access. The proof-of-concept demonstrates reading the content of another user's desktop.ini file from user's desktop, but the author suggests reading Office history files (and other index or history files with known paths) could reveal further paths to interesting files belonging to other users.
The flaw lies in Windows Installer, more specifically in its advertisement functionality, which can be triggered using the MsiAdvertiseProduct function. We're not going to dive into details here; suffice it to say that when a product (i.e., its MSI installation package) is advertised, Windows Installer (running as SYSTEM) takes this MSI file, parses it, then creates a temporary MSI file in C:\Windows\Installer folder and copies the content of the original MSI file in it. (It does much more with it then but that's irrelevant for this issue.) SandboxEscaper noticed that the original MSI file is opened twice, and that a symbolic link can be made that points to a regular MSI file on the first open, but points to some other file on the second open - which results in the content of the latter file being copied to the temporary MSI file. Due to file permissions on the temporary MSI file, which allow Everyone read access, an attacker can thus trick the Windows Installer Service to copy the content of arbitrary file to the temporary MSI file, and then read that file to obtain said content.
Micropatching this issue required us to familiarize ourselves with the inner working of Windows Installer. It turned out that the function copying the source MSI file into the temporary MSI file already supports setting two different permission sets on the temporary file: (1) inherited permissions, and (2) permissions of the source MSI file. Its behavior in advertising a product uses the inherited permissions (which allow the attacker to read the file), so we used four strategically-placed patchlets to force it to use permissions of the source MSI file instead. With the micropatch in place, product advertisement still works as before, but since the permissions on the temporary MSI file are the same as on the file being copied, the attacker gains nothing from this process.
We currently have this micropatch for fully updated 64-bit Windows 10 version 1803 and fully updated 64-bit Windows 7. (Contact support@0patch.com to express your interest in porting to other versions.)
You can see this micropatch in action in the following video.
And here's the source code for this micropatch; four patchlets, each with a single instruction.
MODULE_PATH "..\AffectedModules\msi.dll_5.0.17134.228_64bit.dll\msi.dll"
; Windows 10 version 1803
PATCH_ID 345
PATCH_FORMAT_VER 2
VULN_ID 4658
PLATFORM win64
patchlet_start
PATCHLET_ID 1
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x002edb76 ; Injecting after eax is set to FlagsAndAttributes
; in CMsiFileCopy::OpenDestination
code_start
or r12d, 0x8000 ; we set the 15th bit of FlagsAndAttributes,
; which will cause the execution to flow towards using
; source file's ACL
code_end
patchlet_end
patchlet_start
PATCHLET_ID 2
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x002edbe9 ; Overwriting code that checks value #12 in
; MSI record, and setting eax to 1
JUMPOVERBYTES 20
code_start
mov eax, 1 ; we set eax to 1 to simulate IsNull returning false
code_end
patchlet_end
patchlet_start
PATCHLET_ID 3
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x002edd0e ; Overwriting code that checks value #12 in
; MSI record, and setting eax to 1
JUMPOVERBYTES 22
code_start
mov eax, 1 ; we set eax to 1 to simulate IsNull returning false
code_end
patchlet_end
patchlet_start
PATCHLET_ID 4
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x002edd61 ; Overwriting code that checks value #12 in
; MSI record, and setting eax to 1
JUMPOVERBYTES 6
code_start
mov eax, 1 ; we set eax to 1 to simulate IsNull returning false
code_end
patchlet_end
0day #3: the "Windows Contacts arbitrary code execution"
This 0day was published by a ZDI researcher John Page after Microsoft had exceeded ZDI's 90-day window for fixing a reported issue. The issue was initially reported as related to VCF files (which are by default associated with the Windows Contacts application) but Page subsequently added that CONTACT files (also by default associated with Windows Contacts) can be used to achieve the same.
The issue is in the fact that almost any string provided via a VCF or CONTACT file in the web site URL or email value (yes, we figured this one out ourselves :) ends up being used as an argument to a ShellExecute call. While ShellExecute is a handy function for opening URLs in user's default browser, its problem is that before doing that, it tries to "launch" the provided string on the local computer; to illustrate, provided with "www.microsoft.com", ShellExecute would first attempt to locate and launch a local executable called www.microsoft.com, and only failing that, it would open www.microsoft.com in your browser. This behavior has produced many a vulnerability before (see this article for some examples) and will undoubtedly continue to bless us with more in the future. To exploit this issue, the attacker must get the user to open a malicious VCF or CONTACT file and click on the displayed web site or email link, which launches attacker's executable that must also be present on user's computer or a network share.
We analyzed what happens when the web site or email link is clicked, which led us to a SafeExecute function in wab32.dll. This function does some parsing of the URL to accommodate mshelp:// URLs, and then calls ShellExecute. We simply added some logic before this call to make sure that if the URL doesn't start with "mailto:", "http://" or "https://", it gets prepended with "http://" to prevent any possible launching of local executables. We have initially considered adding sanitization in the code that reads the file but then discovered that the code displaying the link is prone to, yes, HTML injection - which made any sanitization too complex and potentially bypassable.
We currently have this micropatch for fully updated 64-bit Windows 10 version 1803 and fully updated 64-bit Windows 7. (Contact support@0patch.com to express your interest in porting to other versions.)
You can see this micropatch in action in the following video. The video also nicely demonstrates how a micropatch can be applied to a running process: we enable the micropatch while Windows Contacts is displaying the malicious contact card, and the result of clicking on the link gets changed.
And here's the source code for this micropatch.
;Micropatch for wab32.dll version 6.1.7601.17699
;
;How it works:
; in SafeExecute an unsanitized pszUrl - a user-controlled parameter is passed to ShellExecute.
; This patch adds a series of checks for valid pszUrl prefixes and eventually,
; if no valid prefix is found, adds a http:// prefix to pszUrl.
MODULE_PATH "..\AffectedModules\wab32.dll_6.1.7601.17699\wab32.dll"
PATCH_ID 347
PATCH_FORMAT_VER 2
VULN_ID 4656
PLATFORM win64
patchlet_start
PATCHLET_ID 1
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x00087f7a
PIT SHLWAPI.dll!StrCmpNICW,msvcrt.dll!memmove
JUMPOVERBYTES 0
N_ORIGINALBYTES 1
code_start
;store registers:
push rsi
push rcx
push rdx
push r8
push r8 ; extra space for prefix variable
push rax ; store pszUrl
;check lpFile for valid prefixes:
call mailto
db __utf16__('mailto:'),0
mailto:
pop rcx ; pszStr1 = "mailto:"
mov rdx, rax ; pszStr2 = pszUrl
mov r8, 07h ; nChar
sub rsp, 20h ; homespace
call PIT_StrCmpNICW
test rax,rax
jz skip ; if found, exit patch
call https
db __utf16__('https://'),0
https:
pop rcx ; pszStr1 = "https://"
mov rdx, [rsp+20h] ; pszStr2 = pszUrl
mov r8, 08h ; nChar
call PIT_StrCmpNICW
test rax,rax
jz skip ; if found, exit patch
call http
db __utf16__('http://'),0
http:
pop rcx ; pszStr1 = "http://"
mov [rsp+28h], rcx ; store prefix
mov rdx, [rsp+20h] ; pszStr2 = pszUrl
mov r8, 07h ; nChar
call PIT_StrCmpNICW
test rax,rax
jz skip ; if found, exit patch
mov r8, 1024h ; num
mov rdx, [rsp+20h] ; Src = pszUrl
lea rcx,[rdx+0eh] ; Dst
call PIT_memmove ; moving pszUrl of max 1024h in size to *pszUrl+0eh
mov rcx, [rsp+20h] ; Dst = pszUrl
mov rdx, [rsp+28h] ; Src = "http://"
mov r8, 0eh ; num
call PIT_memmove ; copying http:// to *pszUrl
skip:
;revert stack:
add rsp, 20h ; revert homespace
pop rax
pop r8 ; blank pop
pop r8
pop rdx
pop rcx
pop rsi
code_end
patchlet_end
;
;How it works:
; in SafeExecute an unsanitized pszUrl - a user-controlled parameter is passed to ShellExecute.
; This patch adds a series of checks for valid pszUrl prefixes and eventually,
; if no valid prefix is found, adds a http:// prefix to pszUrl.
MODULE_PATH "..\AffectedModules\wab32.dll_6.1.7601.17699\wab32.dll"
PATCH_ID 347
PATCH_FORMAT_VER 2
VULN_ID 4656
PLATFORM win64
patchlet_start
PATCHLET_ID 1
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x00087f7a
PIT SHLWAPI.dll!StrCmpNICW,msvcrt.dll!memmove
JUMPOVERBYTES 0
N_ORIGINALBYTES 1
code_start
;store registers:
push rsi
push rcx
push rdx
push r8
push r8 ; extra space for prefix variable
push rax ; store pszUrl
;check lpFile for valid prefixes:
call mailto
db __utf16__('mailto:'),0
mailto:
pop rcx ; pszStr1 = "mailto:"
mov rdx, rax ; pszStr2 = pszUrl
mov r8, 07h ; nChar
sub rsp, 20h ; homespace
call PIT_StrCmpNICW
test rax,rax
jz skip ; if found, exit patch
call https
db __utf16__('https://'),0
https:
pop rcx ; pszStr1 = "https://"
mov rdx, [rsp+20h] ; pszStr2 = pszUrl
mov r8, 08h ; nChar
call PIT_StrCmpNICW
test rax,rax
jz skip ; if found, exit patch
call http
db __utf16__('http://'),0
http:
pop rcx ; pszStr1 = "http://"
mov [rsp+28h], rcx ; store prefix
mov rdx, [rsp+20h] ; pszStr2 = pszUrl
mov r8, 07h ; nChar
call PIT_StrCmpNICW
test rax,rax
jz skip ; if found, exit patch
mov r8, 1024h ; num
mov rdx, [rsp+20h] ; Src = pszUrl
lea rcx,[rdx+0eh] ; Dst
call PIT_memmove ; moving pszUrl of max 1024h in size to *pszUrl+0eh
mov rcx, [rsp+20h] ; Dst = pszUrl
mov rdx, [rsp+28h] ; Src = "http://"
mov r8, 0eh ; num
call PIT_memmove ; copying http:// to *pszUrl
skip:
;revert stack:
add rsp, 20h ; revert homespace
pop rax
pop r8 ; blank pop
pop r8
pop rdx
pop rcx
pop rsi
code_end
patchlet_end
So here they are, three micropatches for three 0days. We don't know if and when Microsoft is going to fix these issues (likely in the following months); meanwhile, if you have our Agent installed and registered, these micropatches are already on your computer and applied to all affected processes. Otherwise, register a free 0patch account and install 0patch Agent to get these micropatches applied. As always, once Microsoft fixes any of these 0days, its associated micropatch will automatically stop applying - in other words, you don't have to worry about future Windows updates.
Again, please note that we haven't ported these micropatches to all supported Windows versions (see each section above for version information); if you're interested in patching your particular version of Windows, don't hesitate contacting us at support@0patch.com.
Cheers!
@mkolsek
@0patch