Detecting Local Files to Evade Analysts (IE)

Last month we’ve been looking at how attackers were targeting unsavvy users by checking the associated mimeTypes to applications on the system. If the PC had analyst tools installed, something detected from withing the browser, then the malware refused to download the bad bits staying below the radars for a longer period of time. The vendor patched the bug but unfortunately bypassing it is quite easy by just combining what attackers already have.

Today we are going to work with another fingerprinting bug which allows attackers to check the presence of files in our PCs. According to the fellows at Proofpoint, Microsoft finally patched this information disclosure bug which was being used in different malvertising campaigns and exploit kits. If you have no idea about this, let me recap in two lines:

Proofpoint discovered at least two information disclosure bugs (CVE-2016-3351, CVE-2016-3298) exploited on the wild which were reported to Microsoft between October and December of 2015. Microsoft patched CVE-2016-3351 (mimeType disclosure) in September 2016 and CVE-2016-3298 (file existence check) in October 2016. Unfortunately both patches were bypassed in less than a day.

The vendor claims users need to navigate to a malicious site, but it is not really the case. Both bugs were found in banners running in mainstream sites. Today with all these malvertising campaigns there’s no need to convince the user to go to an evil site.

Anyway, let’s get started. We will first study one of the techniques that attackers are using, test it with the patch, and and finally bypass it again. In fact, we will be nice fellows and take the road less traveled by finding a variation and not a completely different route to achieve our goal. This means that we will be using something pretty similar to what attackers are doing now and not anything new. If we employ a completely different technique, it would be perfectly valid but then we shouldn’t be calling this a bypass but just a new way to achieve the same old thing: detecting the presence of local files.


How Attackers Exploited CVE-2016-3298

There is a well known method to detect the presence of binary files (exe, dll, cpl, etc) by loading their internal resources and checking if events like onload/onreadystate/onerror fire. Essentially, IE uses internal resources to load information pages, error messages, and even icons. Those resources are embedded inside binary files, but can be loaded separately with no problems.

The most basic example is the error page that IE renders when we attempt to load an invalid URL. For example, if we type http://invalidsite in the address bar, IE will show us this error page:

Invalid Site

In the address bar we can read http://invalidsite/ but the browser has rendered an error page which is obviously not coming from that site. Let’s find out the real URL by checking its properties: right click anywhere on the page and select “Properties” from the context menu to reveal the truth.

Real URL


Loading Resources

As we can see, the content is really coming from res://ieframe.dll/dnserror.htm#http://invalidsite/ where “res:” stands for resource and is the way IE has to load internal resources from binary files. The format is very simple: res://resourcefile.ext/resourceitem. By default the browser assumes the resource file is in windows/system32, but we can set the full path to any file on the system. For example, if we have Fiddler installed we could simply find a valid resource from one of its files and render it on IE. Then, the readystate event will inform us if the resource was loaded or not.

If we use a tool like the free Resource Hacker, we can openly read the resource names/content and load them later as images, html files, etc. Check out how the embedded dnserror.htm is uncovered by Resource Hacker.

Resource Hacker

Note: we loaded ieframe.dll.mui in Resource Hacker because ieframe.dll takes some of its resources from that file, however most binary files load the resources from themselves so we can just drop the file in resource hacker and find everything immediately.

In fact, we don’t even need resource hacker to find a valid resource, because there are default resources in all binary files that have constant values which never change. For example, the file information of all binary files can be found in the resource /16/1 (16 == RT_VERSION). To be clear, it’s easier for us to load a default resource than having to go over every file that we want to test. For example, in Fiddler (or any binary file) we can easily render the default information text which conveniently fires a readystatechange event.

res://c:\Program Files (x86)\Fiddler2\Fiddler.exe/16/1


PoC Before the Patch

To exploit this bug, we should load the resource inside an iFrame and count how many times the onreadystate event fires. If it fires once the file exists, two it does not. Don’t waste your time trying to get the states (loading, complete, etc) of the readystate event because that’s not unreliable. Counting is 100% sure fire: one event fired == exists, two events == does not exist.

Here’s the code, but you can download a working PoC (password: infected) if you prefer. I’m not placing it live because my hosting detects it as a virus, and who wants to fight with a hosting named HostWithLove? No way! =)

<iframe src="res://c:\Program Files (x86)\Fiddler2\Fiddler.exe/16/1" onreadystatechange="count++"></iframe>

count = 0;

// This onload gives the iFrame a bit of time to load.
// But has nothing to do with the method itself
window.onload = function()
  if (count == 1) alert("File exists!");
  else alert("File does not exist");

The PoC above worked until last week but Microsoft patched it so we need to work around a variation. IE still allows internal resources to be loaded (for example, from ieframe.dll) so the URL res://ieframe.dll/16/1 works because it is considered an internal resource, but we can’t do the same with other files. In fact, if we disassemble IE we will find a string comparison against ieframe.dll that does not even consider the full path to the file, so if we try to load a valid resources from res://c:\windows\system32\ieframe.dll/16/1 it will also fail.

It seems that developers got inspired by Jérôme Segura’s post, where he describes possible mitigations and even if I believe the fix is good, it did not consider alternatives. One thing is to patch a buffer overflow where you can get away by changing a line of code, but a different thing is a design bug which needs to be tested from different perspectives.

Anyway, we know that IE refuses to load interesting files using the res: protocol, so what can we do? First, let’s take a look at the attackers code. Sorry if the picture below is pixelated, it’s a graphic that I took from the impressive Proofpoint research and zoomed it a bit to make it clearer for us. Yellow is mine.


Finding a Variation

The malware authors are using three different techniques here to detect the presence of local files, but all of them are patched now. If you are interested in the details, read this fantastic explanation. Either way, the first thing that called my attention was the mhtml:file because even if the file: protocol is apparently disabled on IE, the mhtml still works. I think I’ve been influenced by a great post from Alex (insertScript) which I read a month ago, so as soon as I sumbled upon the mhtml protocol in the attackers code, I got the idea of combining mhtml with res.To be clear, we are going to mix two methods that don’t work anymore by themselves, hoping that the mix will create something useful out of them.

So neither mhtml:file: or res:// work now. But what would happen if we mix them together? mhtml:res://

<iframe src="mhtml:res://c:\Program Files (x86)\Fiddler2\Fiddler.exe/16/1" onload="count++"></iframe>

count = 0;

window.onload = function()
  if (count == 1) alert("File exists!");
  else alert("File does not exist");

If you are on IE, check out the PoC LIVE.

Update 2016-10-24: yet another variation of the same thing, but simpler: PoC CVE-2013-7331 bypass. This is a trivial bypass of the XMLDom version widely used in exploit kits.

It seems attackers will continue fingerprinting us regardless of the patch. The variation was easy but taking a closer look we can see that onreadystatechange was replaced by onload, because the former didn’t fire. On the other hand, IE failed to render the resource the first time that loaded it, so we are now setting the iFrame location twice using the exact same mhtml:res URL.

Clearly attackers will find their way again and again until this bug is patched all around, completely. If we take a look at the times this bug resurfaced in the past we can know in advance that the bad guys are not tired of trying. For example, here we have two other versions of the same vulnerability with slight variations: CVE-2013-7331 and CVE-2015-2413, both exploited in the past and for sure there are more.

I personally stopped here because we are doing it for fun, to learn and share, and hopefully to make my friends at Microsoft change their minds on the priority of these vulnerabilities. But attackers won’t stop here, they will keep finding more tricks (backup-ups of the same thing) to be ready after a new patch. See how it works? Attackers are ahead of us with more variations of the same thing, while we are still dealing with ~year old versions of this vulnerability.

Final thoughts and details

If you are interested to go deeper into the bits of the patch, an interesting starting point would be to check both COmWindowProxy::CanNavigateToUriWithZoneCheck and IsIEResourcePage from mshtml.dll.

If you prefer to go with pure JavaScript let me suggest you to set the location of the iframe using the method instead of iframe.location. Setting it with the regular location/location.href will make IE gracefully refuse to load the resource (rendering an about:blank instead). However, using“res://something”, “iframename”) will throw a convenient access denied error letting us use a try/catch to fuzz it properly without the need to wait for the iframe to change its URL (and wait for an onload).

<iframe name="ifr"></iframe>

// No errors, about:blank loaded in iFrame, very slow.
ifr.location = "res://testing.dll";

// Access Denied, nothing changes in the iFrame, super-fast."res://testing.dll", "ifr");

// This last option can be used with a try/catch creating
// a battery of tests that will immediately return
// the result.

Have a nice day!