HOOKING THREADS WITHOUT DETOURS OR PATCHES

Thread safety is an important thing.  Some data is meant to be left alone and utilized by specific threads, and if you ignore that, you’ll occasionally find yourself reading, writing or executing invalid memory which will, of course, produce wildly unexpected results in your software.  This generally occurs for one of two reasons — because a different thread is actively modifying the data which our thread is trying to access, resulting in invalid pointers, fields, etc. — or because our thread is unable to access the thread-local storage (or “TLS”) of the thread who owns the data.

If you’re like me, you’ll probably have a better understanding if you can see the control flow of such a situation:

if (buffer != nullptr) {
    // Another thread deletes the buffer before the sleep has completed.
    Sleep(1000);

    // Trying to print a buffer at invalid memory!
    printf(buffer);
}

As you can see, this is a dangerous situation and could occur at any given time without both threads speaking with one-another. As with thread-safety in our own software, we need to treat the threads of process containing our injected module, with the same respect.


A lot of programmers seem to detour functions which run in the thread which they want to execute code from, to avoid the issue of thread-safety.  But if you’re using a lightweight detours library, then chances are it will not apply detours in a particularly thread-safe manner, either.  Microsoft’s Detours library and EasyHook are good examples of keeping detours kosher, so to speak, but I still find it’s inefficient to use these libraries unless completely necessary.  And for God’s sake, don’t detour the renderer just to run code in another thread, you maniac.

There’s a much simpler approach: WndProc.  We can override this function as we please using only Windows API, is completely thread-safe to override, and requires no detours or patches whatsoever.  This is the function all windows use to handle input and other messages, and therefore any Windows GUI application can be hooked this way.  It’s perfect for safely accessing data used by the thread in which WndProc is called, and it is especially perfect for hacking games — games almost always have the main window running on the same thread as the game engine.  This is primarily what I’m interested in doing, and you probably are too.

Once we’ve loaded our module inside the target process, by means of CreateRemoteThread and LoadLibrary, manual mapping or whichever injection method you prefer, we can simply call SetWindowLong (x86) or SetWindowLongPtr (x64) to apply our own WndProc callback.  This is a pretty straightforward task:

public class Window {

    [DllImport("user32.dll", SetLastError = true)]
    private static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

    [DllImport("user32.dll")]
    public static extern int CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, int Msg, int wParam, int lParam);

    private delegate int WindowProc(IntPtr hWnd, int Msg, int wParam, int lParam);
    private const int GWL_WNDPROC = -4;

    private IntPtr _handle;
    private IntPtr _oldCallback;
    private WindowProc _newCallback;

    public Window(IntPtr handle) {
        _handle = handle;
    }

    public void Attach() {
        _newCallback = WndProc; // Pins WndProc - will not be garbage collected.
        _oldCallback = SetWindowLong(_handle, GWL_WNDPROC,
            Marshal.GetFunctionPointerForDelegate(_newCallback));

        // Just to be sure...
        if (_oldCallback == IntPtr.Zero)
            throw new Win32Exception(Marshal.GetLastWin32Error());
    }

    public void Detach() {
        if (_newCallback == null || _oldCallback == null)
            return;

        SetWindowLong(_handle, GWL_WNDPROC, _oldCallback);
        _newCallback = null;
    }

    private int WndProc(IntPtr hWnd, int Msg, int wParam, int lParam) {
        /* Do stuff, we're in the window's thread! */

        // Forward the message to the original WndProc function.
        return CallWindowProc(_oldCallback, hWnd, Msg, wParam, lParam);
    }

}

As you can see, we now have space to execute our code in the window’s thread upon receiving the first and subsequent window messages. As an added benefit, we’re also now able to easily handle some pretty useful messages, including mouse and keyboard events — this is handy for things like hotkeys or interacting with our own interfaces, and we can even block the message from reaching the original WndProc function by simply not forwarding it. But we can worry about that later.

You’re probably wondering we would want to use this method if we need to wait for a window event to fire — after all, it’s possible that no events will occur for an unlimited amount of time. This is where we can use the simplicity of Windows GUI to our advantage, we can simply fire our own custom user message using SendMessage:

public enum UserMessage {
    Startup,
    Shutdown
}

public class Window {

    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
    private const int WM_USER = 0x0400; // WM_USER can be defined as anything between 0x0400 and 0x7FFF!

    ...

    private int WndProc(IntPtr hWnd, int Msg, int wParam, int lParam) {
        if (Msg == WM_USER && HandleUserMessage((UserMessage) wParam))
            return 0; // Already handled, woohoo!

        // Forward the message to the original WndProc function.
        return CallWindowProc(_oldCallback, hWnd, Msg, wParam, lParam);
    }

    public void Invoke(UserMessage message) {
        SendMessage(_handle, WM_USER, (int) message, 0);
    }

    private bool HandleUserMessage(UserMessage message) {
        switch (message) {
            case UserMessage.Startup:  Engine.Startup();  return true;
            case UserMessage.Shutdown: Engine.Shutdown(); return true;
        }

        return false;
    }

}

Awesome, now we can invoke the window’s thread at will! On startup:

_mainWindow = new Window(_gameProcess.MainWindowHandle);
_mainWindow.Attach();
_mainWindow.Invoke(UserMessage.Startup);

And don’t forget to shut it down:

_mainWindow.Invoke(UserMessage.Shutdown);
_mainWindow.Detach();

Congratulate yourself, and give yourself a high-five, ’cause you can now run your injected module’s code safely in other threads! Aren’t you glad you stuck around? 

posted @ 2020-03-19 10:50  糖果的二师兄  阅读(202)  评论(0编辑  收藏  举报