Intro
Edge Legacy’s XAML WebView control has always been incompatible with both unpackaged apps and legacy Win32 apps. Any attempt to using this control under these conditions will always result in an exception, whether on XAML Islands or any other non-UWP XAML hosting techniques such as XamlHost or XamlPresenter. I wanted to use WebView as fallback SVG renderer for MrmTool since the default NanoSVG-based renderer is limited and doesn’t cover all of SVG features, but the problem is that MrmTool is an unpackaged Win32 app so it cannot normally use the WebView control, and I wanted to avoid WebView2 since it would increase the app size and it’s not available (or at least not installed by default) on all platform environments that MrmTool supports. So I decided to investigate why it throws exceptions and see if I can fix it or at least patch it to work…Investigating
The first exception we face seems to beE_XAMLPARSEFAILED


E_FAIL that makes more sense, but sadly it seems like the actual location of the exception has been lost, the callstack is from the generic XAML HRESULT handler, so lets see if we can track where that HRESULT was originally returned…
The easiest way to do that would be following the WebView initialization code under IDA Pro and seeing where that HRESULT first occurs.
it seems like the init logic happens in CWebView::CreateComponent which then calls DirectUI::WebView::CreateComponent which then calls DirectUI::CoreWebViewHost::CreateComponent, and here we found our first occurrence of E_FAIL (0x80004005):

s_bWebPlatformSecurityManagerFactoryCallbackRegistered is false, so lets see Xrefs of this field to see what sets it

DirectUI::CoreWebViewHost::RegisterWebViewPermanentSecurityManager


EnsureDelayedInit so lets check it out…

DesignerInterop::GetDesignerMode, DirectUI::XamlRuntime::IsWebViewEnabled, and ShouldProcessRegisterWebViewSecurityManager.
We are not in the designer so we can ignore the first check, and by debugging I found out that the second check returns true so we can ignore that one too, so it must be the third one, so lets check it out…

true one of these conditions must be true:
- The application is running under AppContainer
- The application is not the Settings app and the application is not using
ClassicDesktopAppModel windowing policy
AppPolicyGetWindowingModel function.
Patching & More Investigation
The most logical thing to do would be hooking the function using Detours (or similar hooking libraries), but since MrmTool is written in C# it would be harder to statically link Detours without sarcrificing CoreCLR and being exclusively on NativeAOT, and using Detours as a dynamic library would increase the app size and the number of files it carries, so I decided to use IAT patching instead. Luckily a project I’m part of called XWine1 already had helpers so I ported them to C# with few adaptations to unpatch the functions on process exit to accommodate for GC Shutdown:AppPolicyGetWindowingModel function:

APPMODEL_ERROR_NO_PACKAGE, so XAML must be calling some packaging related APIs in the WebView init code, lets see if we can find any…
Hm this call in DirectUI::CoreWebViewHost::CreateComponent seems interesting, it mentions “Appx” so it must be doing something with packaging APIs:



GetCurrentPackageInfo for that, so lets patch it and redirect it to the Settings app package…

Abandonment::InduceHRESULTAbandonment inside edgehtml.dll, lets check this function to see if we can get the HRESULT somehow…

Abandonment::LastError, lets check its address in Visual Studio disassembler so we can read it from the debugger…

E_ACCESSDENIED (-2147024891 = 0x80070005), and it’s thrown from CCoreWebViewTaskHandler::_CreateWebPlatform so lets see if we can find it in that function…


CWebPlatform::CreateInstance function call, so lets check this one…

E_ACCESSDENIED!
It seems like the execution never reaches this block or otherwise the HRESULT would have changed since this block changes it

if condition must be failing, and I was able to confim via debugger that they indeed are…
The second check seems to be for checking the device family so lets ignore that one since it would also fail for UWP under the same device family, the first check seems to be checking some boolean state 536870925 / 0x2000000D using the IEConfiguration_GetBool function from iertutil.dll, so lets check what this state is…

unk_18029808A (name generated by IDA Pro), so lets see what sets that…
It seems to be set through _IEProcessState_GetStateHolder function but it seems like this function is only called by IEConfiguration_SetBool function which isn’t called by that state ID in the dll, so unk_18029808A must be a part of a global struct field and it’s accessed through it instead, there seem to be a function called IEConfiguration_Initialize and judging by its name it seems to be the one responsible for initializing these state values, so lets check it out…

_IEConfiguation_InitializeHelper with a global field byte_180298070 casted to struct SIEProcessState* passed as the second paramter, which is just 26 bytes away from unk_18029808A so this must be its parent struct, so lets see if anything sets [second parameter] + 26 in that function…

true if IEIsWebPlatformProcess returned true, so lets check that function…

IEIsImmersiveProcess immediatly caught my attention, since that would definetly be true on UWP but false on legacy Win32, and comparing the results of all these checks with a UWP app under a debugger confirms that this indeed is the different result out of them, so lets check that function and see if we can patch it…

IsImmersiveProcess for that but it’s dynamically loaded so a direct IAT patch wouldn’t work, we need to IAT patch GetProcAddress first then from that we can return our custom IsImmersiveProcess, so lets do that!

CreateUriPriv function, lets check the function and see what could cause that…

0x2000000F isn’t what it expects, which I assume is true, using the same method we used previously I found the global field (or struct member to be more accurate) for this state ID to be unk_18029808C which is 28 bytes away from byte_180298070, so lets see what sets this state ID in _IEConfiguation_InitializeHelper

byte_180296648 which is set by InitOnceIsCurrentProcessEdgeContentHost, so lets check it out…

true if the value of DWORD state ID 0x1000002D is 2, so lets see where that state ID is stored by checking IEConfiguration_GetDWORD



v7 to RVA 0x1802987C0 then later sets v3 to *((DWORD*)v7 + 292) and then it returns v3, so (DWORD*)v7 + 292 must be the RVA where state value is stored, and it can be calculated like this: 0x1802987C0 + (sizeof(DWORD) * 292) = 0x1802987C0 + (4 * 292) = 0x180298c50, so lets check Xrefs of that RVA…

IEConfiguration_SetBrowserAppProfile function, so lets check it out…


#797!
So now we have to figure out the params of the function and call it from our app.
The first parameter seems to be a string judging by the lstrcmpW call and calls to this function from other functions in the dll confirm this and it seems to be the selected profile judging by the function name, and we know that we need to set the second parameter to 2 for 0x2000000F to be true and it seems like this parameter is the type of the profile selected? since there seem to be other valid values, and the last paramter is unknown but judging by the call in IEConfiguration_SetBrowserAppProfileDefault it seems like it can be 0

Results
Now, lets see if it finally works…
Fixing a Crash on Process Shutdown
The app seems to throw an exception on shutdown after WebView is loaded
SubmitThreadpoolWork when detaching/unloading on process shutdown which isn’t allowed, but we can workaround this by forcing edgehtml.dll to cleanup early by sending it a DLL_PROCESS_DETACH signal inside our OnProcessExit event handler: