In 2012, Microsoft introduced “DirectComposition”, a technology that helps improve performance for bitmap drawings & compositions tremendously, the way it works is that it utilizes the graphics hardware to compose & render objects, which means that it’ll run independently, aside from the main UI thread.
It can therefore be deduced that there must be a layer of interaction, or a method to apply the composition onto the desired window, or target, abusing this layer of interaction is the main target of today’s article.
The layer of interaction that DirectCompositions use, are objects called “targets” & “visuals”, every IDCompositionTarget will be created by a respective API function that depends on a window handle, and every target will depend on a IDCompositionVisual which contains the visual content represented on the screen.
If you think that you can easily just create a window, then compose on-top of another window from a non-owning process, then you’re wrong. This will cause an error, and the composition won’t be created.
Reversal
Opening up win32kfull, which is the kernel-mode component for DWM, GDI & other windows features then searching for “DComposition” will yield multiple results:
The one we’re interested in is NtUserCreateDCompositionHwndTarget
, according to it’s prototype: __int64 (HWND a1, int a2, _QWORD *a3)
, we can induce that this is simply just IDCompositionDevice::CreateTargetForHwnd
, and the parameters are: (HWND hwnd, BOOL topmost, IDCompositionTarget** target)
.
At the very start of this function there’s a test that checks whether you can create a target for this composition or not:
last_status = TestWindowForCompositionTarget(window_handle, top_most);
This is a simplified form of that function:
NTSTATUS TestWindowForCompositionTarget(HWND window_handle, BOOL top_most)
{
tagWND* window_instance = ValidateHwnd(window_handle);
if (!window_instance
|| !window_instance->thread_info)
return STATUS_INVALID_PARAMETER;
// some checks here to verify that DCompositions are supported, and available
PEPROCESS calling_process = IoGetCurrentProcess();
PEPROCESS owning_process = PsGetThreadProcess(window_instance->thread_info->owning_thread); // tagWnd*->tagTHREADINFO*->KTHREAD*
if (calling_process != owning_process)
return STATUS_ACCESS_DENIED;
CHwndTargetProp target_properties{};
if (CWindowProp::GetProp<CHwndTargetProp>(window_instance, &target_properties))
{
bool unk_error = false;
if (top_most)
unk_error = !(target_properties.top_most_handle == nullptr);
else
unk_error = !(target_properties.active_bg_handle == nullptr);
if (unk_error)
return (NTSTATUS)0x803e0006; // unique error code, i don't know what it's supposed to resemble
}
return STATUS_SUCCESS;
}
The check causing failures is if (calling_process != owning_process)
, this compares the caller’s process to the window’s owner process, and if this check fails they return a STATUS_ACCESS_DENIED error.
They retrieve the window’s owner process by calling ValidateHwnd
, which is a function used everywhere in win32k:
This function will return a pointer to a struct of type tagWND
, then access a member of type tagTHREADINFO
at +0x10 (window_instance->thread_info), then access the actual thread pointer at +0x0 (thread_info->owning_thread).
One way to circumvent these checks is to swap the owning thread of the process’ window to our window temporarily, compose our target on it then swap it back very quickly, which is what the PoC is based on.
Proof Of Concept
I’ve made a PoC, that’ll hijack a window by it’s class name, then render a rectangle at it’s center. you can access the code here.