工作中需要用到win32 api hook,但为了方便,希望能直接用c#来实现,上网搜索了一番,找到easyhook,感觉挺好用的,一下午做了个demo,现记录如下:
首先到官网下载二进制文件(我下载的是当前最新的EasyHook-2.7.6270.0-Binaries.zip)
https://easyhook.github.io/
解压后可以看到NetFX3.5和NetFX4.0两个文件夹,其中内容一模一样,前者用于.NET 3.5,后者用于.NET 4.0
然后在VS中新建两个工程,一个是将要注入目标进程的类库dll工程TestLib,另一个是命令行工程EasyHookDemo,用来执行注入操作。EasyHook使用非常方便,直接在工程中引用对应版本的EasyHook.dll即可,不过在运行时还需要将另外两个dll也放入可执行文件目录下。由于我的系统是64位win7,所以是EasyHook64.dll和EasyLoad64.dll,如下图所示
接下来就是写代码了,本样例的目标是,在windows自带的计算器程序上注入一个钩子,使得计算器输入的数字增加1。本样例的思路来自于如下网址,但在其基础上做了改进。
http://www.cnblogs.com/ghostr/p/5513199.html
TestLib中有两个文件Class1.cs和Class2.cs,每个文件中有一个类。Class1.cs中的类对目标对象进行操作;Class2.cs中的类则充当目标程序与注入程序EasyHookDemo之间的交互。下面便是二者的源代码:
Class1.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using EasyHook;
namespace TestLib
{
public class TestClass : EasyHook.IEntryPoint
{
FileMonInterface Interface = null;
LocalHook Hook;
Stack<String> Queue = new Stack<String>();
public TestClass(
RemoteHooking.IContext InContext,
String InChannelName)
{
// connect to host...
Interface = RemoteHooking.IpcConnectClient<FileMonInterface>(InChannelName);
Interface.Ping();
}
public void Run(
RemoteHooking.IContext InContext,
String InChannelName)
{
// install hook...
Hook = LocalHook.Create(
LocalHook.GetProcAddress("user32.dll", "SetWindowTextW"),
new DSetWindowText(SetWindowText_Hooked),
this);
Hook.ThreadACL.SetExclusiveACL(new Int32[] { 0 });
Interface.IsInstalled(RemoteHooking.GetCurrentProcessId());
try
{
while (true)
{
Thread.Sleep(500);
}
}
catch (Exception e)
{
Interface.ReportException(e);
}
Hook.Dispose();
LocalHook.Release();
}
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi, SetLastError = true)]
delegate bool DSetWindowText(IntPtr hWnd, string text);
[DllImport("user32.dll", CharSet = CharSet.Ansi, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
static extern bool SetWindowText(IntPtr hWnd, string text);
static bool SetWindowText_Hooked(IntPtr hWnd, string text)
{
// Interface.ReportMessage(text); // 可用于调试
int num = 0;
bool flag = int.TryParse(text, out num);
if (flag)
{
text = (num + 1).ToString();//修改要显示的数据
}
return SetWindowText(hWnd, text);//调用API
}
}
}
Class2.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestLib
{
public class FileMonInterface : MarshalByRefObject
{
public void IsInstalled(Int32 InClientPID)
{
Console.WriteLine("FileMon has been installed in target {0}.\r\n", InClientPID);
}
public void ReportMessages(String[] InFileNames)
{
for (int i = 0; i < InFileNames.Length; i++)
{
Console.WriteLine(InFileNames[i]);
}
}
public void ReportMessage(string message)
{
Console.WriteLine(message);
}
public void ReportException(Exception InInfo)
{
Console.WriteLine("The target process has reported an error:\r\n" + InInfo.ToString());
}
public void Ping()
{
}
}
}
EasyHookDemo中只有一个文件Program.cs,用于将TestLib注入计算器进程,并显示一些信息(比如来自FileMonInterface的信息)。其源代码如下:
Program.cs
using EasyHook;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.Remoting;
using System.Text;
namespace EasyHookDemo
{
class Program
{
static String ChannelName = null;
static void Main(string[] args)
{
int TargetPID;
string targetExe = null;
// Will contain the name of the IPC server channel
string channelName = null;
Process[] localByName = Process.GetProcessesByName("calc");
TargetPID = localByName[0].Id;
RemoteHooking.IpcCreateServer<TestLib.FileMonInterface>(ref ChannelName, WellKnownObjectMode.SingleCall);
string injectionLibrary = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "TestLib.dll");
RemoteHooking.Inject(
TargetPID,
injectionLibrary,
injectionLibrary,
ChannelName);
Console.WriteLine("Injected to process {0}", TargetPID);
Console.WriteLine("<Press any key to exit>");
Console.ReadKey();
}
}
}
运行结果如下:
注意鼠标点的是5,但实际显示的是6。