# Applying Composition effects to XAML elements
Source: https://ahmed-walid.com/applying-composition-effects-to-xaml-elements
And also alpha masking XAML elements
**Update**
I found a [better way](#update) to achieve this than the method described in this article, I recommend using that way instead.
## **Backstory**
I was exploring XAML's [Handoff Visual](https://learn.microsoft.com/uwp/api/windows.ui.xaml.hosting.elementcompositionpreview.getelementvisual) implementation and how it's created, clipped, wrapped, etc... and found out that the function responsible for creating that visual is `CUIElement::EnsureHandOffVisual` and when I looked at this function I noticed that it has a `bool createLayerVisual` parameter:
So I looked at the xrefs of this function to see if and when this parameter gets used, and noticed there's a function called `CUIElement::GetHandOffLayerVisual` in the xrefs:
So I was curious and checked the xrefs of it, and found a very interesting function, `DirectUI::UIElementFactory::GetElementLayerVisual`
Stuff under the `DirectUI` namespace are usually exposed via interfaces, and that was the case here too!
Xrefs showed that it's exposed through the `Windows::UI::Xaml::IUIElementStaticsPrivate` interface:
I was curious when this was introduced since I have never seen it before, and after checking multiple **Windows.UI.Xaml.dll** versions, it turns out that it was introduced in **Windows 11 Build 22000**.
`LayerVisual` is a visual type that allows you to apply effects and attach shadows to its child visuals.
## **Experimentation**
Now that we have the interface and the function signature, the only thing missing is the interface **IID**, checking the `UIElementFactory` constructor reveals that the interface is stored at offset **0x50** of the factory class object:
And if we look at the `QueryInterface` implementation, we can see that it wraps/calls another `QueryInterface` function but with a pointer to the object at offset **0x10** passed as a `this` parameter:
This means that we are looking for the **IID** of object returned at offset **0x40** ( **0x50 - 0x10** ) in that wrapped `QueryInterface` function.
If we look at that function we see that IDA mistyped the `this` parameter and also wasn't able to detect that it's the `this` parameter and showed it as a regular register variable:
To fix this mess, we will simply change the type of this variable to `void*`, this way we can see the offsets more clearly, and here we go, we found our **IID**!
We are using **UWP .NET 9** for this experiment so we have to use **Source Generated COM** to declare this interface definition (technically we can use a projection project or handroll the interface projection codegen too but this is simpler, well, *kinda*)
```csharp theme={null}
[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("7cc8cb07-5a0d-46bb-8c54-9beafe63b476")]
internal unsafe partial interface IUIElementStaticsPrivate
{
// IInspectable methods
void GetIids();
void GetRuntimeClassName();
void GetTrustLevel();
// IUIElementStaticsPrivate methods
void add_PopupOpening();
void remove_PopupOpening();
void add_PopupPlacement();
void remove_PopupPlacement();
void InternalGetIsEnabled();
void InternalPutIsEnabled();
void GetRasterizationScale();
void PutRasterizationScale();
void PutPopupRootLightDismissBounds();
void EnablePopupZIndexSorting();
void* GetElementLayerVisual(void* pElement);
}
```
Now we only need to write some test code and see if it works
```jsx theme={null}
```
```csharp expandable theme={null}
private void playButton_Click(object sender, RoutedEventArgs e)
{
// In CsWinRT 3, this probably needs to be changed to something like:
// using var objRef = WindowsRuntimeActivationFactory.GetActivationFactory(typeof(UIElement).FullName, typeof(IUIElementStaticsPrivate).GUID);
// var staticsPrivate = ComInterfaceMarshaller.ConvertToManaged(objRef.GetThisPtrUnsafe());
var staticsPrivate = UIElement.As();
// GetElementLayerVisual expects an IUIElement* but CsWinRT won't give us an IUIElement from a composed class (such as ContentControl) unless
// it's passed to a projected method that takes an UIElement, so we have to QI manually
Marshal.QueryInterface(((IWinRTObject)contentControl).NativeObject.ThisPtr, new("676d0be9-b65c-41c6-ba40-58cf87f201c1") /* IUIElement */, out nint ppv);
var layerVisualPtr = staticsPrivate.GetElementLayerVisual((void*)ppv);
Marshal.Release(ppv);
var layerVisual = LayerVisual.FromAbi((nint)layerVisualPtr);
var compositor = layerVisual.Compositor;
var blur = new GaussianBlurEffect
{
Name = "Blur",
BlurAmount = 0.0f,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Balanced,
Source = new CompositionEffectSourceParameter("source")
};
var effectFactory = compositor.CreateEffectFactory(blur, new[] { "Blur.BlurAmount" });
var effectBrush = effectFactory.CreateBrush();
layerVisual.Effect = effectBrush;
var easing = CompositionEasingFunction.CreateExponentialEasingFunction(compositor, CompositionEasingFunctionMode.InOut, 6);
var animation = compositor.CreateScalarKeyFrameAnimation();
animation.InsertKeyFrame(0.0f, 0.0f, easing);
animation.InsertKeyFrame(1.0f, 10.0f, easing);
animation.Duration = TimeSpan.FromSeconds(2);
animation.IterationBehavior = AnimationIterationBehavior.Forever;
animation.Direction = AnimationDirection.Alternate;
effectBrush.StartAnimation("Blur.BlurAmount", animation);
Marshal.QueryInterface(((IWinRTObject)hiButton).NativeObject.ThisPtr, new("676d0be9-b65c-41c6-ba40-58cf87f201c1") /* IUIElement */, out nint ppv2);
var layerVisual2Ptr = staticsPrivate.GetElementLayerVisual((void*)ppv2);
Marshal.Release(ppv2);
var layerVisual2 = LayerVisual.FromAbi((nint)layerVisual2Ptr);
var alphaMask = new AlphaMaskEffect
{
Name = "AlphaMask",
Source = new CompositionEffectSourceParameter("source"),
AlphaMask = new CompositionEffectSourceParameter("mask")
};
var gradientBrush = compositor.CreateLinearGradientBrush();
gradientBrush.StartPoint = new(0, 0);
gradientBrush.EndPoint = new(1, 0);
gradientBrush.MappingMode = CompositionMappingMode.Relative;
CompositionColorGradientStop stop;
gradientBrush.ColorStops.Add(compositor.CreateColorGradientStop(-1.0f, Colors.Black));
gradientBrush.ColorStops.Add(stop = compositor.CreateColorGradientStop(1.0f, Colors.Black));
gradientBrush.ColorStops.Add(compositor.CreateColorGradientStop(1.0f, Colors.Transparent));
var easing2 = CompositionEasingFunction.CreatePowerEasingFunction(compositor, CompositionEasingFunctionMode.InOut, 3);
var animation2 = compositor.CreateScalarKeyFrameAnimation();
animation2.InsertKeyFrame(0.0f, 1.0f, easing2);
animation2.InsertKeyFrame(1.0f, -1.0f, easing2);
animation2.Duration = TimeSpan.FromSeconds(2);
animation2.IterationBehavior = AnimationIterationBehavior.Forever;
animation2.Direction = AnimationDirection.Alternate;
stop.StartAnimation("Offset", animation2);
var effectFactory2 = compositor.CreateEffectFactory(alphaMask);
var effectBrush2 = effectFactory2.CreateBrush();
effectBrush2.SetSourceParameter("mask", gradientBrush);
layerVisual2.Effect = effectBrush2;
}
```
*And voila!*
**Warning**
* `GetElementLayerVisual` has to be called **before** any handoff visual was created for the element, so if `ElementCompositionPreview.GetElementVisual` was called on the element prior it, the function will fail.
* If you are calling that method on an element that has any `Popup` children, you have to call `ElementCompositionPreview.GetElementVisual` on the element after calling `GetElementLayerVisual` or otherwise the `Popup` will break.
## **But what about Windows 10?**
This function we just used doesn't exist in the **Windows 10** version of that interface, but looking at how `createLayerVisual` is used inside `CUIElement::EnsureHandOffVisual` we can see that the `LayerVisual` and the normal visual paths are *very* similar, so similar that we can hook `IDCompositionDevice2::CreateVisual` and redirect it to `Windows.UI.Compositor.CreateLayerVisual` and it will just work.
We will be using a **C++/WinRT Runtime Component** for this since we are gonna need **Detours**, and using **Detours** in C# is a bit harder than using it from C++ directly.
So let's create a simple WinRT helper static class for this, lets start with the **IDL**:
```cpp theme={null}
namespace XamlCompositionHelpers
{
static runtimeclass ElementCompositionPreviewEx
{
static Windows.UI.Composition.LayerVisual GetElementLayerVisual(Windows.UI.Xaml.UIElement element);
}
}
```
And now the implementation:
```cpp expandable theme={null}
namespace winrt::XamlCompositionHelpers::implementation
{
bool ElementCompositionPreviewEx::s_Hooked = false;
DWORD ElementCompositionPreviewEx::s_ThreadId = NULL;
std::mutex ElementCompositionPreviewEx::s_Mutex = { };
decltype(ElementCompositionPreviewEx::s_CreateVisual) ElementCompositionPreviewEx::s_CreateVisual = nullptr;
decltype(ElementCompositionPreviewEx::s_QueryInterface) ElementCompositionPreviewEx::s_QueryInterface = nullptr;
static bool IsOnWindows11OrHigher()
{
bool isWin11 = ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 14);
return isWin11;
}
LayerVisual ElementCompositionPreviewEx::GetElementLayerVisual(UIElement const& element)
{
// On Windows 11 b22000 and higher, we can use the IUIElementStaticsPrivate interface to get the LayerVisual directly
com_ptr uiElementPrivate;
if (IsOnWindows11OrHigher() && (uiElementPrivate = try_get_activation_factory()))
{
LayerVisual layerVisual { nullptr };
check_hresult(uiElementPrivate->GetElementLayerVisual(winrt::get_abi(element), put_abi(layerVisual)));
return layerVisual;
}
// We are using the thread ID to verify and ensure that we aren't hoking any other ElementCompositionPreview::GetElementVisual call
// that happened to be going in another thread at the same time we are hooking the function to return a LayerVisual,
// and we use a lock to ensure that only one thread can be hooking at a time so that thread ID doesn't get changed mid-hook.
std::scoped_lock lock(s_Mutex);
EnsureHooked();
s_ThreadId = GetCurrentThreadId();
auto visual = ElementCompositionPreview::GetElementVisual(element);
s_ThreadId = NULL;
return visual.as();
}
void ElementCompositionPreviewEx::EnsureHooked()
{
if (!s_Hooked)
{
// assuming we are on the UI thread, it wouldn't work otherwise anyway
auto compositor = Window::Current().Compositor();
auto device3 = compositor.as();
auto vtbl = *reinterpret_cast(device3.get());
s_CreateVisual = reinterpret_cast(vtbl[6]);
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)s_CreateVisual, &ElementCompositionPreviewEx::CreateVisualHook);
check_win32(DetourTransactionCommit());
// some older Windows builds don't have the createLayerVisual parameter
// and on these builds XAML actually tries to use the visual as IDCompsitionVisual*
// which fails because LayerVisual is not an InteropVisual, we work around this
// by creating a dummy InteropVisual and hooking QI calls on the LayerVisual
// that query for IDCompsitionVisual* and redirect them to our dummy InteropVisual
check_hresult(device3->CreateVisual(&s_dummyVisual));
auto layerVisual = compositor.CreateLayerVisual().as();
auto layerVtbl = *reinterpret_cast(winrt::get_abi(layerVisual));
s_QueryInterface = reinterpret_cast(layerVtbl[0]);
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)s_QueryInterface, &ElementCompositionPreviewEx::QueryInterfaceHook);
check_win32(DetourTransactionCommit());
s_Hooked = true;
}
}
HRESULT WINAPI ElementCompositionPreviewEx::CreateVisualHook(IDCompositionDevice2* pThis, IDCompositionVisual2** ppVisual)
{
// Ensure that we are only hooking our own calls to ElementCompositionPreview::GetElementVisual / IDCompositionDevice2::CreateVisual
if (s_ThreadId != GetCurrentThreadId())
return s_CreateVisual(pThis, ppVisual);
Compositor compositor { nullptr };
copy_from_abi(compositor, pThis);
LayerVisual layerVisual = compositor.CreateLayerVisual();
copy_to_abi(layerVisual, *(void**&)ppVisual);
return S_OK;
}
HRESULT WINAPI ElementCompositionPreviewEx::QueryInterfaceHook(IUnknown* pThis, REFIID riid, void** ppv)
{
const static auto xamlModule = GetModuleHandleW(L"Windows.UI.Xaml.dll");
static const constexpr auto IID1 = std::bit_cast(guid("1CD4B256-A5ED-4DED-A0C1-82A7A20B8755"));
static const constexpr auto IID2 = std::bit_cast(guid("86C82676-5D2A-4E6A-A89F-50A22FF86263"));
static const constexpr auto IID3 = std::bit_cast(guid("A0AD01B5-9C60-4A6F-A7E2-04E18CF5E011"));
LayerVisual layerVisual { nullptr };
HMODULE module = NULL;
HRESULT result = s_QueryInterface(pThis, riid, ppv);
if (FAILED(result) &&
s_dummyVisual &&
(riid == IID1 || riid == IID2 || riid == IID3) &&
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)_ReturnAddress(), &module) &&
module == xamlModule &&
SUCCEEDED(s_QueryInterface(pThis, winrt::guid_of(), winrt::put_abi(layerVisual))))
{
result = s_dummyVisual->QueryInterface(riid, ppv);
}
return result;
}
}
```
And with this we can then replace our C# code with this:
```csharp expandable theme={null}
private void playButton_Click(object sender, RoutedEventArgs e)
{
var layerVisual = ElementCompositionPreviewEx.GetElementLayerVisual(contentControl);
var compositor = layerVisual.Compositor;
var blur = new GaussianBlurEffect
{
Name = "Blur",
BlurAmount = 0.0f,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Balanced,
Source = new CompositionEffectSourceParameter("source")
};
var effectFactory = compositor.CreateEffectFactory(blur, new[] { "Blur.BlurAmount" });
var effectBrush = effectFactory.CreateBrush();
layerVisual.Effect = effectBrush;
var easing = CompositionEasingFunctionEx.CreateExponentialEasingFunction(compositor, CompositionEasingFunctionMode.InOut, 6);
var animation = compositor.CreateScalarKeyFrameAnimation();
animation.InsertKeyFrame(0.0f, 0.0f, easing);
animation.InsertKeyFrame(1.0f, 10.0f, easing);
animation.Duration = TimeSpan.FromSeconds(2);
animation.IterationBehavior = AnimationIterationBehavior.Forever;
animation.Direction = AnimationDirection.Alternate;
effectBrush.StartAnimation("Blur.BlurAmount", animation);
var layerVisual2 = ElementCompositionPreviewEx.GetElementLayerVisual(hiButton);
var alphaMask = new AlphaMaskEffect
{
Name = "AlphaMask",
Source = new CompositionEffectSourceParameter("source"),
AlphaMask = new CompositionEffectSourceParameter("mask")
};
var gradientBrush = compositor.CreateLinearGradientBrush();
gradientBrush.StartPoint = new(0, 0);
gradientBrush.EndPoint = new(1, 0);
gradientBrush.MappingMode = CompositionMappingMode.Relative;
CompositionColorGradientStop stop;
gradientBrush.ColorStops.Add(compositor.CreateColorGradientStop(-1.0f, Colors.Black));
gradientBrush.ColorStops.Add(stop = compositor.CreateColorGradientStop(1.0f, Colors.Black));
gradientBrush.ColorStops.Add(compositor.CreateColorGradientStop(1.0f, Colors.Transparent));
var easing2 = CompositionEasingFunctionEx.CreatePowerEasingFunction(compositor, CompositionEasingFunctionMode.InOut, 3);
var animation2 = compositor.CreateScalarKeyFrameAnimation();
animation2.InsertKeyFrame(0.0f, 1.0f, easing2);
animation2.InsertKeyFrame(1.0f, -1.0f, easing2);
animation2.Duration = TimeSpan.FromSeconds(2);
animation2.IterationBehavior = AnimationIterationBehavior.Forever;
animation2.Direction = AnimationDirection.Alternate;
stop.StartAnimation("Offset", animation2);
var effectFactory2 = compositor.CreateEffectFactory(alphaMask);
var effectBrush2 = effectFactory2.CreateBrush();
effectBrush2.SetSourceParameter("mask", gradientBrush);
layerVisual2.Effect = effectBrush2;
}
```
*And voila!*
As you might have noticed I'm using `CompositionEasingFunctionEx` instead of `CompositionEasingFunction` to create easing functions in the \*\*Windows 10 \*\*code, that's because the latter only got the support for creating easing functions in **Windows 11**, so I created a `CompositionEasingFunctionEx` helper class to allow creating such easing functions under **Windows 10**, but that's a story for another article.
## **But what about WinUI 3?**
Unfortunately WinUI 3 doesn't have that interface method at all, neither in latest version or older ones, \*\*BUT \*\*we can use the same trick we used for **Windows 10**, so lets take a look at the `CUIElement::EnsureHandOffVisual` function in **WinUI 3**:
Interesting, so it uses the WinRT `Microsoft.UI.Composition` API for both cases, so we need to copy the C++ implementation code we wrote before and make few changes to accommodate that (previous comments are omitted, so check them in the UWP/WUX code if you haven't)
```cpp expandable theme={null}
namespace winrt::WinUICompositionHelpers::implementation
{
bool ElementCompositionPreviewEx::s_Hooked = false;
DWORD ElementCompositionPreviewEx::s_ThreadId = NULL;
std::mutex ElementCompositionPreviewEx::s_Mutex = { };
decltype(ElementCompositionPreviewEx::s_CreateContainerVisual) ElementCompositionPreviewEx::s_CreateContainerVisual = nullptr;
LayerVisual ElementCompositionPreviewEx::GetElementLayerVisual(UIElement const& element)
{
std::scoped_lock lock(s_Mutex);
assert(element.DispatcherQueue().HasThreadAccess());
EnsureHooked(CompositionTarget::GetCompositorForCurrentThread());
s_ThreadId = GetCurrentThreadId();
auto visual = ElementCompositionPreview::GetElementVisual(element);
s_ThreadId = NULL;
return visual.as();
}
void ElementCompositionPreviewEx::EnsureHooked(Compositor compositor)
{
if (!s_Hooked)
{
auto vtbl = *reinterpret_cast(winrt::get_abi(compositor));
s_CreateContainerVisual = reinterpret_cast(vtbl[9]);
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)s_CreateContainerVisual, &ElementCompositionPreviewEx::CreateContainerVisualHook);
check_win32(DetourTransactionCommit());
s_Hooked = true;
}
}
HRESULT WINAPI ElementCompositionPreviewEx::CreateContainerVisualHook(void* pThis, void** ppVisual)
{
if (s_ThreadId != GetCurrentThreadId())
return s_CreateContainerVisual(pThis, ppVisual);
Compositor compositor { nullptr };
copy_from_abi(compositor, pThis);
LayerVisual layerVisual = compositor.CreateLayerVisual();
copy_to_abi(layerVisual, *(void**&)ppVisual);
return S_OK;
}
}
```
The C# code is the same as UWP/WUX version, so no need to include it again, and with that in place.... *voila!*
And that's it, see you in the next article!
## **Update**
I accidentally found a better and easier way to achieve this without hooking or any complex stuff at all while working on another thing.
So basically I found out that `CompositionVisualSurface` completely ignores the `Opacity` property of the source Visual, so you can just get the Visual of the target `UIElement`, make its `Opacity` **0**, then create a `CompositionSurfaceBrush` over a `CompositionVisualSurface` of the Visual then use that to create a `SpriteVisual` with `CompositionEffectBrush` or `CompositionMaskBrush` (with the source brush set to the `CompositionSurfaceBrush`) on top of it!
*Special thanks to* [*Dongle*](https://x.com/a_donglee) *for proofreading the article*
# Using Edge Legacy's WebView in an unpackaged Win32 app
Source: https://ahmed-walid.com/edge-legacy-webview-in-unpackaged-win32-app
And debugging a mysterious crash on app exit
## **Intro**
Edge Legacy's XAML [WebView](https://learn.microsoft.com/uwp/api/windows.ui.xaml.controls.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](https://github.com/ahmed605/MrmTool) since the default [NanoSVG](https://github.com/memononen/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](https://developer.microsoft.com/microsoft-edge/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 be `E_XAMLPARSEFAILED`
But that error is from the XAML parser so it's already too late and the actual error was absorbed by the parser, so let's initialize WebView early manually and see what we get...
Ah `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**):
It seems to be returned if the global field `s_bWebPlatformSecurityManagerFactoryCallbackRegistered` is `false`, so lets see Xrefs of this field to see what sets it
As you can see, it seems to be set by a function called `DirectUI::CoreWebViewHost::RegisterWebViewPermanentSecurityManager`
I put a breakpoint on this function to see what goes wrong in it but the breakpoint was never hit??
So there must be something else preventing this function from being called, lets see what calls this function and how it is called...
It seems to be called by `EnsureDelayedInit` so lets check it out....
Ah so it's indeed called conditionally based on `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...
By looking at this function it seems like for it to return `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 `ClassicDesktop` AppModel windowing policy
As expected both are false in our case, the second condition seems to be the easier one to patch since we only need to patch the return value of the `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](https://blog.neteril.org/blog/2016/12/23/diverting-functions-windows-iat-patching/) instead.
Luckily a project I'm part of called [XWine1](https://twitter.com/XWineOne) already had helpers so I ported them to **C#** with few adaptations to unpatch the functions on process exit to accommodate for **GC Shutdown**:
```csharp expandable theme={null}
internal static readonly Dictionary PatchedFunctions = [];
// Originally written by @DaZombieKiller for the XWine1 project
private static HRESULT XWineFindImport(HMODULE Module,
byte* Import,
IMAGE_THUNK_DATA* pImportAddressTable,
IMAGE_THUNK_DATA* pImportNameTable,
IMAGE_THUNK_DATA** pThunk)
{
for (nuint j = 0; pImportNameTable[j].u1.AddressOfData > 0; j++)
{
if ((pImportNameTable[j].u1.AddressOfData & IMAGE.IMAGE_ORDINAL_FLAG) != 0)
{
if (!IS_INTRESOURCE((nuint)Import))
continue;
if (((pImportNameTable[j].u1.Ordinal & ~IMAGE.IMAGE_ORDINAL_FLAG) == (nuint)Import))
{
*pThunk = &pImportAddressTable[j];
return S.S_OK;
}
continue;
}
var name = MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)Unsafe.AsPointer(in ((IMAGE_IMPORT_BY_NAME*)((byte*)Module + pImportNameTable[j].u1.AddressOfData))->Name.e0));
var importName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(Import);
if (!name.SequenceEqual(importName))
continue;
*pThunk = &pImportAddressTable[j];
return S.S_OK;
}
*pThunk = null;
return E.E_FAIL;
}
// Originally written by @DaZombieKiller for the XWine1 project
internal static HRESULT XWineGetImport(HMODULE Module,
HMODULE ImportModule,
byte* Import,
IMAGE_THUNK_DATA** pThunk)
{
if (ImportModule.Value is null)
return E.E_INVALIDARG;
if (pThunk == null)
return E.E_POINTER;
if (Module.Value is null)
Module = GetModuleHandleW(null);
var dosHeader = (IMAGE_DOS_HEADER*)Module;
var ntHeaders = (IMAGE_NT_HEADERS*)((byte*)Module + dosHeader->e_lfanew);
var directory = &ntHeaders->OptionalHeader.DataDirectory[IMAGE.IMAGE_DIRECTORY_ENTRY_IMPORT];
if (directory->VirtualAddress <= 0 || directory->Size <= 0)
return E.E_FAIL;
var peImports = (IMAGE_IMPORT_DESCRIPTOR*)((byte*)Module + directory->VirtualAddress);
for (nuint i = 0; peImports[i].Name > 0; i++)
{
if (GetModuleHandleA((sbyte*)((byte*)Module + peImports[i].Name)) != ImportModule)
continue;
var iatThunks = (IMAGE_THUNK_DATA*)((byte*)Module + peImports[i].FirstThunk);
var intThunks = (IMAGE_THUNK_DATA*)((byte*)Module + peImports[i].OriginalFirstThunk);
if (SUCCEEDED(XWineFindImport(Module, Import, iatThunks, intThunks, pThunk)))
return S.S_OK;
}
var delayDir = &ntHeaders->OptionalHeader.DataDirectory[IMAGE.IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT];
if (delayDir->VirtualAddress > 0 && directory->Size > 0)
{
var delayImports = (IMAGE_DELAYLOAD_DESCRIPTOR*)((byte*)Module + delayDir->VirtualAddress);
for (nuint i = 0; delayImports[i].DllNameRVA > 0; i++)
{
if (GetModuleHandleA((sbyte*)((byte*)Module + delayImports[i].DllNameRVA)) != ImportModule)
continue;
var iatThunks = (IMAGE_THUNK_DATA*)((byte*)Module + delayImports[i].ImportAddressTableRVA);
var intThunks = (IMAGE_THUNK_DATA*)((byte*)Module + delayImports[i].ImportNameTableRVA);
if (SUCCEEDED(XWineFindImport(Module, Import, iatThunks, intThunks, pThunk)))
return S.S_OK;
}
}
*pThunk = null;
return E.E_FAIL;
}
// Originally written by @DaZombieKiller for the XWine1 project
internal static HRESULT XWinePatchImport(HMODULE Module,
HMODULE ImportModule,
byte* Import,
void* Function)
{
HRESULT hr;
uint protect;
IMAGE_THUNK_DATA* pThunk;
if (!SUCCEEDED_LOG(hr = XWineGetImport(Module, ImportModule, Import, &pThunk)))
{
return hr;
}
if (!VirtualProtect(&pThunk->u1.Function, (nuint)sizeof(nuint), PAGE.PAGE_READWRITE, &protect))
{
return HRESULT_FROM_WIN32(GetLastError());
}
nuint originalFunction = (nuint)pThunk->u1.Function;
pThunk->u1.Function = (nuint)Function;
PatchedFunctions.TryAdd((nuint)(void*)&pThunk->u1.Function, originalFunction);
if (!VirtualProtect(&pThunk->u1.Function, (nuint)sizeof(nuint), protect, &protect))
{
return HRESULT_FROM_WIN32(GetLastError());
}
return S.S_OK;
}
```
I didn't need few of the features this helper includes so I removed them for performance reasons:
```csharp expandable theme={null}
internal static readonly (nuint Thunk, nuint OriginalFunction)[] PatchedFunctions = new (nuint, nuint)[Constants.PatchesCount];
// Originally written by @DaZombieKiller for the XWine1 project
private static HRESULT XWineFindImport(HMODULE Module,
ReadOnlySpan Import,
IMAGE_THUNK_DATA* pImportAddressTable,
IMAGE_THUNK_DATA* pImportNameTable,
IMAGE_THUNK_DATA** pThunk)
{
for (nuint j = 0; pImportNameTable[j].u1.AddressOfData > 0; j++)
{
if ((pImportNameTable[j].u1.AddressOfData & IMAGE.IMAGE_ORDINAL_FLAG) != 0)
continue;
var name = MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)Unsafe.AsPointer(in ((IMAGE_IMPORT_BY_NAME*)((byte*)Module + pImportNameTable[j].u1.AddressOfData))->Name.e0));
if (!name.SequenceEqual(Import))
continue;
*pThunk = &pImportAddressTable[j];
return S.S_OK;
}
*pThunk = null;
return E.E_FAIL;
}
// Originally written by @DaZombieKiller for the XWine1 project
internal static HRESULT XWineGetImport(HMODULE Module,
HMODULE ImportModule,
ReadOnlySpan Import,
IMAGE_THUNK_DATA** pThunk)
{
if (ImportModule.Value is null)
return E.E_INVALIDARG;
if (pThunk == null)
return E.E_POINTER;
if (Module.Value is null)
Module = GetModuleHandleW(null);
var dosHeader = (IMAGE_DOS_HEADER*)Module;
var ntHeaders = (IMAGE_NT_HEADERS*)((byte*)Module + dosHeader->e_lfanew);
var directory = &ntHeaders->OptionalHeader.DataDirectory[IMAGE.IMAGE_DIRECTORY_ENTRY_IMPORT];
if (directory->VirtualAddress <= 0 || directory->Size <= 0)
return E.E_FAIL;
var peImports = (IMAGE_IMPORT_DESCRIPTOR*)((byte*)Module + directory->VirtualAddress);
for (nuint i = 0; peImports[i].Name > 0; i++)
{
if (GetModuleHandleA((sbyte*)((byte*)Module + peImports[i].Name)) != ImportModule)
continue;
var iatThunks = (IMAGE_THUNK_DATA*)((byte*)Module + peImports[i].FirstThunk);
var intThunks = (IMAGE_THUNK_DATA*)((byte*)Module + peImports[i].OriginalFirstThunk);
if (SUCCEEDED(XWineFindImport(Module, Import, iatThunks, intThunks, pThunk)))
return S.S_OK;
}
var delayDir = &ntHeaders->OptionalHeader.DataDirectory[IMAGE.IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT];
if (delayDir->VirtualAddress > 0 && directory->Size > 0)
{
var delayImports = (IMAGE_DELAYLOAD_DESCRIPTOR*)((byte*)Module + delayDir->VirtualAddress);
for (nuint i = 0; delayImports[i].DllNameRVA > 0; i++)
{
if (GetModuleHandleA((sbyte*)((byte*)Module + delayImports[i].DllNameRVA)) != ImportModule)
continue;
var iatThunks = (IMAGE_THUNK_DATA*)((byte*)Module + delayImports[i].ImportAddressTableRVA);
var intThunks = (IMAGE_THUNK_DATA*)((byte*)Module + delayImports[i].ImportNameTableRVA);
if (SUCCEEDED(XWineFindImport(Module, Import, iatThunks, intThunks, pThunk)))
return S.S_OK;
}
}
*pThunk = null;
return E.E_FAIL;
}
// Originally written by @DaZombieKiller for the XWine1 project
internal static HRESULT XWinePatchImport(HMODULE Module,
HMODULE ImportModule,
ReadOnlySpan Import,
void* Function,
int Index)
{
HRESULT hr;
uint protect;
IMAGE_THUNK_DATA* pThunk;
if (!SUCCEEDED_LOG(hr = XWineGetImport(Module, ImportModule, Import, &pThunk)))
{
return hr;
}
if (!VirtualProtect(&pThunk->u1.Function, (nuint)sizeof(nuint), PAGE.PAGE_READWRITE, &protect))
{
return HRESULT_FROM_WIN32(GetLastError());
}
nuint originalFunction = (nuint)pThunk->u1.Function;
pThunk->u1.Function = (nuint)Function;
PatchedFunctions[Index] = ((nuint)(void*)&pThunk->u1.Function, originalFunction);
if (!VirtualProtect(&pThunk->u1.Function, (nuint)sizeof(nuint), protect, &protect))
{
return HRESULT_FROM_WIN32(GetLastError());
}
return S.S_OK;
}
```
Then it was time to patch the `AppPolicyGetWindowingModel` function:
```csharp expandable theme={null}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
private static int AppPolicyGetWindowingModelHook(HANDLE processToken, AppPolicyWindowingModel* policy)
{
*policy = AppPolicyWindowingModel.AppPolicyWindowingModel_None;
return TerraFX.Interop.Windows.ERROR.ERROR_SUCCESS;
}
internal static bool PatchWebViewAppModelChecks()
{
var appmodel = LoadLibraryA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("kernel.appcore.dll"u8)));
var xaml = LoadLibraryA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("Windows.UI.Xaml.dll"u8)));
if (appmodel.Value is not null && xaml.Value is not null)
{
var fptr = (delegate* unmanaged[Stdcall])&AppPolicyGetWindowingModelHook;
if (SUCCEEDED_LOG(PatchingHelper.XWinePatchImport(xaml, appmodel, "AppPolicyGetWindowingModel"u8, fptr, 0)))
{
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
return true;
}
}
return false;
}
private static void OnProcessExit(object? sender, EventArgs e)
{
foreach (var (Thunk, OriginalFunction) in PatchingHelper.PatchedFunctions)
{
var thunk = (nuint*)Thunk;
uint protect;
if (VirtualProtect(thunk, (nuint)sizeof(nuint), PAGE.PAGE_READWRITE, &protect))
{
*thunk = OriginalFunction;
VirtualProtect(thunk, (nuint)sizeof(nuint), protect, &protect);
}
}
}
```
So did it fix the exception? ***Yes!***
So the WebView control worked? well, ***No!***
We got another exception!!!
This time we got `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:
So lets see what it does...
Judging by the function name and its code, it seems to be getting the current package info then uses that to register activation protocols the package registers for WebView to be able to invoke them, so it doesn't seem to be a cruical thing for our use case, so lets see if we can redirect it to use another package that is always installed on the system for that, we can use the Settings app for example, so lets start by checking what functions are used to get the package info...
it's using `GetCurrentPackageInfo` for that, so lets patch it and redirect it to the Settings app package...
```csharp expandable theme={null}
private static readonly string? settingsPFN =
new PackageManager().FindPackagesForUser(null, "windows.immersivecontrolpanel_cw5n1h2txyewy").FirstOrDefault()?.Id.FullName;
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
private static int GetCurrentPackageInfoHook(uint flags, uint* bufferLength, byte* buffer, uint* count)
{
if (settingsPFN is not null)
{
fixed (char* pSettingPFN = settingsPFN)
{
PACKAGE_INFO_REFERENCE pir;
if (OpenPackageInfoByFullName(pSettingPFN, 0, &pir) is TerraFX.Interop.Windows.ERROR.ERROR_SUCCESS)
{
var result = GetPackageInfo(pir, flags, bufferLength, buffer, count);
_ = ClosePackageInfo(pir);
return result;
}
}
}
return GetCurrentPackageInfo(flags, bufferLength, buffer, count);
}
internal static bool PatchWebViewAppModelChecks()
{
var appmodel = LoadLibraryA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("kernel.appcore.dll"u8)));
var xaml = LoadLibraryA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("Windows.UI.Xaml.dll"u8)));
if (appmodel.Value is not null && xaml.Value is not null)
{
var fptr = (delegate* unmanaged[Stdcall])&AppPolicyGetWindowingModelHook;
if (SUCCEEDED_LOG(PatchingHelper.XWinePatchImport(xaml, appmodel, "AppPolicyGetWindowingModel"u8, fptr, 0)))
{
var fptr2 = (delegate* unmanaged[Stdcall])&GetCurrentPackageInfoHook;
if (SUCCEEDED_LOG(PatchingHelper.XWinePatchImport(xaml, appmodel, "GetCurrentPackageInfo"u8, fptr2, 1)))
{
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
return true;
}
}
}
return false;
}
```
Now, lets see what that changed...
Hm, an interrupt from a function called `Abandonment::InduceHRESULTAbandonment` inside **edgehtml.dll**, lets check this function to see if we can get the **HRESULT** somehow\...
It seems to be setting the **HRESULT** on a global field called `Abandonment::LastError`, lets check its address in Visual Studio disassembler so we can read it from the debugger...
Interesting, it's `E_ACCESSDENIED` (**-2147024891** = **0x80070005**), and it's thrown from `CCoreWebViewTaskHandler::_CreateWebPlatform` so lets see if we can find it in that function...
First call seems to be about getting user identity and it doesn't seem to be the one that is failing judging by the disassembly at function offset shown in the exception callstack, so lets see the second usage...
It seems to be checking the result of the `CWebPlatform::CreateInstance` function call, so lets check this one...
And here's our `E_ACCESSDENIED`!
It seems like the execution never reaches this block or otherwise the **HRESULT** would have changed since this block changes it
So both checks of that `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...
It seems to be reading the state from global field `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...
It seems to be calling another function `_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....
And there indeed is!
It's set to `true` if `IEIsWebPlatformProcess` returned `true`, so lets check that function...
the call to `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...
Hm, so it uses `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!
```csharp expandable theme={null}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
private static int IsImmersiveProcessHook(void* unk)
{
return 1;
}
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
private static void* GetProcAddressHook(HMODULE module, sbyte* procName)
{
if (procName is not null && !IS_INTRESOURCE((nuint)procName))
{
var name = MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)procName);
if (name.SequenceEqual("IsImmersiveProcess"u8))
{
return (delegate* unmanaged[Stdcall])&IsImmersiveProcessHook;
}
}
return GetProcAddress(module, procName);
}
internal static bool PatchWebViewAppModelChecks()
{
var appmodel = LoadLibraryA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("kernel.appcore.dll"u8)));
var xaml = LoadLibraryA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("Windows.UI.Xaml.dll"u8)));
var iertutil = LoadLibraryA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("iertutil.dll"u8)));
var kb = GetModuleHandleA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("kernelbase.dll"u8)));
if (appmodel.Value is not null &&
xaml.Value is not null &&
iertutil.Value is not null)
{
var fptr = (delegate* unmanaged[Stdcall])&AppPolicyGetWindowingModelHook;
if (SUCCEEDED_LOG(PatchingHelper.XWinePatchImport(xaml, appmodel, "AppPolicyGetWindowingModel"u8, fptr, 0)))
{
var fptr2 = (delegate* unmanaged[Stdcall])&GetCurrentPackageInfoHook;
if (SUCCEEDED_LOG(PatchingHelper.XWinePatchImport(xaml, appmodel, "GetCurrentPackageInfo"u8, fptr2, 1)))
{
var fptr3 = (delegate* unmanaged[Stdcall])&GetProcAddressHook;
if (SUCCEEDED_LOG(PatchingHelper.XWinePatchImport(iertutil, kb, "GetProcAddress"u8, fptr3, 2)))
{
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
return true;
}
}
}
}
return false;
}
```
Now, lets see what that changed...
It failfasts inside `CreateUriPriv` function, lets check the function and see what could cause that...
Hm, it failfasts if value for state ID `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`
it seems to be set to value of `byte_180296648` which is set by `InitOnceIsCurrentProcessEdgeContentHost`, so lets check it out...
It seems to return `true` if the value of **DWORD** state ID **0x1000002D** is **2**, so lets see where that state ID is stored by checking `IEConfiguration_GetDWORD`
The function checks if the ID is **0x1000002D** and if so then it sets `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...
It seems to be set by `IEConfiguration_SetBrowserAppProfile` function, so lets check it out...
The function seems to be setting it to the value of the second parameter of the function,and this function seems to be exported at ordinal `#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**
I checked **Edge Legacy** binaries and it seems to be calling this function with the first parameter being "MicrosoftEdge", so lets go ahead and call this function...
```csharp expandable theme={null}
[PreserveSig]
[DllImport("iertutil.dll", EntryPoint = "#797")]
private static extern HRESULT IEConfiguration_SetBrowserAppProfile(char* profile, uint type, uint unk);
internal static bool PatchWebViewAppModelChecks()
{
var appmodel = LoadLibraryA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("kernel.appcore.dll"u8)));
var xaml = LoadLibraryA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("Windows.UI.Xaml.dll"u8)));
var iertutil = LoadLibraryA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("iertutil.dll"u8)));
var kb = GetModuleHandleA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("kernelbase.dll"u8)));
if (appmodel.Value is not null &&
xaml.Value is not null &&
iertutil.Value is not null)
{
var fptr = (delegate* unmanaged[Stdcall])&AppPolicyGetWindowingModelHook;
if (SUCCEEDED_LOG(PatchingHelper.XWinePatchImport(xaml, appmodel, "AppPolicyGetWindowingModel"u8, fptr, 0)))
{
var fptr2 = (delegate* unmanaged[Stdcall])&GetCurrentPackageInfoHook;
if (SUCCEEDED_LOG(PatchingHelper.XWinePatchImport(xaml, appmodel, "GetCurrentPackageInfo"u8, fptr2, 1)))
{
var fptr3 = (delegate* unmanaged[Stdcall])&GetProcAddressHook;
if (SUCCEEDED_LOG(PatchingHelper.XWinePatchImport(iertutil, kb, "GetProcAddress"u8, fptr3, 2)) &&
SUCCEEDED_LOG(IEConfiguration_SetBrowserAppProfile((char*)Unsafe.AsPointer(in MemoryMarshal.GetReference("MicrosoftEdge".AsSpan())), 2, 0)))
{
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
return true;
}
}
}
}
return false;
}
```
## **Results**
Now, lets see if it finally works...
**IT WORKS!!! 🎉**
## **Fixing a Crash on Process Shutdown**
The app seems to throw an exception on shutdown after WebView is loaded
It seems like the **edgehtml** dll trying to call `SubmitThreadpoolWork` when detaching/unloading on process shutdown which [isn't allowed](https://github.com/microsoft/libHttpClient/issues/458), 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:
```csharp expandable theme={null}
internal static void* GetModuleEntryPoint(HMODULE Module)
{
if (Module.Value is null)
return null;
var dosHeader = (IMAGE_DOS_HEADER*)Module;
var ntHeaders = (IMAGE_NT_HEADERS*)((byte*)Module + dosHeader->e_lfanew);
return (byte*)Module + ntHeaders->OptionalHeader.AddressOfEntryPoint;
}
private static void OnProcessExit(object? sender, EventArgs e)
{
foreach (var (Thunk, OriginalFunction) in PatchingHelper.PatchedFunctions)
{
var thunk = (nuint*)Thunk;
uint protect;
if (VirtualProtect(thunk, (nuint)sizeof(nuint), PAGE.PAGE_READWRITE, &protect))
{
*thunk = OriginalFunction;
VirtualProtect(thunk, (nuint)sizeof(nuint), protect, &protect);
}
}
var edge = GetModuleHandleA((sbyte*)Unsafe.AsPointer(in MemoryMarshal.GetReference("edgehtml.dll"u8)));
if (edge.Value is not null)
{
var dllmain = (delegate* unmanaged[Stdcall])PatchingHelper.GetModuleEntryPoint(edge);
if (dllmain is not null)
{
LOG_LAST_ERROR_IF(!dllmain(edge, DLL_PROCESS_DETACH, null));
}
}
}
```
And this fixes the problem!
***That was all, thank you for reading!***
# Getting client's process handle from a COM/WinRT Server
Source: https://ahmed-walid.com/getting-clients-process-handle-from-a-comwinrt-server
Useful for verifying caller client identity
This might be the shortest article ever written, so basically there's a private built-in Call Context interface that allows you to get a handle to client's process.
This can be useful for various of things, for example verifying the caller client identity from your server without having a custom interface for that which can actually be exploited.
So let's start!
Here's the interface definition:
```cpp lines theme={null}
MIDL_INTERFACE("68C6A1B9-DE39-42C3-8D28-BF40A5126541")
ICallingProcessInfo : public IUnknown
{
public:
virtual STDMETHOD(OpenCallerProcessHandle)(DWORD dwDesiredAccess, HANDLE* handle) = 0;
};
```
And here's the usage:
```cpp lines theme={null}
// ComPtr is from WRL, you can use the interface directly instead
ComPtr callingProcessInfo;
CoGetCallContext(__uuidof(ICallingProcessInfo), (void**)callingProcessInfo.GetAddressOf());
HANDLE handle;
callingProcessInfo->OpenCallerProcessHandle(PROCESS_QUERY_LIMITED_INFORMATION, &handle);
```
You can use that handle to get the PID, for example, by using the [GetProcessId](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocessid) function.
And that's it, bye!
# How to create your own NavigationTransitionInfo
Source: https://ahmed-walid.com/how-to-create-your-own-navigation-transition-info
Create your own XAML page transitions
I always wondered how **NavigationTransitionInfo**-s work if they have no publicly exposed animation/navigation data.
Looking at the public definition of `NavigationTransitionInfo` WinRT class, it appears that it implements only 2 interfaces, `INavigationTransitionInfo` and `INavigationTransitionInfoOverrides`
`INavigationTransitionInfo` is totally empty
```csharp theme={null}
namespace Windows.UI.Xaml.Media.Animation
{
[ComImport]
[ExclusiveTo(typeof(NavigationTransitionInfo))]
[ContractVersion(typeof(UniversalApiContract), 65536u)]
[Windows.Foundation.Metadata.Guid(2846904465u, 44618, 17266, 134, 37, 33, 183, 168, 185, 140, 164)]
[WebHostHidden]
internal interface INavigationTransitionInfo
{
}
}
```
`INavigationTransitionInfoOverrides` has 2 members
```csharp theme={null}
namespace Windows.UI.Xaml.Media.Animation
{
[ComImport]
[ExclusiveTo(typeof(NavigationTransitionInfo))]
[ContractVersion(typeof(UniversalApiContract), 65536u)]
[Windows.Foundation.Metadata.Guid(3645996650u, 43472, 19447, 157, 176, 70, 51, 166, 157, 175, 242)]
[WebHostHidden]
internal interface INavigationTransitionInfoOverrides
{
string GetNavigationStateCore();
void SetNavigationStateCore([In] string navigationState);
}
}
```
However, none of these 2 function seem to hold the animation data
So I guessed that there must be another hidden interface used to hold the animation data, so I assumed that it must be called something like `INavigationTransitionInfoPrivate` or `INavigationTransitionInfoInternal`
So I started searching for the dll that implements the **NavigationTransitionInfo** WinRT class, after some search in Registry I found that it's in [***Windows.UI.Xaml.Phone***](http://Windows.UI.Xaml.Phone)***.dll***
I dropped that dll in IDA Pro and started searching for "`INavigationTransitionInfoPrivate`"
And *YES!* there was actually an interface with that name!
Here comes [ADeltaX](https://blog.adeltax.com/)'s big help, he showed me how to get the ***GUID*** and how to locate the ***vftable*** and the interface member functions
Big thanks to ADeltaX!
Now, we have the ***GUID*** (`1ab93e41-46d2-4a19-b20a-e8d042fe5740`) and member functions and their signatures:
```csharp theme={null}
CreateStoryboards([in] Windows::UI::Xaml::UIElement* pElement,
[in] Windows::UI::Xaml::Media::Animation::NavigationTrigger trigger,
[in] Windows::Foundation::Collections::IVector* pStoryboards);
```
Then we had to find out what **NavigationTrigger** is, ADeltaX was able to find it in ***Windows.UI.Xaml*** public symbols:
```cpp theme={null}
enum Windows::UI::Xaml::Media::Animation::NavigationTrigger
{
NavigationTrigger_NavigatingAway = 0x0,
NavigationTrigger_NavigatingTo = 0x1,
NavigationTrigger_BackNavigatingAway = 0x2,
NavigationTrigger_BackNavigatingTo = 0x3
}
```
So now we can write some code!
Wrote the com import and ***enum*** definition code:
```csharp theme={null}
public enum NavigationTrigger
{
NavigatingAway = 0x0,
NavigatingTo = 0x1,
BackNavigatingAway = 0x2,
BackNavigatingTo = 0x3
};
[ComImport, Guid("1ab93e41-46d2-4a19-b20a-e8d042fe5740")]
[InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
public interface INavigationTransitionInfoPrivate
{
void CreateStoryboards(UIElement pElement,
NavigationTrigger trigger,
IList pStoryboards);
}
```
Created custom **NavigationTransitionInfo** class called ***CustomPageTransitionTest*** that inherits from **NavigationTransitionInfo** and **INavigationTransitionInfoPrivate**:
```csharp theme={null}
public class CustomPageTransitionTest : NavigationTransitionInfo, INavigationTransitionInfoPrivate
{
void INavigationTransitionInfoPrivate.CreateStoryboards(UIElement pElement, NavigationTrigger trigger, IList pStoryboards)
{
switch (trigger)
{
case NavigationTrigger.NavigatingAway:
break;
case NavigationTrigger.NavigatingTo:
break;
case NavigationTrigger.BackNavigatingAway:
break;
case NavigationTrigger.BackNavigatingTo:
break;
}
}
//For some reasons, this is required in some cases...
protected override string GetNavigationStateCore()
{
return base.GetNavigationStateCore() ?? "0";
}
}
```
Wrote simple animation code:
```csharp expandable theme={null}
void INavigationTransitionInfoPrivate.CreateStoryboards(UIElement pElement, NavigationTrigger trigger, IList pStoryboards)
{
switch (trigger)
{
case NavigationTrigger.NavigatingAway:
{
CompositeTransform transform = new CompositeTransform();
pElement.RenderTransformOrigin = new Point(0.5, 0.5);
pElement.RenderTransform = transform;
Storyboard board = new Storyboard();
DoubleAnimation anim1 = new DoubleAnimation();
anim1.From = 0;
anim1.To = 360;
anim1.Duration = TimeSpan.FromSeconds(3);
anim1.EnableDependentAnimation = false;
Storyboard.SetTargetProperty(anim1, "Rotation");
Storyboard.SetTarget(anim1, transform);
board.Children.Add(anim1);
DoubleAnimation anim2 = new DoubleAnimation();
anim2.From = 1;
anim2.To = 0;
anim2.Duration = TimeSpan.FromSeconds(3);
anim2.EnableDependentAnimation = false;
Storyboard.SetTargetProperty(anim2, "ScaleX");
Storyboard.SetTarget(anim2, transform);
board.Children.Add(anim2);
DoubleAnimation anim3 = new DoubleAnimation();
anim3.From = 1;
anim3.To = 0;
anim3.Duration = TimeSpan.FromSeconds(3);
anim3.EnableDependentAnimation = false;
Storyboard.SetTargetProperty(anim3, "ScaleY");
Storyboard.SetTarget(anim3, transform);
board.Children.Add(anim3);
DoubleAnimation anim4 = new DoubleAnimation();
anim4.From = 1;
anim4.To = 0;
anim4.Duration = TimeSpan.FromSeconds(3);
anim4.EasingFunction = new SineEase();
anim4.EnableDependentAnimation = true;
Storyboard.SetTargetProperty(anim4, "Opacity");
Storyboard.SetTarget(anim4, pElement);
board.Children.Add(anim4);
pStoryboards.Add(board);
break;
}
case NavigationTrigger.NavigatingTo:
break;
case NavigationTrigger.BackNavigatingAway:
break;
case NavigationTrigger.BackNavigatingTo:
break;
}
}
```
Everything looks perfect, right? ***No***
There's no exception and the navigation waits for the **Storyboard** to finish but... no animation shows on screen.
It turns out that the API doesn't animate the provided **UIElement** directly but another **IFrameworkElement** ***TargetElement*** (private property?) of **UIElement** returned using the helper `GetLogicalTargetElement` private function instead.
Unfortunately I wasn't able to RE the function because many information about it was stripped out from the public symbols.
But luckily some **NavigationTransitionInfo**-s use the ***PropertyPath*** `(UIElement.TransitionTarget)` instead so we can use that.
So using that ***PropertyPath*** I was able to rewrite the code to use it instead:
```csharp theme={null}
void INavigationTransitionInfoPrivate.CreateStoryboards(UIElement pElement, NavigationTrigger trigger, IList pStoryboards)
{
switch (trigger)
{
case NavigationTrigger.NavigatingAway:
{
Storyboard board = new Storyboard();
PointAnimation pAnim = new PointAnimation();
pAnim.From = new Point(0, 0);
pAnim.To = new Point(0.5, 0.5);
pAnim.Duration = TimeSpan.FromMilliseconds(1);
pAnim.EnableDependentAnimation = true;
Storyboard.SetTargetProperty(pAnim, "(UIElement.TransitionTarget).TransformOrigin");
Storyboard.SetTarget(pAnim, pElement);
board.Children.Add(pAnim);
DoubleAnimation anim1 = new DoubleAnimation();
anim1.From = 0;
anim1.To = 360;
anim1.Duration = TimeSpan.FromSeconds(3);
anim1.EnableDependentAnimation = false;
Storyboard.SetTargetProperty(anim1, "(UIElement.TransitionTarget).(TransitionTarget.CompositeTransform).Rotation");
Storyboard.SetTarget(anim1, pElement);
board.Children.Add(anim1);
DoubleAnimation anim2 = new DoubleAnimation();
anim2.From = 1;
anim2.To = 0;
anim2.Duration = TimeSpan.FromSeconds(3);
anim2.EnableDependentAnimation = false;
Storyboard.SetTargetProperty(anim2, "(UIElement.TransitionTarget).(TransitionTarget.CompositeTransform).ScaleX");
Storyboard.SetTarget(anim2, pElement);
board.Children.Add(anim2);
DoubleAnimation anim3 = new DoubleAnimation();
anim3.From = 1;
anim3.To = 0;
anim3.Duration = TimeSpan.FromSeconds(3);
anim3.EnableDependentAnimation = false;
Storyboard.SetTargetProperty(anim3, "(UIElement.TransitionTarget).(TransitionTarget.CompositeTransform).ScaleY");
Storyboard.SetTarget(anim3, pElement);
board.Children.Add(anim3);
DoubleAnimation anim4 = new DoubleAnimation();
anim4.From = 1;
anim4.To = 0;
anim4.Duration = TimeSpan.FromSeconds(3);
anim4.EasingFunction = new SineEase();
anim4.EnableDependentAnimation = true;
Storyboard.SetTargetProperty(anim4, "(UIElement.TransitionTarget).Opacity");
Storyboard.SetTarget(anim4, pElement);
board.Children.Add(anim4);
pStoryboards.Add(board);
break;
}
case NavigationTrigger.NavigatingTo:
break;
case NavigationTrigger.BackNavigatingAway:
break;
case NavigationTrigger.BackNavigatingTo:
break;
}
```
### **The moment of the truth**
did it work? let's find out...
***YES!*** it did!
# Index
Source: https://ahmed-walid.com/index
Index of all blog posts
XAML Patching XAML Composition COM WinRT
XAML Composition XAML Animations
# Reverse XAML Islands?
Source: https://ahmed-walid.com/reverse-xaml-islands
Hosting HWNDs inside XAML's visual tree
Hosting legacy Win32 content/window in XAML? Seems impossible, right? ***NO.***
The story started when I noticed [IDCompositionDevice::CreateSurfaceFromHwnd](https://docs.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice-createsurfacefromhwnd) function in the public docs of **DirectComposition.**
But I realized that we have a problem, this is for **DirectComposition** while XAML only *publicly* exposes **Windows.UI.Composition** objects
After some investigations on ***Windows.UI.Xaml.dll*** with IDA Pro, I noticed that XAML thankfully creates the **Windows.UI.Composition** device using **InteropCompositor** which allows you to cast **Windows.UI.Composition** objects into **DirectComposition** objects and vice versa. **GREAT!**
You can read more about **InteropCompositor** in this great blogpost by **ADeltaX**: [InteropCompositor (and CoreDispatcher)](https://blog.adeltax.com/interopcompositor-and-coredispatcher/)
So the next problem was that the window has to be a layered (**WS\_EX\_LAYERED**) child (**WS\_CHILD**) window for ["cloak"ing](https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute) to work but we can't set our legacy window as a child window to the **ApplicationFrameWindow** window because it's not owned by our process but ***ApplicationFrameHost.exe.***
But *thankfully (again!)* the **CoreWindow** window is owned by our process so we can use it to be the parent of our legacy child window. **GREAT!**
So let's create our child window\...
```csharp expandable theme={null}
IntPtr hwnd = IntPtr.Zero;
List childHwnds = new List();
...
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct WindowCompositionAttribData
{
public _WINDOWCOMPOSITIONATTRIB Attribute;
public int * Data;
public int SizeOfData;
}
[DllImport("ext-ms-win-ntuser-private-l1-1-1.dll", SetLastError = true)]
internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttribData data);
public static unsafe void SetWindowCloak(IntPtr handle)
{
int flag = 1;
var data = new WindowCompositionAttribData();
data.Attribute = _WINDOWCOMPOSITIONATTRIB.WCA_CLOAK; //17
data.SizeOfData = sizeof(int);
data.Data = & flag;
SetWindowCompositionAttribute(handle, ref data);
int result = Marshal.GetLastWin32Error();
if (result != 0)
{
throw new Win32Exception(result);
}
}
public static IntPtr EnableVisualStyles()
{
StringBuilder sbSystemDir = new StringBuilder(256);
GetSystemDirectory(sbSystemDir, 256);
ACTCTX actCtx = new ACTCTX();
actCtx.cbSize = Marshal.SizeOf(typeof(ACTCTX));
actCtx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID |
ACTCTX_FLAG_SET_PROCESS_DEFAULT |
ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID;
actCtx.lpSource = "shell32.dll";
actCtx.wProcessorArchitecture = 0;
actCtx.wLangId = 0;
actCtx.lpAssemblyDirectory = sbSystemDir.ToString();
actCtx.lpResourceName = (IntPtr) 124;
IntPtr AcCtx = CreateActCtx(ref actCtx);
ActivateActCtx(AcCtx, out IntPtr cookie);
return cookie;
}
...
IntPtr coreWnd = ((ICoreWindowInterop) (object) CoreWindow.GetForCurrentThread()).WindowHandle;
IntPtr hInstance = Process.GetCurrentProcess().Handle;
string szAppName = "HelloWin";
WNDCLASS wndclass = new WNDCLASS();
wndclass.style = (uint)(ClassStyles.HorizontalRedraw | ClassStyles.VerticalRedraw);
wndclass.lpfnWndProc = (WndProc)((hWnd, message, wParam, lParam) =>
{
IntPtr hdc;
PAINTSTRUCT ps;
RECT rect;
switch ((WM) message)
{
case WM.PAINT:
{
hdc = BeginPaint(hWnd, out ps);
GetClientRect(hWnd, out rect);
FillRect(hdc, ref rect, GetStockObject(StockObjects.WHITE_BRUSH));
EndPaint(hWnd, ref ps);
return IntPtr.Zero;
break;
}
case WM.DESTROY:
{
return IntPtr.Zero;
break;
}
case WM.CREATE:
{
return IntPtr.Zero;
break;
}
}
return DefWindowProc(hWnd, (WM) message, wParam, lParam);
});
wndclass.hInstance = hInstance;
wndclass.lpszMenuName = null;
wndclass.lpszClassName = szAppName;
var regResult = RegisterClass(ref wndclass);
EnableVisualStyles();
hwnd = CreateWindowEx(
(uint)(WindowStylesEx.WS_EX_LAYERED | WindowStylesEx.WS_EX_COMPOSITED),
(ushort) new IntPtr((int)(uint) regResult),
"The Hello Program", // window caption
(uint) WindowStyles.WS_CHILD, // window style
0, // initial x position
0, // initial y position
500, // initial x size
500, // initial y size
coreWnd, // parent window handle
IntPtr.Zero, // window menu handle
hInstance, // program instance handle
IntPtr.Zero); // creation parameters
var ctls = new INITCOMMONCONTROLSEX(CommonControls.ICC_STANDARD_CLASSES | CommonControls.ICC_PROGRESS_CLASS);
InitCommonControlsEx(ref ctls);
ShowWindow(hwnd, (int) ShowWindowCommands.Normal);
SetWindowCloak(hwnd);
IntPtr hwndButton = CreateWindow(
"BUTTON", // Predefined class
"Hello from Comctl32!", // Button text
(uint) WindowStyles.WS_VISIBLE | (uint) WindowStyles.WS_CHILD, // Styles
0, // x position
0, // y position
200, // Button width
100, // Button height
hwnd, // Parent window
IntPtr.Zero, // No menu.
hInstance,
IntPtr.Zero); // Pointer not needed.
IntPtr hwndProg = CreateWindow(
"msctls_progress32", // Predefined class
null, // text
(uint) WindowStyles.WS_VISIBLE | (uint) WindowStyles.WS_CHILD | 0x08, // Styles
0, // x position
110, // y position
200, // width
35, // height
hwnd, // Parent window
IntPtr.Zero, // No menu.
hInstance,
IntPtr.Zero); // Pointer not needed.
SendMessage(hwndProg, (int)(WM.USER + 10), 1, 0); //Starts ProgressBar animation
childHwnds.Add(hwndButton);
```
**Notes:**
* **WS\_EX\_COMPOSITED** extended style is required for the window to render
* **ICoreWindowInterop** is defined in the public Windows SDK, you can copy the definition from there
* **CreateWindowEx, InitCommonControlsEx, ShowWindow, SendMessage, ...** are native Windows functions, you can **DllImport** them from the corresponding dlls for them, I got most of definitions from [pinvoke.net](http://pinvoke.net), same with structs used in the code
Now, let's create our Visual...
```csharp lines theme={null}
Compositor comp = Window.Current.Compositor;
IDCompositionDevice dcomp = (IDCompositionDevice)(object)comp;
IDCompositionVisual visualRaw = dcomp.CreateVisual();
visualRaw.SetOffsetX(0);
visualRaw.SetOffsetY(0);
visualRaw.SetContent(dcomp.CreateSurfaceFromHwnd(hwnd));
Visual visual = (Visual)visualRaw;
visual.Size = new Vector2(500, 500);
// "TheBox" is just a Viewbox control defined in page's XAML, you can use any XAML UIElement
ElementCompositionPreview.SetElementChildVisual(TheBox, visual);
```
Now, let's handle input *(the code sucks, I don't recommend using it)*...
```csharp expandable theme={null}
// We have to use CoreWindow pointer events because UIElement pointer events didn't work for some reasons...
CoreWindow coreWindow = CoreWindow.GetForCurrentThread();
coreWindow.PointerPressed += CoreWindow_PointerPressed;
coreWindow.PointerReleased += CoreWindow_PointerReleased;
...
private static int MAKELPARAM(int p, int p2)
{
return ((p2 << 16) | (p & 0xFFFF));
}
...
private void CoreWindow_PointerReleased(CoreWindow sender, PointerEventArgs args)
{
var ttv = this.TransformToVisual(TheBox);
Point point = ttv.TransformPoint(args.CurrentPoint.Position);
if (point.X > 0 && point.Y > 0)
{
SendMessage(hwnd, (uint) WM.LBUTTONUP, 0, MAKELPARAM((int) point.X, (int) point.Y));
foreach(IntPtr wnd in childHwnds)
{
SendMessage(wnd, (uint) WM.LBUTTONUP, 0, MAKELPARAM((int) point.X, (int) point.Y));
}
}
}
private void CoreWindow_PointerPressed(CoreWindow sender, PointerEventArgs args)
{
var ttv = this.TransformToVisual(TheBox);
Point point = ttv.TransformPoint(args.CurrentPoint.Position);
if (point.X > 0 && point.Y > 0)
{
SendMessage(hwnd, (uint) WM.LBUTTONDOWN, 0x0001, MAKELPARAM((int) point.X, (int) point.Y));
foreach(IntPtr wnd in childHwnds)
{
SendMessage(wnd, (uint) WM.LBUTTONDOWN, 0x0001, MAKELPARAM((int) point.X, (int) point.Y));
}
}
}
```
We can use [CoreIndependentInputSource](https://github.com/BreeceW/InteractionTrackerCrossProcessVisuals/pull/4) or **Windows::UI::Internal::Input::IInputSite** to receive input on the Visual itself so we don't need to use **CoreWindow** pointer events or transforming the **Point**, but let's leave that for another blogpost...
And here's the result!