如何挂钩应用程序?
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_CREATE
和 EVENT_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() 一切都会好起来的:)
【讨论】:
以上是关于如何挂钩应用程序?的主要内容,如果未能解决你的问题,请参考以下文章