很早以前就想研究一下托管函数的挂钩,但是一直没有成功。前段时间又尝试了一次,竟然成功了,这让我十分的困惑——因为我几乎没有对代码做任何改变!
这次实验距离以前已经有很长时间了,我实在想不出有什么不同之处,唯一的区别就是以前用的是控制台程序,这次是WinForm程序,但我想不可能是这个原因吧?不过我还是尝试了一下,未曾料到竟真是如此!
我当然不会相信这样的灵异事件!
于是我想,我平时写代码很随意,一般不自己定义类,直接在自动生成的类里定义函数,会不会是这个原因呢?
于是我将控制台的类派生之Form,果然,挂钩成功。
但是很显然,不可能是因为Form类,于是我很自然的想到了继承树上更高层次的MarshalByRefObject类,并验证了一下,正如我所料,代码如下:
namespace CLR_Hook
{
unsafe class Program
{
static void Main()
{
CsToD ctd = new CsToD();
Hook(ctd.My, ctd.You);
ctd.My();
Console.ReadKey();
}
static void Hook(Action my,Action you)
{
IntPtr myAddress = my.Method.MethodHandle.GetFunctionPointer();
IntPtr youAddress = you.Method.MethodHandle.GetFunctionPointer();
*(int*)myAddress = *(int*)youAddress;
}
}
class CsToD:MarshalByRefObject
{
public void My()
{
Console.WriteLine("My");
}
public void You()
{
Console.WriteLine("You");
}
}
}
可以看到,我明明调用的是CsToD类的My方法,但是输出结果告诉我们,实际调用的是You方法,说明挂钩成功了。
而如果我在挂钩之前已经调用过该方法,则挂钩失败:
static void Main()
{
CsToD ctd = new CsToD();
ctd.My();//挂钩之前先请用该方法
Hook(ctd.My, ctd.You);
ctd.My();//挂钩失败,仍调用My方法
Console.ReadKey();
}
这让我十分困惑,唯一能想到的理由是:
My方法的地址最初被存放在内存中的A地址,当My方法被调用后,又在内存中的B地址存了一份拷贝,以后再调用时,直接到B地址去读取My方法的地址。
这样的话,虽然我们修改了A地址中的数据,但并不影响My方法的调用。
当然,这只是我的猜测。
此路不通,我想了另一个方法——直接修改函数体,用经典的Jmp指令跳转。
但是,又失败了,因为该内存受保护,根本不能写:
static void Hook(Action my, Action you)
{
IntPtr myAddress = my.Method.MethodHandle.GetFunctionPointer();
IntPtr youAddress = you.Method.MethodHandle.GetFunctionPointer();
*(int*)*(int*)myAddress = 0;//该地址根本不能写,赋什么值都无所谓
}
本来以为修改一下保护属性就行了,结果连查询保护属性都失败,VirtualQuery、VirtualProtect统统调用失败。
我也尝试用Marshal类,UnmanagedMemoryStream类,和WriteProcessMemory函数来修改内存,全部失败。
另外,静态函数无法挂钩。