Attackers love being able to fingerprint their victims. We’ve seen in the past two techniques that allowed the bad guys to detect the presence of particular files (to evade analysts) and even get the application names associated to specific mimeTypes. Microsoft patched those ones and other variations but today we are going to see how to detect browser extensions installed on Edge.

Yesterday, Nataliia Bielova tweeted about a site that shows how extensions can be detected on different browsers, however, Microsoft Edge is excluded.

By the way, terrific research and proof of concept from Inria Privatics, Gábor Gulyás, and Nataliia Bielova.

If you want to learn more about how to detect extensions on Firefox and Chrome, read this paper describing the methodology or visit the amazing Inria site above, but here we will try to achieve the same on Microsoft Edge.

Installing an Extension

In the extensions store I randomly selected the AdGuard blocker. After two clicks it was installed and automatically opened a thank-you page that gave me ideas before even starting the research. Check it out.

That URL is all we need to get started. If we can load it in an iframe and detect its presence (onload/onreadystatechange/whatever) to distinguish it between a default Edge 404 page, we are almost done. But unfortunately iframes refuse to load the ms-browser-extension: protocol.

<iframe src="ms-browser-extension://EdgeExtension_AdguardAdguardAdBlocker_m055xr0c82818/pages/thankyou.html"></iframe> 1 2 3 <iframe src = "ms-browser-extension://EdgeExtension_AdguardAdguardAdBlocker_m055xr0c82818/pages/thankyou.html" > </iframe>

OK. What about window.open? Can we at least open those URLs from a script? Let’s try!

win = window.open("ms-browser-extension://EdgeExtension_AdguardAdguardAdBlocker_m055xr0c82818/pages/thankyou.html"); // returns null when the URL is ms-browser-extension: (not useful for us) 1 2 3 4 win = window . open ( "ms-browser-extension://EdgeExtension_AdguardAdguardAdBlocker_m055xr0c82818/pages/thankyou.html" ) ; // returns null when the URL is ms-browser-extension: (not useful for us)

It opens! But, it is not really useful for us because the window.open() returns null instead of a window object, with or without the installed extension. In other words, when we try to open a new window using the extension-protocol ms-browser-extension: it will always return null. So even if we can actually open the window (which by the way is an ugly solution), we have no way to check if the content was loaded.

What about images from the extensions? Maybe images are exposed to the main page and using the onload/onerror events we can detect if the extension is installed. Let’s first find out where the extension files are located in our file-system.

Locating the Extension Files

Fire up Process Monitor and include MicrosoftEdgeCP.exe in the filters. Close Edge and load the thank-you page from AdGuard by simply pasting the URL into Edge address bar. Bang! We can immediately see in Process Monitor where the files are located. Remember, our goal is to find an image from this extension to see if we can load it and detect its presence via onload/onerror.

It seems that the extension files are located here:

C:\Program Files\WindowsApps\Adguard.AdguardAdBlocker_2.5.18.0_neutral__m055xr0c82818\Extension\Pages 1 2 3 C : \ Program Files \ WindowsApps \ Adguard . AdguardAdBlocker _ 2 . 5 . 18 . 0 _ neutral _ _ m055xr0c82818 \ Extension \ Pages

Going to the previous (parent) folder, we can see that just like in Chrome and Firefox, there’s a manifest.json which allows a few resources to be loaded by any website.

"web_accessible_resources": [ "elemhidehit.png", "lib/content-script/assistant/css/assistant.css", "lib/content-script/assistant/i/close.svg", "lib/content-script/assistant/i/logo.svg", // Let's use this one! "lib/content-script/assistant/i/logo-white.svg" ] 1 2 3 4 5 6 7 8 9 "web_accessible_resources" : [ "elemhidehit.png" , "lib/content-script/assistant/css/assistant.css" , "lib/content-script/assistant/i/close.svg" , "lib/content-script/assistant/i/logo.svg" , // Let's use this one! "lib/content-script/assistant/i/logo-white.svg" ]

Many of them are images, let’s try to load logo.svg with a quick script. If onload fires the extension is installed, otherwise it means the user does not have it.

var img = new Image(); img.onload = function(){alert("Extension Detected")} img.onerror = function(){alert("Extension NOT Detected")} img.src = "ms-browser-extension://EdgeExtension_AdguardAdguardAdBlocker_m055xr0c82818/lib/content-script/assistant/i/logo.svg"; 1 2 3 4 5 6 7 8 var img = new Image ( ) ; img . onload = function ( ) { alert ( "Extension Detected" ) } img . onerror = function ( ) { alert ( "Extension NOT Detected" ) } img . src = "ms-browser-extension://EdgeExtension_AdguardAdguardAdBlocker_m055xr0c82818/lib/content-script/assistant/i/logo.svg" ;

This does not impress me, at all. We are essentially doing nothing new and just like the other detection methods (for Chrome/FF), we depend on the extension cooperation to allow resources to be loaded. If there is an extension that does not have the web accessible resources in the manifest, how will we detect it?

A Generic Way to Detect Extensions

Update 2017-04-11: the following technique crashes badly after Windows Creators Update, however, there are at least two variations that work. One of them will be posted soon. Thanks for your patience!

After playing around a few minutes with different tricks and trying to load non-accessible-resources, I decided to try something similar to Soroush’s IE DTD trick. which tries to load a resource using the Microsoft XMLDOM object, and depending on the error number it will know if the file was present or not. On Edge we don’t have the XMLDOM object but we can do something similar with the regular XMLHttpRequest.

If try to open a resource and the extension exists it throws an access denied, otherwise an unspecified error. In fact, to honour Soroush amazing finding we will try to be as elegant as him and use the error number received in the catch. A block of code is worth a thousand words. Take a look below:

var extension = "ms-browser-extension://EdgeExtension_AdguardAdguardAdBlocker_m055xr0c82818"; try { var xhr = new XMLHttpRequest(); xhr.open("GET", extension, false); xhr.send(null); } catch(e) { if (e.number == -2147024891) alert("Exists"); else alert("Does not exist"); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var extension = "ms-browser-extension://EdgeExtension_AdguardAdguardAdBlocker_m055xr0c82818" ; try { var xhr = new XMLHttpRequest ( ) ; xhr . open ( "GET" , extension , false ) ; xhr . send ( null ) ; } catch ( e ) { if ( e . number == - 2147024891 ) alert ( "Exists" ) ; else alert ( "Does not exist" ) ; }

Now this is looking good, thanks Soroush for all your brilliant ideas!

By the way, have you noted that the URL that we are using is not even pointing to a specific resource? The xml trick works against the directory name and it does not even need to point to a specific resource of the extension, so now, having the extension ID is enough to detect it. If we want to build a generic tester to find all installed extensions like the one that we’ve seen for Chrome and Firefox, we need to first install all the extensions in our Edge and take note of their IDs. It’s easy, just install the extension, load a blank page and press F12. That enough to reveal the extension ID. I’ve installed a few of them so it becomes clearer in the screenshot below.

With those names we can now create a generic extension detector for Microsoft Edge. Only disabled extensions won’t be loaded in Developer Tools but we can still find them in the registry:

HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Storage\microsoft.microsoftedge_8wekyb3d8bbwe\MicrosoftEdge\Extensions 1 2 3 HKEY_CLASSES_ROOT \ Local Settings \ Software \ Microsoft \ Windows \ CurrentVersion \ AppContainer \ Storage \ microsoft . microsoftedge _ 8wekyb3d8bbwe \ MicrosoftEdge \ Extensions

Worth noting is that our detection method will work even if the user has disabled the extensions. Below is the PoC that detects 20 extensions using the method described above.

Update 2017-04-11: the following technique crashes badly after Windows Creators Update, however, there are at least two variations that work. One of them will be posted soon. Thanks for your patience!

Have a nice day! 🙂

Manuel.