纯C#实现Hook功能详解

2019-12-30 15:51:23丽君

到了这里,我有些不耐烦了,毕竟我是要hook所有函数的,而不是某个固定的函数,而函数入口的指令又不相同,这可怎么办,难道我需要计算出大于等于5字节的最小完整汇编指令长度?

按照这个思路,最终找到了一个用C写的反汇编库(BlackBone),其中提供了类似的方法,我稍作了修改后试用了下,的确不错,可以准确求出汇编指令长度,例如

push ebp

mov ebp,esp

mov eax,dword ptr ds:[33F22ACh]

求出值是9,这样我根据求出的值动态拼接一个函数出来即可,哈哈,到了这里,感觉实现的差不多了,但没想到64位下又给了我当头一棒,之前的原函数指令可以写成:

大于等于5字节的最小完整汇编指令+jmp跳转指令即可构成我们的原函数

但我们知道,C#中要想执行汇编,是需要用Marshal.AllocHGlobal来分配非托管空间的,而这样分配的地址与我们要跳转到的原函数的地址在64位下是超过2GB地址范围的,一般的跳转指令是无法实现的,所以想到了用ret指令实现,而64位地址又不能直接push,所以最后写出如下汇编:

push rax

mov rax,target_addr

push rax

mov rax,qword ptr ss:[rsp+8]

ret 8

由于某些C#函数竟然第一行就是修改rax寄存器的值,所以只能是先保存rax,推入堆栈后再恢复,这里汇编操作就方便多了,之前实现另一个东西,用到IL指令,但发现只有dup这种复制栈顶元素的指令,却没有获取堆栈中某个非栈顶元素值的指令,所以说还是汇编灵活啊,想怎么写就怎么写,啥都能实现。

最后就是这个原函数的调用过程了,因为是动态拼接的函数,所以想到的就是用Marshal.GetDelegateForFunctionPointer转成委托来执行,后来发现不对,因为我虽然拼接的是汇编,而这个汇编是C#方法jit后的汇编,这个并不是C方法编译后的汇编,通过把非托管指针转换为委托的方式运行函数是会添加很多不需要的操作的,例如托管类型与非托管类型的转换,但我拼接出的函数是不需要这些过程的,这个怎么办,看来只能用调用C#普通函数的方式调用,这个怎么实现呢,其实很好办,只需写一个空壳函数,然后修改这个函数的方法表中的原生指令指针即可,具体方法如下:


*((ulong*)((uint*)method.MethodHandle.Value.ToPointer() + 2)) = (ulong)ptr;

method是空壳函数的MethodInfo,ptr是动态拼接的原函数的地址

好,到了这里就基本完成核心功能了,最不好处理的就是这个原函数调用,我的完整的64位原函数指令拼接就实现了,代码很少,如下所示:


byte[] jmp_inst =

{

 0x50,            //push rax

 0x48,0xB8,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90, //mov rax,target_addr

 0x50,            //push rax

 0x48,0x8B,0x44,0x24,0x08,       //mov rax,qword ptr ss:[rsp+8]

 0xC2,0x08,0x00          //ret 8

};

 

protected override void CreateOriginalMethod(MethodInfo method)

{

 uint oldProtect;

 var needSize = NativeAPI.SizeofMin5Byte(srcPtr);

 byte[] src_instr = new byte[needSize];

 for (int i = 0; i < needSize; i++)

 {

  src_instr[i] = srcPtr[i];

 }

 fixed (byte* p = &jmp_inst[3])

 {

  *((ulong*)p) = (ulong)(srcPtr + needSize);

 }

 var totalLength = src_instr.Length + jmp_inst.Length;

 IntPtr ptr = Marshal.AllocHGlobal(totalLength);

 Marshal.Copy(src_instr, 0, ptr, src_instr.Length);

 Marshal.Copy(jmp_inst, 0, ptr + src_instr.Length, jmp_inst.Length);

 NativeAPI.VirtualProtect(ptr, (uint)totalLength, Protection.PAGE_EXECUTE_READWRITE, out oldProtect);

 RuntimeHelpers.PrepareMethod(method.MethodHandle);

 *((ulong*)((uint*)method.MethodHandle.Value.ToPointer() + 2)) = (ulong)ptr;

}