On October 25th, the fellows @MSEdgeDev twitted a link that called my attention because when I clicked on it (being on Chrome) the Windows Store App opened. It might not surprise you, but it surprised me!
As far as I remembered, Chrome had this healthy habit of asking the user before opening external programs but in this case it opened directly, without warnings.
This was different and caught my attention because I never accepted to open the Windows Store in Chrome. There are some extensions and protocols that will open automatically but I’ve never approved the Windows Store.The shortened Twitter link redirected to https://aka.ms/extensions-storecollection which (again) redirected to the interesting: ms-windows-store://collection/?CollectionId=edgeExtensions.
It was a protocol that I was not aware of, so I immediately tried to find it in the place where most protocol associations reside: the registry. A search of “ms-windows-store“ immediately returned our string inside the PackageId of the what seemed to be the Windows Store app.
Noting that we were also in a key called “Windows.Protocol” I scrolled up and down a bit to see if there were other apps inside, and found that tons of them (including MS Edge) had their own protocols registered. This is nice because it opens a new attack surface straight from the browser. But let’s press F3 to see if we find other matches.
It seems that the ms-windows-store: protocol is also accepting search arguments, so we can try opening our custom search straight from Google Chrome. In fact, the Windows Store app seems to be rendering HTML with the Edge engine, which is also interesting because we might try to XSS it or if the app is native, send big chunks of data and see what happens.
But we won’t be doing that now, let’s go back to regedit and press F3 to see what else we can find.
This one is interesting also, because it gives us clues on how to quickly find more protocols if they are prepended with the string “URL:”. Let’s reset our search to “URL:” and see what we get. Pressing the [Home] key takes us back to the top of the registry and a search of “URL:” immediately returns the first match “URL:about:blank“, confirming that we are not crazy.
Press F3 again and we find the bingnews: protocol but this time Chrome requests us confirmation to open it. No problem, let’s try it on Edge to see what happens. It opens! Next match in the registry is the calculator: protocol. Will this work?
Wow! I’m sure this will piss off exploit writers. What program will they pop now? Both calc and notepad can be open without memory corruptions, and cmd.exe is deprecated in favor to powershell now. Microsoft removed the fun 😛 out of you guys.
By pressing F3 a few times I learned a lot. For example, there’s a microsoft-edge: protocol that loads URLs in a new tab. It doesn’t seem to be important, until we remember the limits that HTML pages should have. Will the popUp blocker prevent us from opening 20 microsoft-edge:http://www.google.com tabs?bypass the popup blocker on Microsoft Edge.
<iframe src=”sandboxed.html” sandbox></iframe>
Nice to see that the microsoft-edge protocol allows us to bypass different restrictions. I haven’t went further than that but you can try! This is a journey of discovery, remember that a single tweet fired my motivation to play a bit and ended up giving us stuff that truly deserves more research.
OK, I was curious about what was happening so I appended a few bytes to the read protocol and fired up WinDbg to see if the crash was related to invalid data. Something quick and simple, no fuzzing or anything special: read:xncbmx,qwieiwqeiu;asjdiw!@#$%^&*
Oh yes, I really typed something like that. The only way that I found not to crash the read protocol was to load anything coming from http[s]. Everything else crashed the browser.
So let’s attach WinDbg to Edge. A quick dirty method that I use it to simply kill the Edge process and children, reopen it and attach to the latest process that uses EdgeHtml.dll. Of course there are easier ways but … yeah, I’m just like that. Open a command line and…
taskkill /f /t /im MicrosoftEdge.exe ** Open Edge and load the webpage but make sure it doesn't crash yet ** tasklist /m EdgeHtml.dll
Enough. Now load WinDbg and attach to the latest listed Edge process that uses EdgeHtml. And remember to use Symbols in WinDbg.
Once attached, just press F5 or g [ENTER] inside WinDbg so Edge keeps running. This is how my screen looks right now. On the left I have the page that I use to test everything and on the right, WinDbg attached to that particular Edge process.
We will use a window.open to play with the read: protocol instead of an iframe because it’s more comfortable. Think about it, there are protocols/urls that might end up changing the top location regardless of how framed they are.
If we start playing with a protocol inside an iframe there are chances that our own page (the top) will be unloaded, losing the code that we’ve just typed. My particular test-page saves what I type, and if the browser crashes it’s highly likely that it will be recovered. But even with everything saved, when I’m playing with code that could change the URL of my test-page, I open it in a new window. Just a habit.
ModLoad: ce960000 ce996000 C:\Windows\SYSTEM32\XmlLite.dll ModLoad: c4110000 c4161000 C:\Windows\System32\OneCoreCommonProxyStub.dll ModLoad: d6a20000 d6ab8000 C:\Windows\SYSTEM32\sxs.dll (2c90.33f0): Security check failure or stack buffer overrun - code c0000409 (!!! second chance !!!) EdgeContent!wil::details::ReportFailure+0x120: 84347de0 cd29 int 29h
OK, it seems that Edge knew something went wrong because it’s in a function called “ReportFailure”, right? Come on, I know we can immediately assume that if Edge is here, it failed somewhat “gracefully”. So let’s inspect the stack trace to see where are we coming from. Type “k” in WinDbg.
0:030> k # Child-SP RetAddr Call Site 00 af248b30 88087f80 EdgeContent!wil::details::ReportFailure+0x120 01 af24a070 880659a5 EdgeContent!wil::details::ReportFailure_Hr+0x44 02 af24a0d0 8810695c EdgeContent!wil::details::in1diag3::FailFast_Hr+0x29 03 af24a120 88101bcb EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x7c 04 af24a170 880da669 EdgeContent!CReadingModeViewer::Load+0x6b 05 af24a1b0 880da5ab EdgeContent!CBrowserTab::_ReadingModeViewerLoadViaPersistMoniker+0x85 06 af24a200 880da882 EdgeContent!CBrowserTab::_ReadingModeViewerLoad+0x3f 07 af24a240 880da278 EdgeContent!CBrowserTab::_ShowReadingModeViewer+0xb2 08 af24a280 88079a9e EdgeContent!CBrowserTab::_EnterReadingMode+0x224 09 af24a320 d9e4b1d9 EdgeContent!BrowserTelemetry::Instance::2::dynamic 0a af24a3c0 8810053e shlwapi!IUnknown_Exec+0x79 0b af24a440 880fee33 EdgeContent!CReadingModeController::_NavigateToUrl+0x52 0c af24a4a0 88074f98 EdgeContent!CReadingModeController::Open+0x1d3 0d af24a500 b07df508 EdgeContent!BrowserTelemetry::Instance'::2::dynamic 0e af24a5d0 b0768c47 edgehtml!FireEvent_BeforeNavigate+0x118
Check out the first two lines, both called blah blah ReportFailure, don’t you think Edge is here because something went wrong? Of course! Let’s keep going down until we find a function name that makes sense. The next one is called blah FailFast which also smells like it’s a function Edge called knowing that something went wrong. But we want to find the code that made Edge unhappy so continue reading down.
The next one is blah _LoadRMHTML. This looks much better to me, don’t you agree? In fact, its name makes me think it Loads HTML. It would be interesting to break before the crash, so why not setting a breakpoint a few lines above _LoadRMHTML? We inspected the stack-trace, now we will look at the code.
Let’s first unassemble back from that point (function + offset). It’s easy, using the “ub” command in WinDbg.
0:030> ub EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x7c EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a: 8810693a call qword ptr [EdgeContent!_imp_SHCreateStreamOnFileEx (882562a8)] 88106940 test eax,eax 88106942 jns EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x7d (8810695d) 88106944 mov rcx,qword ptr [rbp+18h] 88106948 lea r8,[EdgeContent!`string (88261320)] 8810694f mov r9d,eax 88106952 mov edx,1Fh 88106957 call EdgeContent!wil::details::in1diag3::FailFast_Hr (8806597c)
We will focus on the names only and ignore everything else, OK? Just like when we were trying to find a variation for the mimeType bug, we are going to speculate here and if we fail we would of course keep going deeper. But sometimes a quick look on the debugger can reveal many things.
We know that Edge will crash if it arrives to the last instruction of this snippet (address 88106957, FailFast_Hr). Our intention is to see why we ended up at that location, who the hell is sending us there. The first instruction of the code above seems to be calling a function with a complicated name which apparently reveals us tons of stuff.
The first part before the ! is the module (exe, dll, etc) where this instruction is located. In this case it is EdgeContent and we don’t even care about its extension, it’s just code. After the ! comes a funny name _imp_ and then SHCreateStreamOnFileEx which seems to be a function name that “creates a stream on file”. Do you agree? In fact, the _imp_ part makes me think that maybe this is an imported function loaded from a different binary. Let’s google that name to see if we find something interesting.
That’s pretty nice. The first result came with the exact name that we searched for. Let’s click on it.
OK. The first parameter that this function receives is a “A pointer to a null-terminated string that specifies the file name“. Interesting! If this snippet of code is being executed, then, it should be receiving a pointer to a file name as the first argument. But how can we see the first parameter? It’s easy, we are working on Winx64, and the calling convention / parameter passing says that “First 4 parameters are RCX, RDX, R8, R9” (speaking about integers/pointers). This means that the first parameter (pointer to a file name) will be loaded in the register RCX.
With this information, we can set a breakpoint before Edge calls that function and see what the RCX has at that precise moment. But let’s restart because it’s a bit late at this point: Edge already crashed. Please, re-do what’s described above (kill Edge, open it, load the page, find the process and attach).
This time, instead of running (F5) the process, we will set a breakpoint. WinDbg revealed the exact offset when we executed our “ub” command.
0:030> ub EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x7c EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a: 8810693a ff1568f91400 call qword ptr [EdgeContent!_imp_SHCreateStreamOnFileEx (882562a8)] 88106940 85c0 test eax,eax
So the breakpoint should go in EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a
We type “bp” and the function name + offset [ENTER]. Then “g” to let Edge run.
0:029> bp EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a 0:029> g
Breakpoint 0 hit EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a: 8820693a ff1568f91400 call qword ptr [EdgeContent!_imp_SHCreateStreamOnFileEx (883562a8)]
That’s great, now we can inspect the content where RCX is pointing. To do this we will use the “d” command (display memory) @ and the register name, just like this:
0:030> d @rcx 02fac908 71 00 77 00 69 00 65 00-69 00 77 00 71 00 65 00 q.w.i.e.i.w.q.e. 02fac918 69 00 75 00 3b 00 61 00-73 00 6a 00 64 00 69 00 i.u.;.a.s.j.d.i. 02fac928 77 00 21 00 40 00 23 00-24 00 25 00 5e 00 26 00 w.!.@.#.$.%.^.&. 02fac938 2a 00 00 00 00 00 08 00-60 9e f8 02 db 01 00 00 *.......`....... 02fac948 10 a9 70 02 db 01 00 00-01 00 00 00 00 00 00 00 ..p............. 02fac958 05 00 00 00 00 00 00 00-00 00 00 00 19 6c 01 00 .............l.. 02fac968 44 14 00 37 62 de 77 46-9d 68 27 f3 e0 92 00 00 D..7b.wF.h'..... 02fac978 00 00 00 00 00 00 08 00-00 00 00 00 00 00 00 00 ................
This isn’t nice on my eyes but on the right of the first line I see something which looks similar to a unicode string. Let’s display again as unicode (du).
0:030> du @rcx 02fac908 "qwieiwqeiu;asjdiw!@#$%^&*"
It seems that the argument passed to this function is whatever we type after the comma. With this knowledge plus knowing that it is expecting a file, we can try a full path to something in my drive. Because Edge runs inside an AppContainer, we will try a file that’s accessible. For example something from the windows/system32 directory.
We are also removing the garbage before the comma which seems unrelated (albeit it deserves more research!). Let’s quickly detach, restart Edge, and run our new code
url = "read:,c:\\windows\\system32\\drivers\\etc\\hosts"; w = window.open(url, "", "width=300,height=300");
And as expected, the local file loads in the new window without crashes.Patched on 2017-03-14 – CVE-2017-0154 ]
Fellow bug hunter, I will stop here but I believe all these things deserve a bit more of research depending on what’s fun for you:
A) Enumerate all loadable protocols and attack those applications via query-strings.
B) Play with microsoft-edge: which bypasses the HTML5 sandbox, popup blocker and who knows what else.
C) Keep going with the read: protocol. We found a way to stop it from crashing but remember there is a function SHCreateStreamOnFileEx expecting things that we can influence! It’s worth trying more. Also, we can continue working on the arguments to see if commas are used to split arguments, etc. If debugging binaries is boring for you, then you can still try to XSS the reading view.
I hope you find tons of vulnerabilities! If you have questions, ping me at @magicmac2000.
Have a nice day!
Reported to MSRC on 2016-10-26
Subject: MS Edge HTML5 Sandbox Escapes
Hey fellows! Here’s an Edge Sandbox escape. http://www.cracking.com.ar/demos/sandboxedge
IMO, it’s a bad idea to let all those protocols run straight out of the box because the attack surface of Edge ends up being huge, but hey, it’s your browser! =)