How to Hook DirectX 11 + ImGui (Vermintide 2)

Why would we hook DirectX?

When creating a cheat for a game, rendering additional content or modifying how the models are rendered into the game is something that sooner or later might be required. There are multiple techniques to achieve this, but one of the most common ones is to hook the 3D graphics component of DirectX API. This API is well documented and all the information can be found publicly on the internet.

DirectX 9 vs DirectX 11

If we want to intercept what is being rendered by the game and manipulate it we need to know that there are multiple version of this API and each version has its own rendering process. DirectX9 is an older version that has been used widely for many years for the majority of the games, but recently I came across the need to hook a game using DirectX 11 and despite having plenty of information on the internet I had faced multiple problems to get this working properly. Especially when I tried to render the interface with ImGUI.

When using DirectX 9 the rendering process of the game looks something like this:

void drawing()
{
	device->clear(...);
	device->BeginScene();
	//the drawing process
	device->EndScene();
	device->Present();
}

For DirectX 11 the drawing loop changed a little bit:

void drawing()
{
	// Update the scene.
	renderer->Update();

	// Render frames during idle time (when no messages are waiting).
	renderer->Render();

	// Present the frame to the screen.
	deviceResources->Present();
}
https://docs.microsoft.com/en-us/windows/desktop/direct3dgetstarted/work-with-dxgi.

For the old version, the main goal is to obtain a reference to the instance of the IDirect3DDevice9  object used by the game. Having a pointer to this object will give us the power to invoke all sorts of drawing calls. There are multiple ways to achieve this:  hooking IDirect3D9::CreateDevice; create a dummy IDirect3DDevice9 and use the VF to get the address of EndScene and hook it; analyze the symbols from d3d9.dll and obtain the address of EndScene to hook it.

However, for version 11, a few things have changed. We need to obtain a reference to the object IDXGISwapChain and we will use this object to obtain all the additional instances of ID3D11Device, ID3D11DeviceContext, and ID3D11RenderTargetView. And we will see in the code explained below why we need all these objects.

Hooking DX11

We are going to see briefly the required code used to hook successfully DirectX 11 and obtain the references to the objects. After that, we will render the demo interface of ImGui. In order to achieve this, we are going to be using Detours to create a trampoline and hook the DirectX API.

If you are not aware of what trampoline are, I really recommend you to read this post. For the sake of simplicity, we are going to be using Detours to inject a jmp to our method and then return to the original function. If you are pretending to create a Cheat for a game that has Anti-cheat software, you may want to do this manually, since anti-cheats usually look for the signature of software like Detours.

In this case, since we are doing this for Vermintide 2 we have to do it for x64 and remember that the compiler of Visual Studio stopped supporting naked methods and inline assembler for x64. Naked removes the assembly prologue and epilogue and we need this to make sure the stack frame stays equivalent for the call. There are a few alternatives to achieve this (as you may have read in the trampoline post). However, for this example, I decided to use Detours because I wanted to give it a try.

I started reading multiple posts about how to hook Present and I came across this post by@timb3r1 that explains it very well and gives you a great introduction about how to do it. Nevertheless, as always, things didn’t work as expected. I had to learn a few things on the way in order to make this work, and from there comes my motivation to make this blog post.

ImGui

I came across multiple problems when I wanted to use this library. You should know that developers of ImGui are not cheaters friendly so creating issues on their Github doesn’t really help so much. Again, if you are pretending to create a cheat for a game with anti-cheat, ImGui shouldn’t be your first choice, there are tons of cheats being sold online that make use of it and it won’t surprise me to find its signature in multiple of them.

Important points:

  • Built for VS2017
  • Contains detours header and .lib file.
  • Contains all headers for ImGui and their implementation (version v1.67).
  • Makes use of the imgui_impl_win32.cpp implementation fixed by @timb3r here.
  • A great introduction about how to hook DirectX 11 is done here. This helped me a lot.

Getting the SwapChain

The first thing we need to do is to create our own IDXGISwapChain::Present  method, which will be called instead of the original. I made some modifications to the methods created in the post mentioned above, this are two of the main methods:

HRESULT __fastcall Present(IDXGISwapChain *pChain, UINT SyncInterval, UINT Flags)
{
	if (!g_bInitialised) {
		std::cout << "\t[+] Present Hook called by first time" << std::endl;
		if (FAILED(GetDeviceAndCtxFromSwapchain(pChain, &pDevice, &pContext)))
			return fnIDXGISwapChainPresent(pChain, SyncInterval, Flags);
		pSwapChain = pChain;
		DXGI_SWAP_CHAIN_DESC sd;
		pChain->GetDesc(&sd);
		ImGui::CreateContext();
		ImGuiIO& io = ImGui::GetIO(); (void)io;
		io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
		window = sd.OutputWindow;

		//Set OriginalWndProcHandler to the Address of the Original WndProc function
		OriginalWndProcHandler = (WNDPROC)SetWindowLongPtr(window, GWLP_WNDPROC, (LONG_PTR)hWndProc);

		ImGui_ImplWin32_Init(window);
		ImGui_ImplDX11_Init(pDevice, pContext);
		ImGui::GetIO().ImeWindowHandle = window;

		ID3D11Texture2D* pBackBuffer;

		pChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
		pDevice->CreateRenderTargetView(pBackBuffer, NULL, &mainRenderTargetView);
		pBackBuffer->Release();

		g_bInitialised = true;
	}
	ImGui_ImplWin32_NewFrame();
	ImGui_ImplDX11_NewFrame();

	ImGui::NewFrame();
	//Menu is displayed when g_ShowMenu is TRUE
	if (g_ShowMenu)
	{
		bool bShow = true;
		ImGui::ShowDemoWindow(&bShow);
	}
	ImGui::EndFrame();

	ImGui::Render();

	pContext->OMSetRenderTargets(1, &mainRenderTargetView, NULL);
	ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());

	return fnIDXGISwapChainPresent(pChain, SyncInterval, Flags);
}
Present
HRESULT GetDeviceAndCtxFromSwapchain(IDXGISwapChain *pSwapChain, ID3D11Device **ppDevice, ID3D11DeviceContext **ppContext)
{
	HRESULT ret = pSwapChain->GetDevice(__uuidof(ID3D11Device), (PVOID*)ppDevice);

	if (SUCCEEDED(ret))
		(*ppDevice)->GetImmediateContext(ppContext);

	return ret;
}
GetDeviceAndCtxFromSwapchain

I want to explain a few things that may be useful if you are having troubles to successfully hook DirectX and may come in handy in the future. The following lines use the method IDXGISwapChain::GetDesc to obtain a HWND to the current window of the game (remember that we are working from inside the process):

DXGI_SWAP_CHAIN_DESC sd;
pChain->GetDesc(&sd);
window = sd.OutputWindow;

Having this HWND is useful because we will use SetWindowLongPtr to change some behaviors from the window:

//Set OriginalWndProcHandler to the Address of the Original WndProc function
OriginalWndProcHandler = (WNDPROC)SetWindowLongPtr(window, GWLP_WNDPROC, (LONG_PTR)hWndProc);

The next step is very important because if you don’t do it you will inject the external UI succesfully but you won’t be able to interact with it. hWndProc will be used to intercept the keyboard and mouse inputs to the window and redirect them to our ImGUI interface whenever it’s required:

LRESULT CALLBACK hWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	ImGuiIO& io = ImGui::GetIO();
	POINT mPos;
	GetCursorPos(&mPos);
	ScreenToClient(window, &mPos);
	ImGui::GetIO().MousePos.x = mPos.x;
	ImGui::GetIO().MousePos.y = mPos.y;

	if (uMsg == WM_KEYUP)
	{
		if (wParam == VK_DELETE)
		{
			g_ShowMenu = !g_ShowMenu;
		}

	}

	if (g_ShowMenu)
	{
		ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam);
		return true;
	}

	return CallWindowProc(OriginalWndProcHandler, hWnd, uMsg, wParam, lParam);
}

If you pay attention to last piece of code, when the dll detects that the “delete” key has been pressed it will change the value of `g_ShowMenu` to its opposite value, which will be evaluated in the next “if” to see if the inputs need to be sent to our interface or to the game.

In case that the if statement results as false, all input data will be sent to the OriginalWndProcHandler directly. This means that the ImGui interface is not active. On the other hand, when the if statement is evaluated as true, the ImGui_ImplWin32_WndProcHandler function will be called. This method has been implemented inside imgui_impl_win32.cpp and it will properly process mouse/keyboard inputs. Something that worth mention is that Vermintide 2 makes use of the game engine Bitsquid/Autodesk and unfortunately, ImGui seems to have some troubles to detect the game window when its injected in games that use this engine or Unreal. That’s why I decided to use a different implementation of imgui_impl_win32.cpp

In order to trigger all this process we need to create a trampoline/detour IDXGISwapChain::Present, this can be achieve with the following piece of code that will make use of the library Detours, which will replace the original opcodes by a jmp to a region on memory with our new injected code and then return it to the correct place:

void detourDirectX()
{
	std::cout << "[+] Calling DirectX Detour" << std::endl;
	DetourTransactionBegin();
	DetourUpdateThread(GetCurrentThread());
	// Detours the original fnIDXGISwapChainPresent with our Present
	DetourAttach(&(LPVOID&)fnIDXGISwapChainPresent, (PBYTE)Present); 
	DetourTransactionCommit();
}

The main:

To sum up, we need the main method that will put everything together:

int WINAPI main()
{
	ConsoleSetup();
	retrieveValues();
	// After this call, Present should be hooked and controlled by me.
	detourDirectX();
	Sleep(2000);
	printValues();
}

ConsoleSetup() will open the console you can see below and we’ll be able to print content to it, and if we have enough luck, we could get information printed by the game.

If you paid enough attention to the output you may have realized the game is printed when the eac_serverl.dll (anti-cheat) is loaded into the process.

retrieveValues() is the one in charge of getting the real address of Present:

DWORD_PTR hDxgi = (DWORD_PTR)GetModuleHandle("dxgi.dll");
fnIDXGISwapChainPresent = (IDXGISwapChainPresent)((DWORD_PTR)hDxgi + 0x5070);

Becareful with the offset 0x5070, this may change from one game to another.

And finally we have the call to our detour function and a gap of time to the game to call Present at least once, the rest will depend on what you want to do with your hook and ImGui:

detourDirectX();
Sleep(2000);

The result:

Downloads

In the following zip you will find a template for Visual Studio 2017 that you could use to hook games that use DirectX11. The template also hook the IO as explained above, required to use properly ImGui.

Note that the libraries are included inside the project, you should update them when required. Additionally, the offset used to hook the Present method may differ from one game to another, pay attention to that.

Useful content: