【问题标题】:SetWindowsHookEx is injecting 32-bit DLL into 64-bit process and vice versaSetWindowsHookEx 将 32 位 DLL 注入 64 位进程,反之亦然
【发布时间】:2015-10-30 16:45:56
【问题描述】:

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

在发现 this is not possible via exclusively managed code 如果我不想使用 WH_MOUSE_LL 并且我需要一个本地 DLL 导出来将自己注入目标进程后,我开始根据什么在 C++ 中创建它我可以找到关于该主题的零散文档,然后尝试使用它连接到记事本。

虽然根据GetLastWin32Error 注入成功,但我没有收到鼠标事件的通知。在几乎放弃并选择低级全局挂钩选项后,我重新阅读了this article 的“备注”部分,这让我怀疑问题可能是因为我的代码与记事本的“位”:

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);
}

【问题讨论】:

  • 因为你的问题是关于钩子的,所以显示设置钩子的代码,以及钩子本身(你得到鼠标事件的地方)。
  • @ElderBug,感谢您的关注。我已经用相关的代码更新了这个问题。

标签: c# c++ windows hook code-injection


【解决方案1】:

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

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

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

在您的情况下没有任何反应,因为您的 UserMouseHookCallback 保持为 NULL。实际上,对SetUserHookCallback() 的调用是在应用程序DLL 实例中完成的,但UserMouseHookCallback 在目标DLL 实例中没有改变。注入后,DLL 处于不同的进程中,因此应被视为如此。您必须找到另一种方式来回调应用程序(可能发布一条消息,例如在 32 到 64 的情况下,和/或使用共享部分)。

要对此进行测试,请将MessageBox() 之类的内容放入InternalMouseHookCallback()。即使在 64-to-64 和 32-to-32 中,该框也应该出现。

【讨论】:

  • 你是说这种方法根本上是错误的,因为我的应用程序和目标进程都将有自己的 DLL 实例,因此在注入之前设置回调(或任何其他字段)一旦 DLL 在目标进程中,DLL 是否会丢失?有没有办法让这个分配持久化,以便注入的 DLL 能够直接调用我的应用程序?
  • @SaebAmini DLL 加载与fork() 不同,您不会从前一个进程中携带任何东西。这没有任何意义,因为没有先前的进程,只是加载了一个 DLL。想象一下,如果您的程序加载 user32.dll 继承了某个随机进程的空间。您可以通过共享部分使数据在实例之间持久存在,但这对您没有帮助,因为您无法在进程之间进行调用,因为它们位于不同的虚拟空间中。
  • 我明白了,这是有道理的,就像库的不同消费者之间不共享状态一样,这太疯狂了,而且目标进程实际上只是另一个消费者。我想你已经回答了我的问题的两个部分,谢谢你的好答案:) 现在我必须找到一种方法让 DLL 通知我的应用程序,无论它在哪里。
  • @SaebAmini 进程间通信有多种方式。它们都不像打电话那么简单,所以你必须选择最适合你的。有一次在这种情况下,我使用了一个命名事件,您可以在其中添加一个共享部分来传递数据。
  • @SaebAmini 对于进程之间的轻量级 IPC,请查看WM_COPYDATA。非常方便!
猜你喜欢
  • 2013-06-20
  • 2019-12-28
  • 1970-01-01
  • 2015-11-04
  • 2021-06-23
  • 2012-05-12
  • 1970-01-01
  • 2012-08-03
  • 2012-02-05
相关资源
最近更新 更多