Tutorial How to Hook DirectInput & Emulate Key Presses

Hexui Undetected CSGO Cheats Sinkicheat PUBG Cheat

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
The "correct" way to manipulate game input



Game: Tomb Raider Legend
Engine: Updated Legacy of Kain Engine
Studio: Crystal Dynamics
Version: Steam
Buy: Steam (86% off)

Rant:

Something that inevitably comes up after awhile is hooking game input. After you're done pasting what ever ImGui code you can find on the net and fumbling your way through that you're probably after a way to manipulate the game's input and like a typical noob you're probably using SendInput.

Here's the thing:
  1. DON'T USE SEND INPUT!
  2. DON'T USE SEND INPUT!
  3. DON'T USE SEND INPUT!
  4. DON'T USE SEND INPUT!
  5. DON'T USE SEND INPUT!
Why? First of all it's a vector for detection because it synthesizes input; secondly: it doesn't change the keyboard state it simply adds to it. Also it's a garbage API that gets pasted all the time and the code is just sad look at this:

C++:
// This is an array (why?)
INPUT input[1];
// What part of the array are you accessing here? The segfault part?
input->type = INPUT_KEYBOARD;
input->ki.dwExtraInfo = 0;
input->ki.time = 0;                                   
input->ki.wScan = 0;
input->ki.wVk = 0x20; // this is virtual key code for SPACE key

// Infinite loop nice, big fan
while (1)
{
   input->ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
   SendInput(1, input, sizeof(input));
   // Sleep lul
   Sleep(5);
   input->ki.dwFlags = KEYEVENTF_KEYUP;
   SendInput(1, input, sizeof(input));
   // And again gotta preserve that CPU time
   Sleep(50);
}
Here's the thing about programming if you're using an infinite loop, Sleep and calling the same function multiple times with different parameters you code is probably inefficient. That is to say it's WRONG. You should really look and this and think to yourself: "Yeah I can do better than that". Use a loop and some arrays but anyway:

I'll say it again:
  1. DON'T PASTE CODE!
  2. DON'T PASTE CODE!
  3. DON'T PASTE CODE!
  4. DON'T PASTE CODE!
  5. DON'T PASTE CODE!
There's nothing wrong with pasting a little bit when you're still learning but if your code is filled with dangling pointers, incorrectly allocated variables and a bunch of other really simple errors that five minutes on a LearnCPP website can teach you, then you need to stop and spend a bit of time learning. I honestly don't understand how people can spend months copy and pasting code and still learn nothing. Do you even read the code? Try to understand it or know the difference between why your code didn't work and someone else's did? Or you just trying to get that p2c out the door as quickly as possible? What happens when it breaks how are you going to fix that? You can't debug or even understand why it does or doesn't work. Or are you simply going to post on SO and pretend like it's a school project or something?

Introduction:

Anyway let's down to business with Tomb Raider and DInput (game chosen specifically for DInput jokes). I've really got to give the development team credit on this one the way Lara animates in this game is really good and looks and plays damn well for a remake. Also women with an English accent amirite fellas?

capture_720.png

Urm Nice ....lighting and fog work

capture1.png

Check out that awesome....spike pit so much creativity!

What is Direct Input?

Direct Input was Microsoft's solution for making handling input for games using DirectX easy and with high performance. I say "was" because even Microsoft has disowned DInput and encourages everyone to use alternatives.

From Wikipedia:
As of 2011 Microsoft doesn't recommend using DirectInput for keyboards or mice, and has started pushing the newer XInput for Xbox 360 controllers. In Windows Vista, Windows 7 and later Windows versions, the in-built action mapping UI has been removed. DirectInput is not available for Windows Store apps.
Direct Input 8 was released sometime in the 2000s I believe and hasn't been updated since then. However while Microsoft strongly recommends not using it that hasn't done anything to sway developers because damn near every game I reverse seems to still use Direct Input. I mean why fix what isn't broken I guess, developers know Microsoft can't remove it because it'll break damn near every game ever made.


Lead developer when asked if they should remove legacy DInput code.

Know your enemy:

I find one of the best ways to attack something from a reverse engineering perspective is to attempt to code your own version of said thing. So let's write our own Direct Input application!

You're going to need to link dinput8.lib and dxguid.lib so add those to your additional dependencies under linker in project properties.

Then we need to include the DInput.h header file while defining what version we want to use. You should check the game to be certain but it's been many a year since I've seen DirectInput7 in the wild. So it's a pretty safe bet if your game was made AFTER 2000 its most likely using DirectInput8.

C++:
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
Now you DirectX programmers will probably feel quite comfortable with DirectInput's interface because it's pretty much identical:

Create an interface to the base class:
C++:
IDirectInput8 *pDirectInput = NULL;
if (DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&pDirectInput, NULL) != DI_OK)
{
    printf("[-] Failed to acquire DirectInput handle\n");
    return -1;
}
Now we can create our device:
C++:
LPDIRECTINPUTDEVICE8  lpdiKeyboard;
if (pDirectInput->CreateDevice(GUID_SysKeyboard, &lpdiKeyboard, NULL) != DI_OK)
{
    printf("[-] Unable to attach to kbd\n");
    pDirectInput->Release();
    return -1;
}
DirectInput can also handle mouse input (you need to remember this and check for it when processing data).

Now in order to use our new device we need to configure some options:

C++:
// Set the data type to keyboard
lpdiKeyboard->SetDataFormat(&c_dfDIKeyboard);
// We want non-exclusive access i.e sharing it with other applications
lpdiKeyboard->SetCooperativeLevel(GetActiveWindow(), DISCL_NONEXCLUSIVE);

// Attempt to acquire the device
if (lpdiKeyboard->Acquire() == DI_OK)
{
    printf("[+] Acquired keyboard.\nPress escape to exit...\n");

    // Start looping and grabbing the data from the device
    while (1)
    {
        // Poll for new data
        lpdiKeyboard->Poll();
        // This is how the data is returned as an array 256 bytes for each key
        BYTE  diKeys[256] = { 0 };
        // Get the state
        if (lpdiKeyboard->GetDeviceState(256, diKeys) == DI_OK)
        {
            // Check if the escape was pressed
            if (diKeys[DIK_ESCAPE] & 0x80) {
                break;
            }
        }
        // We don't need realtime access, don't flood the CPU
        Sleep(100);
    }
    // Unacquire the keyboard
    lpdiKeyboard->Unacquire();
}
// Free the keyboard and device objects
lpdiKeyboard->Release();
pDirectInput->Release();
Pretty simple right? Well hold up that's ONE way to retrieve key data from DInput there's also another way:

C++:
// Same as before
lpdiKeyboard->SetDataFormat(&c_dfDIKeyboard);
lpdiKeyboard->SetCooperativeLevel(GetActiveWindow(), DISCL_NONEXCLUSIVE);

// Setup config for buffered output
DIPROPDWORD  dipdw = { 0 };
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
dipdw.diph.dwObj = 0;
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.dwData = 10;

// Attempt to switch to buffered output
if (lpdiKeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph) == DI_OK)
    printf("[+] Buffered mode enabled for device\n");

// Trip flag
bool bEscape = false;

// Try to acquire the keyboard
if (lpdiKeyboard->Acquire() == DI_OK)
{
    printf("[+] Acquired keyboard.\nPress escape to exit...\n");

    while (1)
    {
        // Poll for new data
        lpdiKeyboard->Poll();

        // We asked for 10 objects at a time
        DIDEVICEOBJECTDATA didod[10] = { 0 };
        DWORD dwNumElements = 10;

        // Get the data
        HRESULT hr = lpdiKeyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), didod, &dwNumElements, 0);
        if (hr == DI_OK)
        {
            // Process the keystrokes
            for (DWORD i = 0; i < dwNumElements; i++)
            {
                // If the escape key is down exit
                if (LOBYTE(didod[i].dwData) > 0 && didod[i].dwOfs == DIK_ESCAPE)
                {
                    bEscape = true;
                    break;
                }
            }
        }
        // Trip flag hit
        if (bEscape)
            break;
        Sleep(100);
    }

    // Give the keyboard back
    lpdiKeyboard->Unacquire();
}
// Free objects
lpdiKeyboard->Release();
pDirectInput->Release();
Okay so now that you understand the basics of how a DirectInput application acquires input we can get to the task of hooking it.

The intricacies of hooking:

cosplay.jpg

"This picture has no purpose: it's just to make sure you're still awake".

If you've been doing this for awhile you're probably thinking a VTable hook and you'd be correct it's the easiest and best solution for this problem. There's just one or two things I feel I should mention:
  1. DirectInput has unicode support.
  2. The game can use either method (above) to retrieve data from the device.
If the game was released internally it's a pretty safe bet that it's running in Unicode however you can check with x64dbg (remember to install the symbols for dinput8.dll):

Put a breakpoint on DirectInput8Create:

dinputcreate.PNG


Now inspect the stack:

stack.jpg

"Not that one the other other stack".

This one:

dinputstack.PNG

"You can see that the game is using the Unicode version of DirectInput".

If you're on x64 the process is slightly different (I'm using Hitman 2 as an example here):

The IID_IDirectInput8 variable is stored in the R8 register:

x64-reg.PNG


In this case 14164D8D0. Goto the memory view press CTRL+G to goto that address so we can see what it's pointing at:

mem-view.PNG


In my case the value it points to is:

000000014167D8D0 30 80 79 BF 3A 48 A2 4D AA 99 5D 64 ED 36 97 00
Which we can compare to the DInput header file and see that:

DEFINE_GUID(IID_IDirectInput8A, 0xBF798030,0x483A,0x4DA2,0xAA,0x99,0x5D,0x64,0xED,0x36,0x97,0x00);
DEFINE_GUID(IID_IDirectInput8W, 0xBF798031,0x483A,0x4DA2,0xAA,0x99,0x5D,0x64,0xED,0x36,0x97,0x00);
It's pointing to IID_IDirectInput8A so you need to build your project in ascii mode.
 
Last edited:

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
The Dummy Device Method:

If you've hooked DirectX before you're probably familiar with this method. All objects created share the same VTable so rather than pattern scanning or something like that our code simply creates an object of the desired type and installs a hook in the VTable, which will hook all objects of the same type regardless of when they are created.

So lets do that now:
in-a-minute.jpg

"I mean in 7 minutes".

You can pretty much just grab the code from before right up until we have the IDirectInputDevice8 object.

C++:
HINSTANCE hInst = (HINSTANCE)GetModuleHandle(NULL);
IDirectInput8 *pDirectInput = NULL;

if (DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&pDirectInput, NULL) != DI_OK)
    return -1;

LPDIRECTINPUTDEVICE8  lpdiKeyboard;
if (pDirectInput->CreateDevice(GUID_SysKeyboard, &lpdiKeyboard, NULL) != DI_OK)
{
    pDirectInput->Release();
    return -1;
}
Now using some VTable hooking code I have laying around we can patch this sucker:

C++:
// By Timb3r
// Source: https://gamephreakers.com/2018/08/introduction-to-vtable-hooking/
void* HookVTableFunction(void* pVTable, void* fnHookFunc, int nOffset)
{
    intptr_t ptrVtable = *((intptr_t*)pVTable); // Pointer to our chosen vtable
    intptr_t ptrFunction = ptrVtable + sizeof(intptr_t) * nOffset; // The offset to the function (remember it's a zero indexed array with a size of four bytes)
    intptr_t ptrOriginal = *((intptr_t*)ptrFunction); // Save original address

    // Edit the memory protection so we can modify it
    MEMORY_BASIC_INFORMATION mbi;
    VirtualQuery((LPCVOID)ptrFunction, &mbi, sizeof(mbi));
    VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);

    // Overwrite the old function with our new one
    *((intptr_t*)ptrFunction) = (intptr_t)fnHookFunc;

    // Restore the protection
    VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &mbi.Protect);

    // Return the original function address incase we want to call it
    return (void*)ptrOriginal;
}
We can apply our hook like so:

C++:
fnGetDeviceData  = (IDirectInputDevice_GetDeviceData_t)HookVTableFunction(lpdiKeyboard, HookGetDeviceData, 10);
And the typedefs and function prototype:

C++:
typedef HRESULT(WINAPI* IDirectInputDevice_GetDeviceData_t)(IDirectInputDevice8*, DWORD, LPDIDEVICEOBJECTDATA, LPDWORD, DWORD);
IDirectInputDevice_GetDeviceData_t fnGetDeviceData = NULL;

HRESULT WINAPI HookGetDeviceData(IDirectInputDevice8* pThis, DWORD a, LPDIDEVICEOBJECTDATA b, LPDWORD c, DWORD d)
{
    HRESULT ret = fnGetDeviceData(pThis, a, b, c, d);
    if (ret == DI_OK)
    {
        for (DWORD i = 0; i < *c; i++)
        {
            if (LOBYTE(b[i].dwData) > 0)
            {
                switch (b[i].dwOfs)
                {
                case DIK_W:
                    printf("[W]");
                    break;
                case DIK_S:
                    printf("[S]");
                    break;
                case DIK_A:
                    printf("[A]");
                    break;
                case DIK_D:
                    printf("[D]");
                    break;
                }
            }
        }
    }
    return ret;
}
This code will log any key presses on WASD which are pretty much the standard movement controls for most games. Here's a full key list here.

Now we've got logging sorted lets do some key injection.

To start with how about a simple key reversal?

If we update our hook to this:
C++:
HRESULT WINAPI HookGetDeviceData(IDirectInputDevice8* pThis, DWORD a, LPDIDEVICEOBJECTDATA b, LPDWORD c, DWORD d)
{
    HRESULT ret = fnGetDeviceData(pThis, a, b, c, d);
    if (ret == DI_OK)
    {
        for (DWORD i = 0; i < *c; i++)
        {
            switch (b[i].dwOfs)
            {
            case DIK_W:
                b[i].dwOfs = DIK_S;
                break;
            case DIK_S:
                b[i].dwOfs = DIK_W;
                break;
            case DIK_A:
                b[i].dwOfs = DIK_D;
                break;
            case DIK_D:
                b[i].dwOfs = DIK_A;
                break;
            }
        }
    }
    return ret;
}
Now our controls will be reversed. Auto run?

C++:
HRESULT WINAPI HookGetDeviceData(IDirectInputDevice8* pThis, DWORD a, LPDIDEVICEOBJECTDATA b, LPDWORD c, DWORD d)
{
    HRESULT ret = fnGetDeviceData(pThis, a, b, c, d);
    if (ret == DI_OK)
    {
        for (DWORD i = 0; i < *c; i++)
        {
            if (LOBYTE(b[i].dwData) == 0)
            {
                switch (b[i].dwOfs)
                {
                    case DIK_W:
                        b[i].dwData = LOBYTE(0x80);
                    break;
                }
            }
        }
    }
    return ret;
}
And finally here's the big one everyone wants to know how to inject arbitrary key presses:

C++:
HRESULT WINAPI HookGetDeviceData(IDirectInputDevice8* pThis, DWORD cbObjectData, LPDIDEVICEOBJECTDATA rgdod, LPDWORD pdwInOut, DWORD dwFlags)
{
    static ULONGLONG timeStart = 0;
    static bool bShouldInj = false;
    static int totalKeys = 0;
    static int currentKeys = 0;
    struct KEYINJ
    {
        DWORD key;
        DWORD duration;
        bool pressed;
    };
    static KEYINJ keys[256];

    // Should we inject?
    if(bShouldInj)
    {
        // Grab the current time
        ULONGLONG timeEnd = GetTickCount64();

        // Has enough time elapsed to send the next key?
        if (timeEnd - timeStart > keys[currentKeys].duration)
        {
            // Setup the struct with our data
            rgdod[0].dwData = (keys[currentKeys].pressed ? 0x80 : 0x0);
            rgdod[0].dwOfs = keys[currentKeys].key;

            *pdwInOut = 1;

            // Have we finished sending all the keys?
            if (currentKeys++ >= totalKeys) {
                printf("[+] Injection done.\n");
                bShouldInj = false;
            }

            // Reset the timer for the next key
            timeStart = GetTickCount64();
        }

        // This will block incoming keypresses while the injection is running
        return DI_OK;
    }

    // Call original function to get actual keypresses
    HRESULT ret = fnGetDeviceData(pThis, cbObjectData, rgdod, pdwInOut, dwFlags);
    if (ret == DI_OK)
    {
        // Check for CMD key
        for (DWORD i = 0; i < *pdwInOut; i++)
        {
            switch (rgdod[i].dwOfs)
            {
                // LCTRL to start key injection
                case DIK_LCONTROL:
                {
                    if (rgdod[i].dwData == 0 && !bShouldInj)
                    {
                        // Pressed key
                        bShouldInj = true;
                        DWORD sequence = rgdod[i].dwSequence;
                    
                        // Run forwards
                        keys[0] = { DIK_W, 0, true };
                        keys[1] = { DIK_W, 1000, false };
                    
                        // Run backwards
                        keys[2] = { DIK_S, 0, true };
                        keys[3] = { DIK_S, 1000, false };
                    
                        // How many keys we're sending
                        totalKeys   = 3;

                        // Reset current index
                        currentKeys = 0;

                        // Start timer
                        timeStart = GetTickCount64();
                    }
                }
                break;
            }
        }
    }
    return ret;
}
Here's a short vid demonstrating what it looks like:

I'm just tapping control once and the key injection code takes care of the rest!
 
Last edited:

Rake

Cesspool Admin
Administrator
Jan 21, 2014
11,573
78,998
2,316
This might be your best work yet!
 

k9crow

Full Member
Nov 18, 2019
5
22
0
Hi, i tried to follow your tutorial, I'm still noob in C++, The keylogging part works like charm, I'm trying to make it work in a game that is made in Lumberyard by Amazon, the game is called Star Citizen. I can make things work using SendInput, but like you've said, do not use SendInput so i want to try something else, I can't send any keypress to the game, here is my code:
Please guide me to the right direction:
Any reply is much appreciated:

C++:
#include <Windows.h>
#include <iostream>
#include "stdio.h"

#define DIRECTINPUT_VERSION 0x0800
#include "dinput.h"

#pragma comment(lib, "dinput8")
#pragma comment(lib, "dxguid")

using namespace std;

DWORD WINAPI Main();

HINSTANCE hInst = (HINSTANCE)GetModuleHandle(NULL);
IDirectInput8* pDirectInput = NULL;

// By Timb3r
// Source: https://gamephreakers.com/2018/08/introduction-to-vtable-hooking/
void* HookVTableFunction(void* pVTable, void* fnHookFunc, int nOffset)
{
    intptr_t ptrVtable = *((intptr_t*)pVTable); // Pointer to our chosen vtable
    intptr_t ptrFunction = ptrVtable + sizeof(intptr_t) * nOffset; // The offset to the function (remember it's a zero indexed array with a size of four bytes)
    intptr_t ptrOriginal = *((intptr_t*)ptrFunction); // Save original address

    // Edit the memory protection so we can modify it
    MEMORY_BASIC_INFORMATION mbi;
    VirtualQuery((LPCVOID)ptrFunction, &mbi, sizeof(mbi));
    VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);

    // Overwrite the old function with our new one
    *((intptr_t*)ptrFunction) = (intptr_t)fnHookFunc;

    // Restore the protection
    VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &mbi.Protect);

    // Return the original function address incase we want to call it
    return (void*)ptrOriginal;
}

typedef HRESULT(WINAPI* IDirectInputDevice_GetDeviceData_t)(IDirectInputDevice8*, DWORD, LPDIDEVICEOBJECTDATA, LPDWORD, DWORD);
IDirectInputDevice_GetDeviceData_t fnGetDeviceData = NULL;

HRESULT WINAPI HookGetDeviceData(IDirectInputDevice8* pThis, DWORD cbObjectData, LPDIDEVICEOBJECTDATA rgdod, LPDWORD pdwInOut, DWORD dwFlags)
{
    static ULONGLONG timeStart = 0;
    static bool bShouldInj = false;
    static int totalKeys = 0;
    static int currentKeys = 0;
    struct KEYINJ
    {
        DWORD key;
        DWORD duration;
        bool pressed;
    };
    static KEYINJ keys[256];

    // Should we inject?
    if (bShouldInj)
    {
        // Grab the current time
        ULONGLONG timeEnd = GetTickCount64();

        // Has enough time elapsed to send the next key?
        if (timeEnd - timeStart > keys[currentKeys].duration)
        {
            // Setup the struct with our data
            rgdod[0].dwData = (keys[currentKeys].pressed ? 0x80 : 0x0);
            rgdod[0].dwOfs = keys[currentKeys].key;

            *pdwInOut = 1;

            // Have we finished sending all the keys?
            if (currentKeys++ >= totalKeys) {
                printf("[+] Injection done.\n");
                bShouldInj = false;
            }

            // Reset the timer for the next key
            timeStart = GetTickCount64();
        }

        // This will block incoming keypresses while the injection is running
        return DI_OK;
    }

    // Call original function to get actual keypresses
    HRESULT ret = fnGetDeviceData(pThis, cbObjectData, rgdod, pdwInOut, dwFlags);
    if (ret == DI_OK)
    {
        // Check for CMD key
        for (DWORD i = 0; i < *pdwInOut; i++)
        {
            switch (rgdod[i].dwOfs)
            {
                // LCTRL to start key injection
            case DIK_LCONTROL:
            {
                if (rgdod[i].dwData == 0 && !bShouldInj)
                {
                    // Pressed key
                    bShouldInj = true;
                    DWORD sequence = rgdod[i].dwSequence;

                    printf("[+] Pressed Detected.\n");
                   
                    // Run forwards
                    keys[0] = { DIK_W, 0, true };
                    keys[1] = { DIK_W, 1000, false };

                    // Run backwards
                    keys[2] = { DIK_S, 0, true };
                    keys[3] = { DIK_S, 1000, false };

                    // How many keys we're sending
                    totalKeys = 3;

                    // Reset current index
                    currentKeys = 0;

                    // Start timer
                    timeStart = GetTickCount64();
                }
            }
            break;
            }
        }
    }
    return ret;
}

DWORD WINAPI Main()
{

    AllocConsole();
    FILE* f;
    freopen_s(&f, "CONOUT$", "w", stdout);

    IDirectInput8* pDirectInput = NULL;
    if (DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&pDirectInput, NULL) != DI_OK)
    {
        printf("[-] Failed to acquire DirectInput handle\n");
        return -1;
    }

    LPDIRECTINPUTDEVICE8  lpdiKeyboard;
    if (pDirectInput->CreateDevice(GUID_SysKeyboard, &lpdiKeyboard, NULL) != DI_OK)
    {
        pDirectInput->Release();
        return -1;
    }

    fnGetDeviceData = (IDirectInputDevice_GetDeviceData_t)HookVTableFunction(lpdiKeyboard, HookGetDeviceData, 10);

    while (true)
    {
        lpdiKeyboard->SetDataFormat(&c_dfDIKeyboard);
        lpdiKeyboard->SetCooperativeLevel(GetActiveWindow(), DISCL_NONEXCLUSIVE);

        // Setup config for buffered output
        DIPROPDWORD  dipdw = { 0 };
        dipdw.diph.dwSize = sizeof(DIPROPDWORD);
        dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
        dipdw.diph.dwObj = 0;
        dipdw.diph.dwHow = DIPH_DEVICE;
        dipdw.dwData = 10;

        // Attempt to switch to buffered output
        if (lpdiKeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph) == DI_OK)
            printf("[+] Buffered mode enabled for device\n");

        // Trip flag
        bool bEscape = false;

        // Try to acquire the keyboard
        if (lpdiKeyboard->Acquire() == DI_OK)
        {
            printf("[+] Acquired keyboard.\nPress escape to exit...\n");

            while (1)
            {
                // Poll for new data
                lpdiKeyboard->Poll();

                // We asked for 10 objects at a time
                DIDEVICEOBJECTDATA didod[10] = { 0 };
                DWORD dwNumElements = 10;

                // Get the data
                HRESULT hr = lpdiKeyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), didod, &dwNumElements, 0);
                if (hr == DI_OK)
                {
                    // Process the keystrokes
                    for (DWORD i = 0; i < dwNumElements; i++)
                    {
                        // If the escape key is down exit
                        if (LOBYTE(didod[i].dwData) > 0 && didod[i].dwOfs == DIK_ESCAPE)
                        {
                            bEscape = true;
                            break;
                        }
                    }
                }
                // Trip flag hit
                if (bEscape)
                    break;
                Sleep(100);
            }

            // Give the keyboard back
            lpdiKeyboard->Unacquire();
        }
        // Free objects
        lpdiKeyboard->Release();
        pDirectInput->Release();

        fclose(f);
        FreeConsole();

    }
    return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
       
        CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&Main, 0, 0, 0);
       
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
Thanks!

Best regards,

k9crow
 
Last edited:

k9crow

Full Member
Nov 18, 2019
5
22
0
@k9crow if the game doesn't use DirectInput it won't work
Thanks, i tested my code in Skyrim Special Edition and it works like charm, and i think Skyrim is a x86 game. Star Citizen is a x64 game, i checked in x64dbg and it loads dinput8.dll, or is the code or approach different in x64 games?
 

Rake

Cesspool Admin
Administrator
Jan 21, 2014
11,573
78,998
2,316
Thanks, i tested my code in Skyrim Special Edition and it works like charm, and i think Skyrim is a x86 game. Star Citizen is a x64 game, i checked in x64dbg and it loads dinput8.dll, or is the code or approach different in x64 games?
Just because it imports dinput8.dll doesn't mean it uses it for key input. this code uses a vtable hook so you don't have to do anything special to make it work for x64 except the pointer you write needs to be the correct size
 

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
If the game does use dinput you can easily check by blocking input to the game if it no longer detects input after the hook is installed it's using dinput.

If it's still detecting keypresses then it's using another method. As I mentioned in the tutorial there's actually two ways to detect input using dinput make sure you hook both.

Other than that you need to make sure you're using the same encoding scheme the game uses either multibyte or unicode. You can check by using a breakpoint on directinputcreate.
 
  • Like
Reactions: Rake and Kleon742

k9crow

Full Member
Nov 18, 2019
5
22
0
If the game does use dinput you can easily check by blocking input to the game if it no longer detects input after the hook is installed it's using dinput.

If it's still detecting keypresses then it's using another method. As I mentioned in the tutorial there's actually two ways to detect input using dinput make sure you hook both.

Other than that you need to make sure you're using the same encoding scheme the game uses either multibyte or unicode. You can check by using a breakpoint on directinputcreate.
Thanks guys @Rake @timb3r
 

kino0924

Full Member
Dec 31, 2019
11
114
0
Thank you for detailed tutorial.
So, I did some reversing towards the target that im working on.
Its x86 process on x64 environment (wow64)
I bped DirectInput8Create and 3rd argument was just pure pointer just like hitman example.
I followed the address and it showed "30 80 79 BF 3A 48 A2 4D AA 99 5D 64 ED 36 97 00" which indicates IID_IDirectInput8A

I created dll project as Multi-Byte Character Set and injected example code but nothing happens.
I also tried to block all input with following hook
C++:
HRESULT WINAPI HookGetDeviceData(IDirectInputDevice8* pThis, DWORD a, LPDIDEVICEOBJECTDATA b, LPDWORD c, DWORD d)
{
    return DI_OK;
}
Nothing happens and all the input goes into the target process.

https://drive.google.com/file/d/0B7JKx6OOZrslSl9BWFBqUE1sY00

I also tried sample from above which has directinput8 sample keyboard app but same result; nothing happens.

Now, I am thinking that hook offset (10) is incorrect in my system.

C++:
fnGetDeviceData  = (IDirectInputDevice_GetDeviceData_t)HookVTableFunction(lpdiKeyboard, HookGetDeviceData, 10);
I thoroughly read the tutorial but it is missing how it came with that offset "10"

Also, I searched google thoroughly but couldnt find a way to get offset of GetDeviceData from LPDIRECTINPUTDEVICE8


Can someone please help me?

Thanks
 

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
Either your VTable address is incorrect OR the game doesn't use DirectInput for retrieving input.
 

kino0924

Full Member
Dec 31, 2019
11
114
0
Either your VTable address is incorrect OR the game doesn't use DirectInput for retrieving input.
Thank you for the reply!
However, above DirectInput example clearly uses DirectInput8Create and GetDeviceData.
But it doesnt work just like my target.
Now, my primary goal is to make it work on this example app.

Also, I created LPDIRECTINPUTDEVICE8 just as you mentioned on the example.
C++:
HINSTANCE hInst = (HINSTANCE)GetModuleHandle(NULL);
IDirectInput8 *pDirectInput = NULL;

typedef HRESULT(WINAPI* IDirectInputDevice_GetDeviceData_t)(IDirectInputDevice8*, DWORD, LPDIDEVICEOBJECTDATA, LPDWORD, DWORD);
IDirectInputDevice_GetDeviceData_t fnGetDeviceData = NULL;

if (DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&pDirectInput, NULL) != DI_OK)
    return -1;

LPDIRECTINPUTDEVICE8  lpdiKeyboard;
if (pDirectInput->CreateDevice(GUID_SysKeyboard, &lpdiKeyboard, NULL) != DI_OK)
{
    pDirectInput->Release();
    return -1;
}

fnGetDeviceData  = (IDirectInputDevice_GetDeviceData_t)HookVTableFunction(lpdiKeyboard, HookGetDeviceData, 10);

Or, Do I have to hook CreateDevice to get what target has been created and hook it?
 

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
The whole point of hooking a VTable is it applies the hook to all instances of a class object. Post your "example" app that you're trying to hook I suspect the issue lies there.
 

kino0924

Full Member
Dec 31, 2019
11
114
0
The whole point of hooking a VTable is it applies the hook to all instances of a class object. Post your "example" app that you're trying to hook I suspect the issue lies there.
https://drive.google.com/file/d/0B7JKx6OOZrslSl9BWFBqUE1sY00

Im currently testing with DirectInput-DirectX8-Samples\DirectInput\Keyboard\Debug\keyboard.exe
Source of keyboard.exe is also included in there.


C++:
LPDIRECTINPUT8       g_pDI       = NULL;         
LPDIRECTINPUTDEVICE8 g_pKeyboard = NULL;

    // Create a DInput object
    if( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION,
                                         IID_IDirectInput8, (VOID**)&g_pDI, NULL ) ) )
        return hr;
    
    // Obtain an interface to the system keyboard device.
    if( FAILED( hr = g_pDI->CreateDevice( GUID_SysKeyboard, &g_pKeyboard, NULL ) ) )
        return hr;


    hr = g_pKeyboard->GetDeviceData( sizeof(DIDEVICEOBJECTDATA),
                                     didod, &dwElements, 0 );
This is part of the source of keyboard.exe where it DirectInput8Create, CreateDevice and GetDeviceData
 

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
I have no idea what you're doing but I just downloaded those samples, added the hooking code and pasted my own unmodified code and it worked with zero issues. Same VTable addresses and everything.
 

Attachments

kino0924

Full Member
Dec 31, 2019
11
114
0
I have no idea what you're doing but I just downloaded those samples, added the hooking code and pasted my own unmodified code and it worked with zero issues. Same VTable addresses and everything.
C++:
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <InitGuid.h>
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>

#pragma comment(lib, "dinput8.lib")

typedef HRESULT(WINAPI* IDirectInputDevice_GetDeviceData_t)(IDirectInputDevice8*, DWORD, LPDIDEVICEOBJECTDATA, LPDWORD, DWORD);

IDirectInputDevice_GetDeviceData_t fnGetDeviceData = NULL;

void* HookVTableFunction(void* pVTable, void* fnHookFunc, int nOffset)
{
    intptr_t ptrVtable = *((intptr_t*)pVTable); // Pointer to our chosen vtable
    intptr_t ptrFunction = ptrVtable + sizeof(intptr_t) * nOffset; // The offset to the function (remember it's a zero indexed array with a size of four bytes)
    intptr_t ptrOriginal = *((intptr_t*)ptrFunction); // Save original address

    // Edit the memory protection so we can modify it
    MEMORY_BASIC_INFORMATION mbi;
    VirtualQuery((LPCVOID)ptrFunction, &mbi, sizeof(mbi));
    VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);

    // Overwrite the old function with our new one
    *((intptr_t*)ptrFunction) = (intptr_t)fnHookFunc;

    // Restore the protection
    VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &mbi.Protect);

    // Return the original function address incase we want to call it
    return (void*)ptrOriginal;
}

HRESULT WINAPI HookGetDeviceData(IDirectInputDevice8* pThis, DWORD cbObjectData, LPDIDEVICEOBJECTDATA rgdod, LPDWORD pdwInOut, DWORD dwFlags)
{
    return DI_OK;
}

bool Hook_DirectInput(bool enable)
{
    HINSTANCE hInst = (HINSTANCE)GetModuleHandle(NULL);
    IDirectInput8* pDirectInput = NULL;

    if (DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&pDirectInput, NULL) != DI_OK)
        return -1;

    LPDIRECTINPUTDEVICE8  lpdiKeyboard;
    if (pDirectInput->CreateDevice(GUID_SysKeyboard, &lpdiKeyboard, NULL) != DI_OK)
    {
        pDirectInput->Release();
        return -1;
    }

    fnGetDeviceData = (IDirectInputDevice_GetDeviceData_t)HookVTableFunction(lpdiKeyboard, HookGetDeviceData, 10);
    return true;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        MessageBox(0, "dinput8 hook!", "Hello", MB_ICONINFORMATION);
        Hook_DirectInput(true);
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
Pretty much same as my code.
Only difference is that, I made it as DLL and injected after the device got created
 

timb3r

Semi-Retired
Dank Tier VIP
Jul 15, 2018
767
22,668
47
C++:
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include <InitGuid.h>
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>

#pragma comment(lib, "dinput8.lib")

typedef HRESULT(WINAPI* IDirectInputDevice_GetDeviceData_t)(IDirectInputDevice8*, DWORD, LPDIDEVICEOBJECTDATA, LPDWORD, DWORD);

IDirectInputDevice_GetDeviceData_t fnGetDeviceData = NULL;

void* HookVTableFunction(void* pVTable, void* fnHookFunc, int nOffset)
{
    intptr_t ptrVtable = *((intptr_t*)pVTable); // Pointer to our chosen vtable
    intptr_t ptrFunction = ptrVtable + sizeof(intptr_t) * nOffset; // The offset to the function (remember it's a zero indexed array with a size of four bytes)
    intptr_t ptrOriginal = *((intptr_t*)ptrFunction); // Save original address

    // Edit the memory protection so we can modify it
    MEMORY_BASIC_INFORMATION mbi;
    VirtualQuery((LPCVOID)ptrFunction, &mbi, sizeof(mbi));
    VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);

    // Overwrite the old function with our new one
    *((intptr_t*)ptrFunction) = (intptr_t)fnHookFunc;

    // Restore the protection
    VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &mbi.Protect);

    // Return the original function address incase we want to call it
    return (void*)ptrOriginal;
}

HRESULT WINAPI HookGetDeviceData(IDirectInputDevice8* pThis, DWORD cbObjectData, LPDIDEVICEOBJECTDATA rgdod, LPDWORD pdwInOut, DWORD dwFlags)
{
    return DI_OK;
}

bool Hook_DirectInput(bool enable)
{
    HINSTANCE hInst = (HINSTANCE)GetModuleHandle(NULL);
    IDirectInput8* pDirectInput = NULL;

    if (DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID*)&pDirectInput, NULL) != DI_OK)
        return -1;

    LPDIRECTINPUTDEVICE8  lpdiKeyboard;
    if (pDirectInput->CreateDevice(GUID_SysKeyboard, &lpdiKeyboard, NULL) != DI_OK)
    {
        pDirectInput->Release();
        return -1;
    }

    fnGetDeviceData = (IDirectInputDevice_GetDeviceData_t)HookVTableFunction(lpdiKeyboard, HookGetDeviceData, 10);
    return true;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        MessageBox(0, "dinput8 hook!", "Hello", MB_ICONINFORMATION);
        Hook_DirectInput(true);
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
Pretty much same as my code.
Only difference is that, I made it as DLL and injected after the device got created
That shouldn't matter with this type of hook as all instances share the same VTable. I just edited the code to apply the hook after the device is created and it still works as expected. You're not doing something silly like injecting an x86 dll into into a x64 process are you?
 

kino0924

Full Member
Dec 31, 2019
11
114
0
That shouldn't matter with this type of hook as all instances share the same VTable. I just edited the code to apply the hook after the device is created and it still works as expected. You're not doing something silly like injecting an x86 dll into into a x64 process are you?
I wish it was that something silly simple problem haha.
I compiled with VS2019, used precompiled example from the example, Windows 7 x64, injected x86 release dll with xenos.

Start keyboard.exe
Create device with Exclusive and Foreground.
Press keys to see if app captures keyboard.
Inject DLL.
When my dll got injected, I see messagebox popup.
Also, I added printf just like you did on the example.
C++:
[+] fnGetDeviceData Hook installed, old func at 734A6BA7
Press keys and still the app captures keyboard.

Im lost~
 
Attention! Before you post:

Read the How to Ask Questions Guide
99% of questions are answered in the Beginner's Guide, do it before asking a question.

No Hack Requests. Post in the correct section.  Search the forum first. Read the rules.

How to make a good post:

  • Fill out the form correctly
  • Tell us the game name & coding language
  • Post everything we need to know to help you
  • Ask specific questions, be descriptive
  • Post errors, line numbers & screenshots
  • Post code snippets using code tags
  • If it's a large project, zip it up and attach it

If you do not comply, your post may be deleted.  We want to help, please make a good post and we will do our best to help you.

Community Mods League of Legends Accounts