A DLL that hooks IDXGISwapChain::Present in the DirectX 11 ImGui example and renders an overlay into the game's swap chain using ImGui. The goal was to demonstrate reverse engineering and runtime hooking skills on a real target.
Most DX11 overlays work by finding the swap chain at runtime and detouring its Present method. I wanted to build one from scratch starting with IDA, finding the swap chain pointer in the binary, and ending with a working injected DLL that draws a menu over the target.
- IDA - used for static analysis to locate the swap chain and the offset of its global pointer
- Visual Studio - compiled the injected DLL
The reversing was straightforward once I knew what to look for. All DX11 applications create their swap chain through a small set of D3D11 API calls, so I started by searching for those imports.
I searched for anything referencing SwapChain and found these two functions:
The call was made through the import thunk j_D3D11CreateDeviceAndSwapChain, which jumps into the real D3D11CreateDeviceAndSwapChain exported by d3d11.dll. Looking at the call site, I could see the ppSwapChain output parameter being passed in:
&ppSwapChain is a reference to a module-level global, so I clicked through to the symbol in .data and noted its address:
After rebasing the image to 0x0 (so addresses displayed in IDA match RVAs directly), the global lived at RVA 0x2C2C48. At runtime, the address is:
swapchain_ptr = module_base + 0x2C2C48
That single offset was all I needed.
The DLL polls the swap chain global until the game creates it. Once it's non-null, I patch slot 8 of the swap chain's vtable (Present) with my own function. My hook runs the ImGui frame, draws the menu, then calls the original Present so the game's rendering is unaffected.
The menu displays the swap chain pointer, the original Present address, and the hooked Present address to verify the detour took effect.
IDXGISwapChain is a COM interface, and like all COM interfaces it inherits from IUnknown. The vtable is built in inheritance order:
| Offset | Slot | Method |
|---|---|---|
| 0x00 | 0 | QueryInterface |
| 0x08 | 1 | AddRef |
| 0x10 | 2 | Release |
| 0x18 | 3 | SetPrivateData |
| 0x20 | 4 | SetPrivateDataInterface |
| 0x28 | 5 | GetPrivateData |
| 0x30 | 6 | GetParent |
| 0x38 | 7 | GetDevice |
| 0x40 | 8 | Present |
| 0x48 | 9 | GetBuffer |
| ... | ... | ... |
This layout is fixed by the DirectX SDK headers, so Present is always at slot 8 on any Windows machine. I confirmed it in IDA by dumping the IDXGISwapChainVtbl struct from the type library.
Inject DLL
│
▼
read module_base + 0x2C2C48
│
▼
poll until *ppSwapChain != null
│
▼
vtable = *(void***)pSwapChain
│
▼
save old Present pointer
overwrite slot 8 with hk_present
│
▼
game calls Present → hk_present
│
├─ ImGui frame: NewFrame / draw_menu / Render
│
└─ call original Present (game renders normally)
│
▼
frame presented
- Open
DX11_hook_example.slnin Visual Studio 2026 - Set configuration to Release / x64 (must match the target's bitness)
- Build — the DLL outputs to
x64/Release/
- Launch the ImGui DX11 example (
example_win32_directx11.exe) - Inject
DX11_hook_example.dllwith any injector (Xenos, Extreme Injector, or any other) - A console window opens and logs the hook status
- The menu renders on top of the target window
ResizeBuffersis not yet hooked — resizing the window may leave the overlay stretched or crash the target- The back-buffer render target view is recreated every frame instead of being cached
- The
WndProchook is not restored when the DLL unloads - Only tested against the ImGui DX11 example; other engines may use
D3D11CreateDevice+IDXGIFactory::CreateSwapChaininstead, which would require an additional hook point
This project uses Dear ImGui, which is licensed under the MIT License. The full license text is available in the imgui/LICENSE.txt file within this repository.
This project is provided for educational and research purposes only. It is intended to demonstrate reverse engineering and runtime hooking techniques on software you own or have explicit permission to modify. The author is not responsible for any misuse.



