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.

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.

This could be a good moment to enumerate all loadable protocols and see which apps accept arguments so we can try to inject code into them (binary or pure javascript, depending on how the app was coded and how it treats the arguments). There is a lot of interesting stuff here to play with, and if we keep searching for protocols we will find tons of apps that open (including Candy Crush which I didn’t know it was on my PC).

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?

[ Silently Patched on 2017-04-11 – Windows Creators Update ] Alternative version here: bypass the popup blocker on Microsoft Edge

What about the HTML5 Sandbox? If you are not familiar with it, it’s just a way to impose restrictions to a webpage using the sandbox iframe attribute or the sandbox http header. For example, if we want to render content inside an iframe and make sure it does not run javascript (not even open new tabs) we can just use this tag:

<iframe src=”sandboxed.html” sandbox></iframe>

And the rendered page will be completely restricted. Essentially it can only render HTML/CSS but no javascript or access to things like cookies. In fact, if we use the sandbox granularity and allow at least new windows/tabs, all of them should inherit the sandboxed attributes and opened links from that iframe will still be sanboxed. However, using the microsoft-edge protocol bypasses this completely.

[ Silently Patched on 2017-04-11 – Windows Creators Update ]

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.

I continued pressing F3 in regedit and found the read: protocol which called my attention because when reading its (javascript) source code it had the potential for a UXSS, but Edge kept crashing again and again while trying. It crashed too much. For example setting the location of an iframe to “read:” was enough to crash the browser including all tabs. Want to see it?

[ Silently Patched on 2017-04-11 – Windows Creators Update ]

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 1 2 3 4 5 6 7 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.

On the left screen we can type and execute JavaScript code quickly, on the right we have WinDbg prepared to reveal us what’s happening behind this crash. Go ahead, let’s run the JavaScript code and… Bang! WinDbg breaks.

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 1 2 3 4 5 6 7 8 9 ModLoad : ce 960000 ce 996000 C : \ Windows \ SYSTEM 32 \ XmlLite . dll ModLoad : c 4110000 c 4161000 C : \ Windows \ System 32 \ OneCoreCommonProxyStub . dll ModLoad : d 6 a 20000 d 6 ab 8000 C : \ Windows \ SYSTEM 32 \ sxs . dll ( 2c90.33f0 ) : Security check failure or stack buffer overrun - code c 0000409 ( ! ! ! second chance ! ! ! ) EdgeContent ! wil :: details :: ReportFailure + 0x120 : 84347de0 cd 29 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0 : 030 > k # Child - SP RetAddr Call Site 00 af 248 b 30 88087f80 EdgeContent ! wil :: details :: ReportFailure + 0x120 01 af 24 a 070 880659a5 EdgeContent ! wil :: details :: ReportFailure _ Hr + 0x44 02 af 24 a 0 d 0 8810695c EdgeContent ! wil :: details :: in 1 diag 3 :: FailFast _ Hr + 0x29 03 af 24 a 120 88101bcb EdgeContent ! CReadingModeViewerEdge :: _ LoadRMHTML + 0x7c 04 af 24 a 170 880da669 EdgeContent ! CReadingModeViewer :: Load + 0x6b 05 af 24 a 1 b 0 880da5ab EdgeContent ! CBrowserTab :: _ ReadingModeViewerLoadViaPersistMoniker + 0x85 06 af 24 a 200 880da882 EdgeContent ! CBrowserTab :: _ ReadingModeViewerLoad + 0x3f 07 af 24 a 240 880da278 EdgeContent ! CBrowserTab :: _ ShowReadingModeViewer + 0xb2 08 af 24 a 280 88079a9e EdgeContent ! CBrowserTab :: _ EnterReadingMode + 0x224 09 af 24 a 320 d 9 e 4 b 1 d 9 EdgeContent ! BrowserTelemetry :: Instance :: 2 :: dynamic 0a af 24 a 3 c 0 8810053e shlwapi ! IUnknown _ Exec + 0x79 0b af 24 a 440 880fee33 EdgeContent ! CReadingModeController :: _ NavigateToUrl + 0x52 0c af 24 a 4 a 0 88074f98 EdgeContent ! CReadingModeController :: Open + 0x1d3 0d af 24 a 500 b 07 df 508 EdgeContent ! BrowserTelemetry :: Instance ' :: 2 :: dynamic 0e af 24 a 5 d 0 b 0768 c 47 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) 1 2 3 4 5 6 7 8 9 10 11 12 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 r 8 , [ EdgeContent ! ` string ( 88261320 ) ] 8810694f mov r 9 d , eax 88106952 mov edx , 1Fh 88106957 call EdgeContent ! wil :: details :: in 1 diag 3 :: 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.

EdgeContent!_imp_SHCreateStreamOnFileEx

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 1 2 3 4 5 6 0 : 030 > ub EdgeContent ! CReadingModeViewerEdge :: _ LoadRMHTML + 0x7c EdgeContent ! CReadingModeViewerEdge :: _ LoadRMHTML + 0x5a : 8810693a ff 1568 f 91400 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 1 2 3 4 0 : 029 > bp EdgeContent ! CReadingModeViewerEdge :: _ LoadRMHTML + 0x5a 0 : 029 > g

This is exciting. We want to see the file name (or string) that RCX points to right before SHCreateStreamOnFileEx executes. Let’s run the code and feel the break. Well, I feel it baby =) breakpoints connect me to my childhood. Let’s run the JavaScript code and bang! WinDbg breaks right there.

Breakpoint 0 hit EdgeContent!CReadingModeViewerEdge::_LoadRMHTML+0x5a: 8820693a ff1568f91400 call qword ptr [EdgeContent!_imp_SHCreateStreamOnFileEx (883562a8)] 1 2 3 4 5 Breakpoint 0 hit EdgeContent ! CReadingModeViewerEdge :: _ LoadRMHTML + 0x5a : 8820693a ff 1568 f 91400 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 ................ 1 2 3 4 5 6 7 8 9 10 11 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 f 8 02 db 01 00 00 * . . . . . . . ` . . . . . . . 02fac948 10 a 9 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 f 3 e 0 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!@#$%^&*" 1 2 3 4 0 : 030 > du @ rcx 02fac908 "qwieiwqeiu;asjdiw!@#$%^&*"

Nice! The string rings me! Look at the JavaScript code that we’ve just ran.

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.

read:,c:\windows\system32\drivers\etc\hosts

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"); 1 2 3 4 5 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.

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!