Using DirectX as a Detection Vector
Table of Contents
How it Works
In DirectX 12, the ID3D12Device::CreateDescriptorHeap function is responsible
for creating
descriptor
heaps, which store
views of resources like textures, constant buffers, and samplers. Every modern DirectX 12
application uses
this function
to allocate GPU descriptors.
ImGui's Use of CreateDescriptorHeap
ImGui's official DirectX 12 backend calls CreateDescriptorHeap during
initialization to
build shader
resource view (SRV)
descriptor heaps for its font atlas and any other textures it needs to render UI. This means that whenever
an overlay or
injected ImGui menu is loaded into a game, CreateDescriptorHeap will inevitably be called. For
anticheat
developers,
this creates a predictable detection vector: monitor the integrity of the CreateDescriptorHeap
signature, and
you can
identify whether a third-party library like ImGui has hooked it.
Inline Hooking of CreateDescriptorHeap
An inline hook works by overwriting the function's first few bytes with a relative jmp instruction that
transfers control to custom code. In d3d12core.dll, the signature of
CreateDescriptorHeap
typically begins
with the following bytes:
These instructions save callee saved registers, a normal part of the function's setup. When an inline hook is applied, these bytes are replaced with a jump instruction. For example:
The E9 opcode is a relative jump in x86-64, followed by a 4-byte offset to the hook's
destination address.
A nop is
sometimes added for padding if the overwritten instruction block is longer than 5 bytes.
With this in place, every call to CreateDescriptorHeap first executes the malicious
jump,
transferring
execution to the
cheat's custom handler.
- Inspect or modify descriptor heap creation parameters.
- Log calls for overlay rendering
(e.g., ImGui). - Pass control back to the original function via a trampoline.
For anticheat systems, CreateDescriptorHeap serves as a reliable detection vector: by hooking
this
function, the
anticheat can monitor when overlays like ImGui initialize, since they must call it to
create their
descriptor heaps.
This makes it a lightweight but effective way to catch injected menus, especially when paired with
call-origin checks.
The Bypass
Aware of this detection vector, a cheat developer could attempt to restore the original signiture bytes
after
their own
code has been injected. By doing so, the inline jmp is overwritten with the expected sequence (0x40 0x53
0x56 0x57 0x41 0x56)
effectively “unhooking” the function in memory while still leaving their injected cheat active.
The code snippet below shows a simplified version of this idea: