9.8.2018.0630
大石头 编写于 2018-06-30 11:15:32
X
using System;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using NewLife.Log;

namespace NewLife.Reflection
{
    /// <summary>API钩子</summary>
    /// <remarks>
    /// 实现上,是两个方法的非托管指针互换,为了方便后面换回来。
    /// 但是很奇怪,UnHook换回来后,执行的代码还是更换后的,也就是无法复原。
    /// 
    /// 一定要注意,在vs中调试会导致Hook失败,尽管换了指针,也无法变更代码执行流程。
    /// </remarks>
    public class ApiHook : DisposeBase
    {
        #region 属性
        /// <summary>原始方法</summary>
        public MethodBase OriMethod { get; set; }

        /// <summary>新方法</summary>
        public MethodBase NewMethod { get; set; }
        #endregion

        #region 构造
        /// <summary>子类重载实现资源释放逻辑时必须首先调用基类方法</summary>
        /// <param name="disposing">从Dispose调用(释放所有资源)还是析构函数调用(释放非托管资源)。
        /// 因为该方法只会被调用一次,所以该参数的意义不太大。</param>
        protected override void OnDispose(bool disposing)
        {
            base.OnDispose(disposing);

            if (ishooked) UnHook();
        }
        #endregion

        #region 方法
        private Boolean ishooked;

        /// <summary>挂钩</summary>
        public void Hook()
        {
            ishooked = true;
            Exchange("ApiHook");
        }

        /// <summary>取消挂钩</summary>
        public void UnHook()
        {
            if (!ishooked) return;
            ishooked = false;

            Exchange("ApiUnHook");
        }

        void Exchange(String action)
        {
            var adds = GetAddress(OriMethod, NewMethod);
            unsafe
            {
                if (IntPtr.Size == 8)
                {
                    var s = (ulong*)adds[0].ToPointer();
                    var d = (ulong*)adds[1].ToPointer();
                    WriteLog("{4} [{0}.{1}] 0x{2:X8} = 0x{3:X8}", OriMethod.DeclaringType.Name, OriMethod.Name, (ulong)s, *s, action);
                    WriteLog("{4} [{0}.{1}] 0x{2:X8} = 0x{3:X8}", NewMethod.DeclaringType.Name, NewMethod.Name, (ulong)d, *d, action);

                    var ori64 = *s;
                    *s = *((ulong*)adds[1].ToPointer());
                    *d = ori64;
                }
                else
                {
                    var s = (uint*)adds[0].ToPointer();
                    var d = (uint*)adds[1].ToPointer();
                    WriteLog("{4} [{0}.{1}] 0x{2:X8} = 0x{3:X8}", OriMethod.DeclaringType.Name, OriMethod.Name, (uint)s, *s, action);
                    WriteLog("{4} [{0}.{1}] 0x{2:X8} = 0x{3:X8}", NewMethod.DeclaringType.Name, NewMethod.Name, (uint)d, *d, action);

                    var ori = *s;
                    *s = *((uint*)adds[1].ToPointer());
                    *d = ori;
                }
            }
        }
        #endregion

        #region 辅助
        [Conditional("DEBUG")]
        void WriteLog(String format, params Object[] args)
        {
            XTrace.WriteLine(format, args);
        }
        #endregion

        #region JIT方法地址
        /// <summary>获取方法在JIT编译后的地址(JIT Stubs)</summary>
        /// <remarks>
        /// MethodBase.DeclaringType.TypeHandle.Value: 指向该类型方法表(编译后)在 JIT Stubs 的起始位置。
        /// Method.MethodHandle.Value: 表示该方法的索引序号。
        /// CLR 2.0 SP2 (2.0.50727.3053) 及其后续版本中,该地址的内存布局发生了变化。直接用 "Method.MethodHandle.Value + 2" 即可得到编译后的地址。
        /// </remarks>
        /// <param name="method"></param>
        /// <returns></returns>
        unsafe public static IntPtr GetMethodAddress(MethodBase method)
        {
            // 处理动态方法
            if (method is DynamicMethod)
            {
                var ptr = (byte*)((RuntimeMethodHandle)method.GetValue("m_method")).Value.ToPointer();

                // 确保方法已经被编译
                RuntimeHelpers.PrepareMethod(method.MethodHandle);

                if (IntPtr.Size == 8)
                    return new IntPtr((ulong*)*(ptr + 5) + 12);
                else
                    return new IntPtr((uint*)*(ptr + 5) + 12);
            }

            ShowMethod(new IntPtr((int*)method.MethodHandle.Value.ToPointer() + 2));
            // 确保方法已经被编译
            RuntimeHelpers.PrepareMethod(method.MethodHandle);
            ShowMethod(new IntPtr((int*)method.MethodHandle.Value.ToPointer() + 2));

            return new IntPtr((int*)method.MethodHandle.Value.ToPointer() + 2);
        }

        static readonly Type mbroType = typeof(MarshalByRefObject);
        /// <summary>替换方法</summary>
        /// <remarks>
        /// Method Address 处所存储的 Native Code Address 是可以修改的,也就意味着我们完全可以用另外一个具有相同签名的方法来替代它,从而达到偷梁换柱(Injection)的目的。
        /// </remarks>
        /// <param name="src"></param>
        /// <param name="des"></param>
        public static void ReplaceMethod(MethodBase src, MethodBase des)
        {
            var adds = GetAddress(src, des);
            ReplaceMethod(adds[0], adds[1]);
        }

        unsafe static IntPtr[] GetAddress(MethodBase src, MethodBase des)
        {
            var adds = new IntPtr[2];
            if (src.IsStatic && des.IsStatic ||
                !mbroType.IsAssignableFrom(src.DeclaringType) && !mbroType.IsAssignableFrom(des.DeclaringType))
            {
                adds[0] = GetMethodAddress(src);
                adds[1] = GetMethodAddress(des);
            }
            else if (mbroType.IsAssignableFrom(src.DeclaringType) && mbroType.IsAssignableFrom(des.DeclaringType))
            {
                adds[0] = src.MethodHandle.GetFunctionPointer();
                adds[1] = des.MethodHandle.GetFunctionPointer();
            }

            return adds;
        }

        unsafe private static void ReplaceMethod(IntPtr src, IntPtr dest)
        {
            XTrace.WriteLine("0x{0}=>0x{1}", src.ToString("x"), dest.ToString("x"));
            //ShowMethod(src);
            //ShowMethod(dest);

            // 区分处理x86和x64
            if (IntPtr.Size == 8)
            {
                var d = (ulong*)src.ToPointer();
                *d = *((ulong*)dest.ToPointer());
            }
            else
            {
                var d = (uint*)src.ToPointer();
                *d = *((uint*)dest.ToPointer());
            }
        }

        private static void ShowMethod(IntPtr mt)
        {
            XTrace.WriteLine("ShowMethod: {0}", mt.ToString("x"));
            var buf = new Byte[8];
            Marshal.Copy(mt, buf, 0, buf.Length);
            //XTrace.WriteLine(buf.ToHex("-"));

            var ip = new IntPtr((Int64)buf.ToUInt64());
            XTrace.WriteLine("{0}", ip.ToString("x"));

            if (ip.ToInt64() <= 0x1000000 || ip.ToInt64() > 0x800000000000L) return;

            buf = new Byte[32];
            Marshal.Copy(ip, buf, 0, buf.Length);
            XTrace.WriteLine(buf.ToHex("-"));
        }
        #endregion
    }
}