Monday, September 24, 2018

Outrunning Attackers On The Jet Database Engine 0day (CVE-2018-8423)

Micropatching Makes It Possible To Create And Apply Patches Before Attackers Write a Reliable Exploit

by Mitja Kolsek, the 0patch Team


Timeline







 

Introduction


Last Thursday, Zero Day Initiative published details of an unpatched remotely exploitable vulnerability in all Windows versions (discovered by Lucas Leong) due to Microsoft missing their 120-day fixing window. We immediately tested ZDI's proof-of-concept and found the following:

  1. Jet is only supported in 32-bit, which means that a 64-bit application tricked into accessing a malformed data source file will not be exploitable. Indeed, double-clicking ZDI's poc.js on 64-bit Windows results in an error message; in order to launch poc.js on a 64bit machine one needs to use the 32-bit wscript.exe by launching c:\windows\SysWOW64\wscript.exe poc.js.
  2. Obviously, getting a user to launch a .js file is not a convincing attack scenario (such file could already do anything within user's privileges). The good news for attackers is that this attack can be mounted via Internet Explorer, especially since even on 64-bit Windows, Internet Explorer rendering processes are 32-bit. On the upside, we were unable to get the exploit working from a web site because - at least on IE11 - the security setting "Access data sources across domains" is disabled in Internet and Intranet zone, which resulted in a JavaScript error. Launching a malicious poc.html from a local drive (or USB disk) does work, however, whereby the accompanying data source file can be in a shared folder and doesn't need to be delivered with poc.html. Nevertheless, the user then has to press the "Allow blocked content" button, which amounts to a considerable level of social engineering required to execute the attack via Internet Explorer.
  3. A more realistic attack could probably be conceived using a malicious Office document referencing an external malformed Jet data source. We haven't investigated that, however, as our job is not to write exploits but micropatches. (Resourceful attackers will soon reveal their weaponization ideas anyway.)

Vulnerability Analysis


As usually, we started our analysis from the closest observable point of failure and worked backward to the vulnerable code. Ideally, the "closest observable point of failure" is a process crash, and in this case, ZDI's PoC indeed causes a crash in wscript.exe due to an attempt to write past the allocated memory block. So their PoC was perfect for us. (Not surprisingly, it's easier for us to work with a crash case than a full blown calc-popping exploit.) Here's how the crash looks like in WinDbg, with Page Heap enabled and invalid memory access in function TblPage::CreateIndexes:


eax=00002300 ebx=00000002 ecx=00834918 edx=00000000 esi=008360a8 edi=0024d64c
eip=6b881234 esp=0024d4c0 ebp=07ac427c iopl=0         nv up ei pl nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010212
msrd3x40!DllRegisterServer+0x1b644:
6b881234 89b48174050000  mov     dword ptr [ecx+eax*4+574h],esi ds:002b:0083da8c=????????


For anyone with some mileage in reverse engineering, the instruction causing the exception indicates that the code is accessing an indexed element in an array containing 4-byte elements, whereby the array starts at ecx+574h and the index is in eax (which contains 2300h). And it's clear that the index is beyond the array's limits.

From a patcher's perspective, this raises the following questions:

1) Where does this index come from? (And does it affect anything else, which would cause a micropatch to simply shift the problem further down the code and prompting another micropatch for that?)

2) Does the array have a fixed size? (Which would mean a simple hard-coded index validation micropatch could suffice.)

In tracking the index value 2300h, we found that eax was read from address esi+24h a few instructions back. We then used !heap to find out who allocated it and set an access breakpoint to see who wrote this value there. That turned out to be function Index::Restore, which is called a couple of instruction earlier in TblPage::CreateIndexes. What happens there is value 2300h is copied to the location we're watching with our access breakpoint from address pointed to by edx. And edx at that moment points to the memory-mapped copy of the malformed data source file.

So we found out that the malformed value 2300h comes directly from the PoC file group1, specifically at offset 1257h. Furthermore, looking around the code for additional uses of this user-provided index, we found one case immediately after the crash location, so whatever we'd do we'd need to make sure that this second use wouldn't also cause problems.

An obvious solution to fixing both uses of the malformed index was to check the index immediately after it gets copied from the memory-mapped file (i.e., right after the call to Index::Restore), and setting it to some safe value in case it was out of bounds. But in order to check the bounds we'd need to know the bounds.

Using !heap on ecx at the point of crash we inspected who allocated the memory block from which the PoC has escaped, and how large that block was. We found it to be a fixed-size block of 778h bytes, most likely a C++ object containing a fixed-size array of 4-byte elements. But how many elements? Let's calculate: the array begins at offset 574h in the memory block, and ends at most at offset 778h-1. The difference between the two means there is at most 204h bytes in the array, and dividing that by 4 (bytes) we get 81h elements, meaning that valid index values are between 00h and (at most) 80h.

Note that we say "at most 80h" - at this point we can't know whether the entire space between offset 574h and the end of the memory block is reserved for the array (although it surely seems so), we just calculated the bounds for preventing the offending instruction from escaping the memory block. And we'll be okay with this for now: we'll prevent an exploit from overwriting heap management data or other objects allocated on the heap, which will take most (if not all) of the leverage from the attacker.


The Micropatch 


So how should the micropatch look like? It would have to be injected right after the call to Index::Restore and check that the user-supplied value that was written to [esi+24h] doesn't exceed 80h. If it did exceed 80h, the patch would have to correct the value at [esi+24h], say by overwriting it with 0 (which is a valid index).  And here is the source code of this micropatch:


; Patch for VULN-4112 in msrd3x40.dll version 4.0.9801.0 32bit
MODULE_PATH "..\AffectedModules\msrd3x40.dll_4.0.9801.0_32bit\msrd3x40.dll"
PATCH_ID 1000010
PATCH_FORMAT_VER 2
VULN_ID 4112
PLATFORM win32


patchlet_start
 PATCHLET_ID 1
 PATCHLET_TYPE 2
 PATCHLET_OFFSET 0x4118C
 code_start

   cmp dword [esi+24h], 0x80  ; is the index lower than or equal to 0x80?
   jbe pass                   ; if not, we let it pass
                              ; (jbe means unsigned compare, we don't want to allow
                              ; values like 0x80002300 to be considered negative)
   mov dword [esi+24h], 0     ; otherwise we set it to 0
   call PIT_ExploitBlocked    ; and show the Exploit Blocked popup
  pass:

 code_end
patchlet_end



We initially wrote this micropatch for Windows 7, and had a candidate ready just 7 hours after ZDI had published their PoC. After attempting to port it to other supported Windows versions, we noticed that almost all of them have the exact same version of msrd3x40.dll - which meant the same micropatch would apply to all these systems. The only Windows version with a different msrd3x40.dll was Windows 10: peculiarly, both DLLs had the same version and exactly the same size, but plenty of small differences between the two (including the link timestamp). The code was exactly the same and in the same place though (probably just a re-build), so we could actually use the exact same source code for the micropatch, just a different file hash.

These two micropatches for a published 0day were then issued less than 24 hours after the 0day was dropped, and distributed to our users' computers within 60 minutes, where they were automatically applied to any running process with vulnerable msrd3x40.dll loaded. Which nicely demonstrates the speed, simplicity and user-friendliness of micropatching when it comes to fixing vulnerabilities. In fact one of our goals with 0patch is to make vulnerability patching so fast that attackers won't even manage to develop a reliable exploit for a public vulnerability, much less launch a campaign with it, before the vulnerability is already patched on most users' computers. What a goal, huh?


How Can You Get These Micropatches, And What To Do Next?


These micropatches are completely free for everyone, and can be obtained by installing and registering 0patch Agent from https://0patch.com. They will prevent exploitation of this 0day as long as you have the latest Windows updates applied (we did not port them to older Windows updates, but if you have a need for that you can contact us at support@0patch.com).

Once Microsoft issues their official fix (likely on October Patch Tuesday), you will simply apply that update and these micropatches will automatically get obsoleted without you having to do anything else.


Cheers!

@mkolsek
@0patch

Tuesday, September 11, 2018

Comparing Our Micropatch With Microsoft's Official Patch For CVE-2018-8440


by Mitja Kolsek, the 0patch Team


As expected, Windows Update has just brought the official patch for CVE-2018-8440 today, a patch that would replace our "unofficial" micropatch we've issued 13 days ago.

To quickly refresh your memory, 15 days ago security researcher SandboxEscaper published details and proof-of-concept (POC) for a "0day" local privilege escalation vulnerability in Windows Task Scheduler service, allowing a local unprivileged user to change permissions of any file on the system. The next day, we at 0patch have analyzed the vulnerability and prepared a "micropatch candidate" (a micropatch that blocks the POC but needs to be tested for functional side effects). We published the micropatch for Windows 10 version 1803 the day after, and subsequently ported this micropatch to a number of other Windows versions.

Obviously, correcting someone else's code is not to be taken lightly (especially if that code is running on millions of computers), and not having the source code doesn't make it any easier. However, our team of security researchers and the growing community of experts who want to get their vulnerabilities fixed as quickly and efficiently as possible, are doing just that.

In this particular case, our analysis showed that a call for changing permissions of a file, made from Task Scheduler's code, should have been impersonated - but it wasn't. We corrected that by making a call to RpcImpersonateClient right before the call, and another call to RpcRevertToSelf right after it. Pretty basic stuff. And it worked.

Our micropatches are sometimes very similar to official patches (at least functionally speaking), and other times very different; not surprisingly, there is always more than one way to skin a cat - or to fix a vulnerability. So when the official patch from Microsoft came out today, we were naturally curious as to how they fixed it.

The first thing we noticed was that the Windows Update replaced schedsvc.dll with a new version. Promising - this is the exact DLL we had micropatched. Next we ran BinDiff to compare the new and the old version. Even more promising - the only modified function was RpcServer::SetSecurity, which is the very function we had micropatched. And finally, BinDiff showed the exact difference between the old and the new version of this function. Let's take a look at that (note: the diff is for 64bit Windows 7).




 
As you can see, Microsoft's official patch is functionally identical to our micropatch. We could say that this official patch delivers Microsoft's unofficial confirmation that our approach to patching this vulnerability - in a complex closed-source product - was also optimal from their perspective. That's very positive feedback for us and a great case for "unofficial" 3rd-party micropatches.

Don't get carried away, however! There have been, and will be cases where a 3rd-party micropatch will fix a vulnerability differently and less well than the official vendor fix - or even break a thing or two. While we're doing our best and leverage our advantages (agility, instant patch deployment, instant patch removal), original product developers will always have advantage in their knowledge about the product and its environment, and their code fixes will be preferred. Nevertheless, our mistakes are much easier to remedy than those delivered in a 300MB package that requires a reboot both to install and uninstall. When a micropatch needs to be revoked, it takes just a few minutes for us to do that, and within an hour all online agents stop applying it without interrupting users in the slightest. And an improved version of the micropatch can later be delivered just as easily.

In any case, if you have installed 0patch Agent to micropatch this vulnerability on your computer(s), it is now time to let the official fix take over. To do that, you simply apply today's Windows Updates and as soon as they replace the vulnerable schedsvc.dll, our micropatch will automatically stop applying because the cryptographic hash of the DLL will no longer match that associated with the micropatch. You don't have to do anything else; this is 0patch being a good citizen and stepping aside when it's no longer your best option.


Cheers!

@mkolsek
@0patch