Improving your Cheat Template (Hooking DirectX11)

Template

For the ones that are interested in improving your code, this may be a post for you. In the last few weeks, I have been posting about hooking and DirectX11 (here). Programming this template and solving the challenges to achieve each goal has been a lot of fun but I have modified the code so many times until it met my expectations. This will be a really quick post to tell you some optimization I have implemented in the template that I found interesting, here is the list:

  • Wiping the model list
  • Enable/Disable Wallhack and Logger
  • Wait for the hook to be executed
  • Compilation/Validation for x86 and x64
  • Implement a backup hook for Present

Wiping the model list

The first thing I want to mention is something that came to my mind while I was holding the PG_UP key trying to find the correct model for a game. After waiting for minutes I decided to find a solution to that. What happens if we wipe the list of properties models that we already have while we are in front of the object we want to reveal? The models that we are seeing at that precise moment will be the first to get into our list.

if (GetAsyncKeyState(VK_DELETE) & 1)
{
	bDrawIndexed = true;
	std::cout << "[+] Model Log cleared" << std::endl;
	seenParams.clear();
}

By doing this, it’s possible to accelerate the identification of the Models we want, and easily grab their properties values. The mechanic is simple, you just go and stand in front of the model/object you need and now you press DELETE. After that, you just simply move forward on the list and that model will be highlighted with just a few clicks 😉 Voalá!

Enable/Disable Wallhack and Logger

We will be constantly rendering models and comparing each of them to the one we want to reveal or highlight. This will decrease heavily the performance of the game, of course. Having a simple option to enable disable these features will help a lot during testing and development of a cheat. So remember to add some validation and keys to do this:

if (GetAsyncKeyState(VK_F9) & 1)
{
	bShader = !bShader;
	bTexture = !bTexture;
	if (bShader)
	{
		std::cout << "[+] Shader Mode Enabled" << std::endl;
	} 
	else
	{
		std::cout << "[+] Texture Mode Enabled" << std::endl;
	}
}
if (GetAsyncKeyState(VK_F10) & 1)
{
	bWallhack = !bWallhack;
	if (bWallhack)
	{
		std::cout << "[+] Wallhack Enabled" << std::endl;
	}
	else
	{
		std::cout << "[+] Wallhack Disabled" << std::endl;
	}
}

Wait for the hook to be executed

Sometimes we just add Sleeps everywhere to give time to some process/method to run, but we have to think that this time may change from one computer or game to another. After testing the template in different games I found that the hook was crashing sometimes because the main method was trying to continue while the hook hadn’t been executed yet. A quick change to the code fixed this error:

// After this call, Present should be hooked and controlled by me.
detourDirectXPresent();
while (!g_bInitialised) {
	Sleep(1000);
}

If you don’t like infinite loops, you could implement also a counter and unload the DLL if the hook never gets executed.

Compilation/Validation for x86 and x64

When you compile a DLL for different architectures there are multiple things that will change, especially when you are hooking and manipulating run time memory or an external DLL. The first thing you may think of is offsets. Yes, exactly offsets of DirectX or whatever library you are hooking will vary from x32 to x64. A simple modification to your code will make your DLL work in both architectures:

// Check windows
#if _WIN32 || _WIN64
#if _WIN64
#define ENV64BIT
#else
#define ENV32BIT
#endif
#endif


void retrieveValues()
{
	DWORD_PTR hDxgi = (DWORD_PTR)GetModuleHandle("dxgi.dll");
	#if defined(ENV64BIT)
		fnIDXGISwapChainPresent = (IDXGISwapChainPresent)((DWORD_PTR)hDxgi + 0x5070);
	#elif defined (ENV32BIT)
		fnIDXGISwapChainPresent = (IDXGISwapChainPresent)((DWORD_PTR)hDxgi + 0x10230);
	#endif
	std::cout << "[+] Present Addr: " << std::hex << fnIDXGISwapChainPresent << std::endl;
}

Implement a backup hook for Present

When I made the first post we hook Present function by finding its offset inside dxgi.dll. After that, we added this value to the base address of the module to get the real address of Present. But will this always work? There are multiple versions of DirectX11, and this technique may fail. Some methods are implemented at run time and a VT is created for each object as we seen before with DrawIndexed.

Therefore we are going to implement a “backup” method. One that we are going to call first, and if this method fails we are going to use our first technique. What we are going to see now, you might have seen it before, we did it just the same with DrawIndexed. But how does it work for hooking Present?

  1. We are going to create a fake IDXGISwapChain
  2. Then we are going to obtain the 8th pointer of its VT
  3. Finally, we are going to deference this pointer and store it

Creating a fake IDXGISwapChain

This can be kind of tricky but in order to create a fake IDXGISwapChain, we are going to need two things: A new windows handler and a DXGI_SWAP_CHAIN_DESC. If you read the Microsoft documentation you will notice that DXGI_SWAP_CHAIN_DESC contains a lot of attributes, we will need to define them all in order to be able to create a new SwapChain.

First things first, let’s get a new window handle:

WNDCLASSEXA wc = { sizeof(WNDCLASSEX), CS_CLASSDC, DXGIMsgProc, 0L, 0L, GetModuleHandleA(NULL), NULL, NULL, NULL, NULL, "DX", NULL };
RegisterClassExA(&wc);
HWND hWnd = CreateWindowA("DX", NULL, WS_OVERLAPPEDWINDOW, 100, 100, 300, 300, NULL, NULL, wc.hInstance, NULL);

Now, let’s define all the properties of our DXGI_SWAP_CHAIN_DESC:

DXGI_SWAP_CHAIN_DESC sd;
ZeroMemory(&sd, sizeof(sd));
sd.BufferCount = 1;
sd.BufferDesc.Width = 2;
sd.BufferDesc.Height = 2;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = hWnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

The most important part here is to set sd.OutputWindow as our newly created windows handle. To create our SwapChain we are going to call to D3D11CreateDeviceAndSwapChain and send all the required parameters. Therefore, we will need some additional variables to be defined:

D3D_FEATURE_LEVEL FeatureLevelsRequested = D3D_FEATURE_LEVEL_11_0;
UINT numFeatureLevelsRequested = 1;
D3D_FEATURE_LEVEL FeatureLevelsSupported;
HRESULT hr;
IDXGISwapChain *swapchain = 0;
ID3D11Device *dev = 0;
ID3D11DeviceContext *devcon = 0;

It is important to define D3D_FEATURE_LEVEL with the value D3D_FEATURE_LEVEL_11_0. This will define our DirectX target that we are going to hook. Everything is ready to call to D3D11CreateDeviceAndSwapChain, remember that we will need to manage the return value to validate if everything worked and decide if we have to call our backup method or not:

if (FAILED(hr = D3D11CreateDeviceAndSwapChain(NULL,
		D3D_DRIVER_TYPE_HARDWARE,
		NULL,
		0,
		&FeatureLevelsRequested,
		numFeatureLevelsRequested,
		D3D11_SDK_VERSION,
		&sd,
		&swapchain,
		&dev,
		&FeatureLevelsSupported,
		&devcon)))
{
	std::cout << "[-] Failed to hook Present with VT method." << std::endl;
	return;		
}

If CreateDeviceAndWapChain fails, we will just return and call to our first method to hook Present. If it succeeds, we are going to retrieve Present Address from its Virtual Table:

DWORD_PTR* pSwapChainVtable = NULL;
pSwapChainVtable = (DWORD_PTR*)swapchain;
pSwapChainVtable = (DWORD_PTR*)pSwapChainVtable[0];
fnIDXGISwapChainPresent = (IDXGISwapChainPresent)(DWORD_PTR)pSwapChainVtable[8];
g_PresentHooked = true;
std::cout << "[+] Present Addr:" << fnIDXGISwapChainPresent << std::endl;

Nice job! Now we have our original Present address and we can hook it properly. Notice that we are setting g_PresentHooked as true, which means that we are not going to call our first hooking method:

if (!g_PresentHooked) {
	SecondMethod();
}

 

Conclusion

There are a lot of things that we may not take into account when writing our code and it is important to stick to the small details and think on the different ways to achieve each thing, this may bring us to a situation where our code will execute but others will fail 🙂 And that’s a huge advantage.