[C#]Hooking没有Detours或补丁的线程
线程安全性很重要。某些数据本来应该留给特定线程使用,并且如果忽略了这些信息,您有时会发现自己正在读取,写入或执行无效的内存,这当然会出现意外的结果。发生这种情况通常是由于以下两个原因造成的,一-由于其他线程正在主动修改我们的线程尝试访问的数据,从而导致无效的指针,字段等,或者因为我们的线程无法访问线程本地存储(或拥有数据的线程的“ TLS”)。
如果您像我一样,如果可以看到这种情况的控制流程,则可能会更好地理解:
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); }
正如您所看到的,这是一种危险的情况,并且在任何给定的时间都可能发生,因为两个线程之间没有会话。与我们自己的软件中的线程安全性一样,我们需要以同样的方式对待包含注入模块的进程线程。
许多程序员为了避免线程安全问题,似乎会绕过在他们想要执行代码的线程中运行的函数。但是,如果您使用的是轻量级的detours库,那么它也可能不会以特别线程安全的方式应用detours。可以说,微软的Detours库和EasyHook是保持Detours一致性的好例子,但是我仍然发现除非完全必要,否则使用这些库是低效的。把以,不要为了在另一个线程中运行代码而绕过renderer。
有一个更简单的方法:WndProc。我们可以只使用Windows API重写这个函数,完全是线程安全的,不需要任何detours或补丁。这是所有窗口用来处理输入和其他消息的函数,因此任何windows GUI应用程序都可以通过这种方式连接。它非常适合安全地访问调用WndProc的线程使用的数据,尤其适合破解游戏——游戏的主窗口几乎总是运行在与游戏引擎相同的线程上。
一旦我们在目标进程中加载了我们的模块,通过CreateRemoteThread和LoadLibrary、手动映射或者任何你喜欢的注入方法,我们可以简单地调用SetWindowLong (x86)或SetWindowLongPtr (x64)来应用我们自己的WndProc回调。这是一个非常简单的任务:
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); } }
如你所见,我们现在有空间在接收到第一个和随后的窗口消息时在窗口的线程中执行我们的代码。另一个好处是,我们也可以轻松地处理一些很有用的信息,包括鼠标和键盘事件——这对于热键或与我们自己的界面进行交互非常方便,我们甚至可以阻止消息到达原始指向函数不转发。但是我们可以以后再考虑。
您可能想知道我们是否需要等待窗口事件触发才能使用此方法-毕竟,很长一段时间内都不会发生任何事件。在这里,我们可以利用Windows GUI的简单性来发挥我们的优势,我们可以使用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; } }
现在我们可以随意调用窗口的线程了!启动时:
_mainWindow = new Window(_gameProcess.MainWindowHandle); _mainWindow.Attach(); _mainWindow.Invoke(UserMessage.Startup);
并且不要忘记关闭它:
_mainWindow.Invoke(UserMessage.Shutdown); _mainWindow.Detach();
英文原文:https://www.cnblogs.com/micenote/articles/12519995.html