SetWindowsHookEx正在将32位DLL注入64位进程,反之亦然

本文关键字:64位 进程 反之亦然 注入 DLL 32位 SetWindowsHookEx | 更新日期: 2023-09-27 18:19:52

我一直在开发一个应用程序,该应用程序需要监视另一个进程上的线程特定的鼠标活动(WH_MOUSE),但遇到了一些非常奇怪的事情。

在发现如果我不想使用WH_MOUSE_LL,并且我需要一个本地DLL导出来将自己注入到目标进程中,那么通过独占托管代码是不可能做到这一点之后,我根据我能找到的关于该主题的零散文档,在C++中设置并创建了它,然后尝试使用它连接到记事本中。

尽管根据GetLastWin32Error,注射成功了,但我没有收到鼠标事件的通知。在几乎放弃并选择低级别全局挂钩选项后,我重读了这篇文章的"备注"部分,这让我怀疑问题可能是因为我的代码与记事本的"比特性":

32位DLL不能注入64位进程,而64位DLL无法注入32位进程。如果应用程序需要在其他进程中使用钩子32位应用程序调用SetWindowsHookEx将32位DLL注入32位进程,以及一个64位应用程序调用SetWindowsHookEx将64位DLL注入64位进程。

然而,我的原生DLL和托管应用程序都被编译为x64,我正试图挂接64位版本的记事本,所以它应该工作得很好,但我还是在黑暗中尝试了一下,进入SysWOW64文件夹,从那里打开了32位记事本,再次尝试挂接,这次挂接效果很好!

奇怪的是,我随后将我的原生DLL和托管应用程序重新编译为x86,并在32位记事本上进行了测试,但它没有工作,但在我的普通64位记事本上工作!

我怎么可能将32位DLL注入64位进程,反之亦然!

尽管我最初的问题已经解决,我可以继续我的应用程序开发,但我对为什么SetWindowsHookEx中观察到这种奇怪的反向行为的好奇让我抓狂,所以我真的希望有人能对此有所了解。

我知道这件事很多,但没有代码,但即使是一个示例应用程序的代码也相当大,有托管和非托管两种风格,但我会立即发布您认为可能相关的任何代码。

我还创建了一个示例应用程序,以便您可以自己测试这种行为。这是一个简单的WinForms应用程序,它试图挂接到记事本并显示鼠标事件:

http://saebamini.com/HookTest.zip

它同时包含x86版本和x64版本。在我的机器上(我使用的是64位Windows 7),x86版本只能与64位记事本配合使用,x64版本只能与32位记事本(来自SysWOW64)配合使用。

更新-相关代码位:

对非托管库的C#调用:

public SetCallback(HookTypes type)
{
    _type = type;
    _processHandler = new HookProcessedHandler(InternalHookCallback);
    SetCallBackResults result = SetUserHookCallback(_processHandler, _type);
    if (result != SetCallBackResults.Success)
    {
        this.Dispose();
        GenerateCallBackException(type, result);
    }
}
public void InstallHook()
{
    Process[] bsProcesses = Process.GetProcessesByName("notepad");
    if(bsProcesses.Length == 0)
    {
        throw new ArgumentException("No open Notepad instance found.");
    }
    ProcessThread tmp = GetUIThread(bsProcesses[0]);
    if (!InitializeHook(_type, tmp.Id))
    {
        throw new ManagedHooksException("Hook initialization failed.");
    }
    _isHooked = true;
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern int GetWindowThreadProcessId(IntPtr hWnd, IntPtr procid);
// 64-bit version
[DllImport("SystemHookCore64.dll", EntryPoint = "InitializeHook", SetLastError = true,
        CharSet = CharSet.Unicode, ExactSpelling = true,
        CallingConvention = CallingConvention.Cdecl)]
private static extern bool InitializeHook(HookTypes hookType, int threadID);

[DllImport("SystemHookCore64.dll", EntryPoint = "SetUserHookCallback", SetLastError = true,
        CharSet = CharSet.Unicode, ExactSpelling = true,
        CallingConvention = CallingConvention.Cdecl)]
private static extern SetCallBackResults SetUserHookCallback(HookProcessedHandler hookCallback, HookTypes hookType);

C++:

HookProc UserMouseHookCallback = NULL;
HHOOK hookMouse = NULL;
HINSTANCE g_appInstance = NULL;
MessageFilter mouseFilter;
bool InitializeHook(UINT hookID, int threadID)
{
    if (g_appInstance == NULL)
    {
        return false;
    }
    if (hookID == WH_MOUSE)
    {
        if (UserMouseHookCallback == NULL)
        {
            return false;
        }
        hookMouse = SetWindowsHookEx(hookID, (HOOKPROC)InternalMouseHookCallback, g_appInstance, threadID);
        return hookMouse != NULL;
    }
}
int SetUserHookCallback(HookProc userProc, UINT hookID)
{   
    if (userProc == NULL)
    {
        return HookCoreErrors::SetCallBack::ARGUMENT_ERROR;
    }
    if (hookID == WH_MOUSE)
    {
        if (UserMouseHookCallback != NULL)
        {
            return HookCoreErrors::SetCallBack::ALREADY_SET;
        }
        UserMouseHookCallback = userProc;
        mouseFilter.Clear();
        return HookCoreErrors::SetCallBack::SUCCESS;
    }
    return HookCoreErrors::SetCallBack::NOT_IMPLEMENTED;
}
int FilterMessage(UINT hookID, int message)
{
    if (hookID == WH_MOUSE)
    {
        if(mouseFilter.AddMessage(message))
        {
            return HookCoreErrors::FilterMessage::SUCCESS;
        }
        else
        {
            return HookCoreErrors::FilterMessage::FAILED;
        }
    }
    return HookCoreErrors::FilterMessage::NOT_IMPLEMENTED;
}
static LRESULT CALLBACK InternalMouseHookCallback(int code, WPARAM wparam, LPARAM lparam)
{
    if (code < 0)
    {
        return CallNextHookEx(hookMouse, code, wparam, lparam);
    }
    if (UserMouseHookCallback != NULL && !mouseFilter.IsFiltered((int)wparam))
    {
        UserMouseHookCallback(code, wparam, lparam);
    }
    return CallNextHookEx(hookMouse, code, wparam, lparam);
}

SetWindowsHookEx正在将32位DLL注入64位进程,反之亦然

我对你的问题的最佳猜测:

Windows挂钩系统能够从任何比特挂钩32位和64位应用程序。问题是,正如你所指出的,你不能用错误的比特将DLL注入到应用程序中。为了实现这一点,如果可以的话,Windows通常会注入DLL,但如果不能,它会设置一个使用挂钩应用程序消息循环的回调。由于消息循环由操作系统处理,因此它用于从不同的位进行调用。

在您的情况下,唯一有效的方法是消息循环方式。这有一个很好的理由:64到64和32到32的调用没有成功的机会,因为钩子在注入的DLL中,也就是说,在与应用程序不同的进程中。

在您的情况下不会发生任何事情,因为您的UserMouseHookCallback保持为NULL。实际上,对SetUserHookCallback()的调用是在应用程序DLL实例中完成的,但UserMouseHookCallback在目标DLL实例中没有变化。一旦被注入,DLL就处于不同的过程中,应该被视为这样。您必须找到另一种方法来调用应用程序(可能会像32-to-64的情况一样发布消息,和/或使用共享部分)。

要对此进行测试,请在InternalMouseHookCallback()中放入类似MessageBox()的内容。即使是64比64和32比32,这个框也应该出现。