很早以前就想研究一下托管函数的挂钩,但是一直没有成功。前段时间又尝试了一次,竟然成功了,这让我十分的困惑——因为我几乎没有对代码做任何改变!这次实验距离以前已经有很长时间了,我实在想不出有什么不同之处,唯一的区别就是以前用的是控制台程序,这次是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函数来修改内存,全部失败。另外,静态函数无法挂钩。
于是我将控制台的类派生之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函数来修改内存,全部失败。另外,静态函数无法挂钩。
第一次执行时,通过元数据找到IL代码,然后被JIT编译,编译后的CPU指令被保存在内存中,JIT吧前面的函数地址替换成这个CPU指令的地址但这个地址可能在JIT编译后就不能修改了吧我的猜测,不知道对不对,呵呵。
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 返回
}
写错了 这里是: restoreTargetMethod();