很早以前就想研究一下托管函数的挂钩,但是一直没有成功。前段时间又尝试了一次,竟然成功了,这让我十分的困惑——因为我几乎没有对代码做任何改变!这次实验距离以前已经有很长时间了,我实在想不出有什么不同之处,唯一的区别就是以前用的是控制台程序,这次是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函数来修改内存,全部失败。另外,静态函数无法挂钩。

解决方案 »

  1.   

    而如果我在挂钩之前已经调用过该方法,则挂钩失败这个应该是第一次调用前,方法表的地址指向一个函数,这个函数是没有正式记录的函数,
    第一次执行时,通过元数据找到IL代码,然后被JIT编译,编译后的CPU指令被保存在内存中,JIT吧前面的函数地址替换成这个CPU指令的地址但这个地址可能在JIT编译后就不能修改了吧我的猜测,不知道对不对,呵呵。
      

  2.   

    HOOK的原理是:一般函数调用的时候都会:
    1 pull eax
    2 pull ecx
    3 call 12345678PULL是压寨参数 (反过来的)
    add(int a,int b); 是先压B在压A
    所以 HOOK 只要修改3 call 12345678;就行了 一般修改成
    jmp 22345678 //这个是我们自己的函数然后再22345678里面 先还原3成原来的3 call 12345678然后执行原来的方法 然后再挂钩,把3改成JMP 22345678这个样子就成狗HOOK函数了……
    简化下就是Hook(targetMethod);//先HOOK原来的函数返回值 HookHandler(参数)
    {
      restoreTargetMethod(参数);//先还原原来的函数  //这里做事情,改数据啊什么的  返回 rtn=targetMethod(参数);//执行原来的函数,参数可能别修改过  Hook(targetMethod);//
      return   返回 
    }
      

  3.   

    你那样子写 只是把函数换了一个而已····*(int*)myAddress = *(int*)youAddress;
      

  4.   

      restoreTargetMethod(参数);//先还原原来的函数 
    写错了 这里是:  restoreTargetMethod();