如何挂钩应用程序?

Posted

技术标签:

【中文标题】如何挂钩应用程序?【英文标题】:How to hook an application? 【发布时间】:2012-02-01 20:28:02 【问题描述】:

我正在尝试在我的 C# 应用程序中创建一个窗口。

static IntPtr hhook = IntPtr.Zero;
static NativeMethods.HookProc hhookProc;

static void Main(string[] args)

    // Dummy.exe is a form with a button that opens a MessageBox when clicking on it.
    Process dummy = Process.Start(@"Dummy.exe");

    try
    
        hhookProc = new NativeMethods.HookProc(Hook);
        IntPtr hwndMod = NativeMethods.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
        hhook = NativeMethods.SetWindowsHookEx(HookType.WH_CBT, hhookProc, hwndMod, (uint)AppDomain.GetCurrentThreadId());

        Console.WriteLine("hhook valid? 0", hhook != IntPtr.Zero);

        while (!dummy.HasExited)
            dummy.WaitForExit(500);                
    
    finally
    
        if(hhook != IntPtr.Zero)
            NativeMethods.UnhookWindowsHookEx(hhook);
    


static int Hook(int nCode, IntPtr wParam, IntPtr lParam)

    Console.WriteLine("Hook()");
    return NativeMethods.CallNextHookEx(hhook, nCode, wParam, lParam);

问题是,当点击我的按钮(在 Dummy.exe 中)时,我从来没有进入我的 Hook,我做错了什么?

谢谢。


编辑

程序.cs

using System;
using System.Diagnostics;

namespace Hooker

    class Program
    
        static IntPtr hhook = IntPtr.Zero;
        static NativeMethods.HookProc hhookProc;

        static void Main(string[] args)
        
            Process dummy = Process.Start(@"Dummy.exe");

            try
            
                hhookProc = new NativeMethods.HookProc(Hook);
                hhook = NativeMethods.SetWindowsHookEx(HookType.WH_CBT, hhookProc, IntPtr.Zero, 0);

                Console.WriteLine("hhook valid? 0", hhook != IntPtr.Zero);

                while (!dummy.HasExited)
                    dummy.WaitForExit(500);                
            
            finally
            
                if(hhook != IntPtr.Zero)
                    NativeMethods.UnhookWindowsHookEx(hhook);
            
        

        static int Hook(int nCode, IntPtr wParam, IntPtr lParam)
        
            Console.WriteLine("Hook()");
            return NativeMethods.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
        
    

NativeMethods.cs

namespace Hooker

    using System;
    using System.Runtime.InteropServices;

    internal static class NativeMethods
    
        public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, int dwThreadId);
        [DllImport("user32.dll")]
        public static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
        [DllImport("user32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern int GetWindowThreadProcessId(IntPtr hwnd, ref int pid);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);
    

对于假人,使用 :

创建一个新表单
    public Form1()
    
        InitializeComponent();
    

    private void button1_Click(object sender, EventArgs e)
    
        MessageBox.Show("CONTENT", "TITLE");
    

【问题讨论】:

为什么要使用键盘挂钩?我想检测一个exe(没有GUI)何时调用另一个exe(有GUI)。键盘的链接是什么? 我记错了 WH_CBT 的意思。 为什么在模块句柄前加上hwnd?它不是窗口句柄。 用非托管代码编写。 这个主题写得很好:***.com/questions/11607133/global-mouse-event-handler 【参考方案1】:

与大多数 SetWindowsHookEx 挂钩一样,WH_CBT 挂钩要求挂钩回调位于一个单独的 Win32 DLL 中,该 DLL 将被加载到目标进程中。这本质上要求钩子是用 C/C++ 编写的,C# 在这里不起作用。

(低级鼠标和键盘钩子是此规则的两个例外。也可以在 C# 中使用其他钩子,但前提是您要钩子自己的线程之一,因此 dwThreadId 是线程的 id当前进程,而不是 0。不过,我还没有确认这一点。而且你需要确保你使用的是 Win32 线程 ID,所以在这里使用 GetCurrentThreadId 可能是最好的选择。)

如果您想观察另一个应用程序中出现的新窗口,另一种 C# 友好的方法是使用 SetWinEventHook API,指定 WINEVENT_OUTOFCONTEXT(这是将事件传递到您自己的进程的神奇标志,消除对 DLL 的需求并使 C# 在此处可用)并挂钩 EVENT_OBJECT_CREATEEVENT_OBJECT_SHOW 事件。您可以监听自己的进程/线程的事件,也可以监听当前桌面上的所有进程/线程。

这将为您提供各种“创建”和显示通知,包括对话框中子 HWND 的通知,甚至列表框内的项目等;因此您需要过滤以仅提取*** HWND 的内容:例如。检查 idObject==OBJID_WINDOW 和 idChild==0;那个 hwnd 是可见的 (IsVisible()) 并且是***的。

请注意,使用 WinEvents 要求调用 SetWinEventHook 的线程正在泵送消息 - 如果它是带有 UI 的线程,则通常都是这种情况。如果没有,您可能需要手动添加消息循环(GetMessage/TranslateMessage)。您还需要在此处将 GC.KeepAlive() 与回调一起使用,以防止在您调用 UnhookWinEvents 之前收集它。

【讨论】:

【参考方案2】:

您的代码的一个问题是 hhookProc 可以在您的本机代码仍然需要它时被垃圾收集。使用GC.KeepAlive 或放入静态变量中。

hMod 参数可能应该为空,因为您在自己的进程中指定了一个线程:

hMod [输入]

类型:HINSTANCE

包含 lpfn 参数指向的钩子过程的 DLL 句柄。如果 dwThreadId 参数指定由当前进程创建的线程,并且钩子过程在与当前进程关联的代码内,则 hMod 参数必须设置为 NULL。


但我认为这仅适用于您指定的线程上的窗口。

理论上,您可以在其他应用程序甚至全局挂钩中指定线程。然后在相应的线程上调用指定的回调,即使该线程在另一个进程中,在这种情况下,您的 dll 将被注入该进程(这就是您需要首先指定模块句柄的原因)。

但我相信 .net 代码无法做到这一点,因为注入其他进程和调用钩子方法的机制不适用于 JIT 编译代码。

【讨论】:

@Arnaud 您的新代码仍然损坏。您要么需要在自己的进程中指定0 hModule 和线程。在这种情况下,您只能在自己的进程中观察窗口。或者您指定0 或一个外螺纹和一个非空hModule。但正如我所写,第二种方法在 .net 中可能是不可能的。 实际上,我不知道应该将哪个值传递给 SetWindowsHookEx 来挂钩我从当前应用程序内部启动的应用程序... @Arnaud 您需要将当前的 dll hModule 传递为 hMod,并将与创建的窗口关联的线程传递为 threadId。然后,一旦发生事件,您的 dll 就会被注入目标进程并调用您的钩子方法。但是钩子方法必须与你的 dll 保持恒定的偏移量,即它不能被 JIT 编译。 谢谢,其实我看了这个:klocwork.com/products/documentation/current/How_kwinject_works,这篇文章里写的是On Windows, kwinject starts the user process in debug mode and listens to CREATE_PROCESS debug events.,这是怎么做到的,这个可以用SetWindowsHook来做吧? 不太可能。根据文本,它充当要监视的进程的调试器。【参考方案3】:

这在 C# 中不起作用

作用域:线程

如果应用程序为不同应用程序的线程安装挂钩过程,则该过程必须在 DLL 中

SetWindowsHookEx 的文档)

范围:全球

要安装全局挂钩,挂钩必须具有本机 DLL 导出,才能将自身注入另一个需要有效、一致的函数才能调用的进程。此行为需要 DLL 导出。 .NET Framework 不支持 DLL 导出。

(Source)

【讨论】:

在 C# 中实现的唯一可能的 Windows 挂钩是 全局低级挂钩本地线程挂钩【参考方案4】:

我不熟悉您所引用的 NativeMethod 类,但我会做出一些假设并尝试建立一些基础。 我猜这与你钩住的手柄有关。

dummy.MainWindowHandle

代表最前面窗口的句柄,这通常是您要查找的。但是,在这种情况下,您正在打开一个 MessageBox.Show(),它的句柄可能与您所连接的句柄不同。

我认为

IntPtr hwndMod = NativeMethods.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);

可能会返回与

相同的结果
dummy.Refresh();
IntPtr hwndMod = dummy.MainWindowHandle;

所以我认为可以肯定地说,他们可能会给你你不想要的句柄。

也许尝试制作一个测试 WinForm 应用程序。这样你就可以抓住正确的把手。只要确保使用

dummy.WaitForInputIdle();
dummy.Refresh(); 

在抓住手柄之前,确保您在启动时抓住了正确的手柄。

【讨论】:

@ArnaudF。好吧,我需要更多关于 NativeMethod 东西的代码或项目的副本来进一步测试。不够我工作。 你在什么时候得到假人句柄?从外观上看(如果我没看错的话)你只是给它当前的应用程序句柄。 hModule 不是窗口句柄。 是的,虚拟句柄来自当前应用程序内部启动的进程。 好的,但是从这段代码来看,虚拟应用程序似乎无关紧要。您可以在外部启动它并产生相同的结果。假人的信息永远不会在它启动之外使用。这个钩子似乎正在尝试一个全局钩子。不过听 CodeInChaos 的回复,他似乎对这个话题了解得更多。【参考方案5】:

我看到它是一个控制台应用程序,所以控制台应用程序不会进入 Windows 消息循环。

简单的解决方案是包含 system.windows.forms

然后在你的 main 中输入 application.start() 一切都会好起来的:)

【讨论】:

以上是关于如何挂钩应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

如何制作热键挂钩? “背景热键”

如何让关闭挂钩在从 Eclipse 启动的进程上执行

如何挂钩 ios 连接更改

如何挂钩网络摄像头捕获?

MFC 全局键盘挂钩

如何在最新版本中配置 Detox 生命周期挂钩?