美文网首页Android进阶之路Android奇技淫巧Android开发经验谈
FastHook——巧妙利用动态代理实现非侵入式AOP

FastHook——巧妙利用动态代理实现非侵入式AOP

作者: 图灵技师 | 来源:发表于2019-03-31 10:30 被阅读11次

    一、概述

    FastHook框架要求调用者准备与原方法参数一致的Hook方法和Forward方法,这些限制将业务逻辑和Hook逻辑耦合在一起。若不了解FastHook原理,请移步FastHook——一种高效稳定、简洁易用的Android Hook框架
    因此可能需要一种新实现方式,其可将业务逻辑和Hook逻辑解耦。一种简单的方案便是动态生成Hook方法和Forword方法。然而该方案对性能的影响比较大,动态生成dex文件并加载是耗时操作。
    本文将介绍一种新的思路,统一Hook方法,利用动态代理创建Forward方法。该方案比上述方案更加高效、简洁

    Hook方案

    二、Hook方法

    Hook方法必须能实现以下两点需求
    1. 能正确识别原方法
    2. 能正确解析原方法参数

    2.1 ART方法调用约定

    以32位arm平台为例

        /*
         * Quick invocation stub internal.
         * On entry:
         *   r0 = method pointer
         *   r1 = argument array or null for no argument methods
         *   r2 = size of argument array in bytes
         *   r3 = (managed) thread pointer
         *   [sp] = JValue* result
         *   [sp + 4] = result_in_float
         *   [sp + 8] = core register argument array
         *   [sp + 12] = fp register argument array
         *  +-------------------------+
         *  | uint32_t* fp_reg_args   |
         *  | uint32_t* core_reg_args |
         *  |   result_in_float       | <- Caller frame
         *  |   Jvalue* result        |
         *  +-------------------------+
         *  |          lr             |
         *  |          r11            |
         *  |          r9             |
         *  |          r4             | <- r11
         *  +-------------------------+
         *  | uint32_t out[n-1]       |
         *  |    :      :             |        Outs
         *  | uint32_t out[0]         |
         *  | StackRef<ArtMethod>     | <- SP  value=null
         *  +-------------------------+
         */
    

    1. r0寄存器存放被调用方法ArtMethod指针
    2. r1~r3存放前3个参数
    3. 从(sp+指针长度)地址起,按照参数顺序依次存放参数
    显而易见,只需将被调用方法ArtMethod指针与sp指针传如Hook方法即可。32位指针长度为4字节,将以int类型传入,一种返回类型对应一个Hook方法

    private static Object hookHandleObject(int targetArtMethod, int sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            return param.result;
        }
    
        private static boolean hookHandleBoolean(int targetArtMethod, int sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            if(param.result != null && param.result instanceof Boolean) {
                return ((Boolean) param.result).booleanValue();
            }
    
            return false;
        }
    
        private static byte hookHandleByte(int targetArtMethod, int sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            if(param.result != null && param.result instanceof Byte) {
                return ((Byte) param.result).byteValue();
            }
    
            return 0;
        }
    
        private static char hookHandleChar(int targetArtMethod, int sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            if(param.result != null && param.result instanceof Character) {
                return ((Character) param.result).charValue();
            }
    
            return 0;
        }
    
        private static short hookHandleShort(int targetArtMethod, int sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            if(param.result != null && param.result instanceof Short) {
                return ((Short) param.result).shortValue();
            }
    
            return 0;
        }
    
        private static int hookHandleInt(int targetArtMethod, int sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            if(param.result != null && param.result instanceof Integer) {
                return ((Integer) param.result).intValue();
            }
    
            return 0;
        }
    
        private static long hookHandleLong(int targetArtMethod, int sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            if(param.result != null && param.result instanceof Long) {
                return ((Long) param.result).longValue();
            }
    
            return 0;
        }
    
        private static float hookHandleFloat(int targetArtMethod, int sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
            
            if(param.result != null && param.result instanceof Float) {
                return ((Float) param.result).floatValue();
            }
            
            return 0;
        }
    
        private static double hookHandleDouble(int targetArtMethod, int sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
            
            if(param.result != null && param.result instanceof Double) {
                return ((Double) param.result).doubleValue();
            }
            
            return 0;
        }
    
        private static void hookHandleVoid(int targetArtMethod, int sp) {
            hookHandle(targetArtMethod,sp);
            return;
        }
    

    64位指针长度为8字节,将以long类型传入,一种返回类型对应一个Hook方法

    private static Object hookHandleObject(long targetArtMethod, long sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            return param.result;
        }
    
        private static boolean hookHandleBoolean(long targetArtMethod, long sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            if(param.result != null && param.result instanceof Boolean) {
                return ((Boolean) param.result).booleanValue();
            }
    
            return false;
        }
    
        private static byte hookHandleByte(long targetArtMethod, long sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            if(param.result != null && param.result instanceof Byte) {
                return ((Byte) param.result).byteValue();
            }
    
            return 0;
        }
    
        private static char hookHandleChar(long targetArtMethod, long sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            if(param.result != null && param.result instanceof Character) {
                return ((Character) param.result).charValue();
            }
    
            return 0;
        }
    
        private static short hookHandleShort(long targetArtMethod, long sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            if(param.result != null && param.result instanceof Short) {
                return ((Short) param.result).shortValue();
            }
    
            return 0;
        }
    
        private static int hookHandleInt(long targetArtMethod, long sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            if(param.result != null && param.result instanceof Integer) {
                return ((Integer) param.result).intValue();
            }
    
            return 0;
        }
    
        private static long hookHandleLong(long targetArtMethod, long sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
    
            if(param.result != null && param.result instanceof Long) {
                return ((Long) param.result).longValue();
            }
    
            return 0;
        }
    
        private static float hookHandleFloat(long targetArtMethod, long sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
            
            if(param.result != null && param.result instanceof Float) {
                return ((Float) param.result).floatValue();
            }
            
            return 0;
        }
    
        private static double hookHandleDouble(long targetArtMethod, long sp) {
            FastHookParam param = hookHandle(targetArtMethod,sp);
            
            if(param.result != null && param.result instanceof Double) {
                return ((Double) param.result).doubleValue();
            }
            
            return 0;
        }
    
        private static void hookHandleVoid(long targetArtMethod, long sp) {
            hookHandle(targetArtMethod,sp);
            return;
        }
    

    2.2 参数解析

    ART类型

    类型 字节
    boolean 4
    byte 4
    char 4
    short 4
    int 4
    long 8
    float 4
    double 8
    reference 4

    在ART里,除了long和double类型以8字节解析外,其余类型都以4字节解析
    Java是没有指针概念的,所以必须将传入的指针转化为Java对象,而并没有直接将指针转化为Java的接口,只能先将指针转化为JNI对象jobject,从JNI返回Java时会将jobject转化为Java对象

    • ArtMethod指针与JNI对象jobject转化接口,由JNI提供
    static jobject ToReflectedMethod(JNIEnv* env, jclass cls, jmethodID mid, jboolean isStatic)
    static jmethodID FromReflectedMethod(JNIEnv* env, jobject method)
    

    1. ToReflectedMethod将ArtMethod指针转化为JNI对象jobject
    2. FromReflectedMethod将JNI对象jobject转化为ArtMethod指针

    • 对象实际指针与JNI对象jobject转化接口,JNI不提供,需要解析相应符号
    jobject NewLocalRef(mirror::Object* obj)
    ObjPtr<mirror::Object> Thread::DecodeJObject(jobject obj)
    

    1. NewLocalRef将对象实际指针转化为JNI对象jobject,符号为_ZN3art9JNIEnvExt11NewLocalRefEPNS_6mirror6ObjectE
    2. DecodeJObject将JNI对象jobject转化为对象实际指针,符号为_ZNK3art6Thread13DecodeJObjectEP8_jobject
    按照上述规则,可以正确解析出方法参数

    private static FastHookParam parseParam(long sp, Class[] paramType, boolean isStatic) {
            FastHookParam param = new FastHookParam();
    
            int offset = 0;
            List<Object> args = new ArrayList<Object>();
    
            if(!isStatic) {
                param.receiver = getObjectParam(sp,offset);
                offset += 4;
            }
    
            if(paramType == null)
                return param;
    
            for(Class type : paramType) {
                if(type.equals(boolean.class)) {
                    boolean b = getBooleanParam(sp,offset);
                    args.add(new Boolean(b));
                    offset += 4;
                }else if(type.equals(byte.class)) {
                    byte b2 = getByteParam(sp,offset);
                    args.add(new Byte(b2));
                    offset += 4;
                }else if(type.equals(char.class)) {
                    char c = getCharParam(sp,offset);
                    args.add(new Character(c));
                    offset += 4;
                }else if(type.equals(short.class)) {
                    short s = getShortParam(sp,offset);
                    args.add(new Short(s));
                    offset += 4;
                }else if(type.equals(int.class)) {
                    int i = getIntParam(sp,offset);
                    args.add(new Integer(i));
                    offset += 4;
                }else if(type.equals(long.class)) {
                    long l = getLongParam(sp,offset);
                    args.add(new Long(l));
                    offset += 8;
                }else if(type.equals(float.class)) {
                    float f = getFloatParam(sp,offset);
                    args.add(new Float(f));
                    offset += 4;
                }else if(type.equals(double.class)) {
                    double d = getDoubleParam(sp,offset);
                    args.add(new Double(d));
                    offset += 8;
                }else if(type.equals(void.class)) {
                }else {
                    Object obj = getObjectParam(sp,offset);
                    args.add(obj);
                    offset += 4;
                }
            }
    
            if(!args.isEmpty()) {
                param.args = args.toArray(new Object[args.size()]);
            }
    
            return param;
        }
    
    jobject GetReflectedMethod(JNIEnv *env, jclass clazz, jlong art_method) {
        jobject result = (*env)->ToReflectedMethod(env,clazz,(void *)art_method,JNI_FALSE);
        return result;
    }
    
    jboolean GetBooleanParam(JNIEnv *env, jclass clazz, jlong sp, jint offset) {
        jboolean result = (jboolean)ReadInt32((unsigned char *)sp + pointer_size_ + offset);
        return result;
    }
    
    jbyte GetByteParam(JNIEnv *env, jclass clazz, jlong sp, jint offset) {
        jbyte result = (jbyte)ReadInt32((unsigned char *)sp + pointer_size_ + offset);
        return result;
    }
    
    jchar GetCharParam(JNIEnv *env, jclass clazz, jlong sp, jint offset) {
        jchar result = (jchar)ReadInt32((unsigned char *)sp + pointer_size_ + offset);
        return result;
    }
    
    jshort GetShortParam(JNIEnv *env, jclass clazz, jlong sp, jint offset) {
        jshort result = (jshort)ReadInt32((unsigned char *)sp + pointer_size_ + offset);
        return result;
    }
    
    jint GetIntParam(JNIEnv *env, jclass clazz, jlong sp, jint offset) {
        jint result = (jint)ReadInt32((unsigned char *)sp + pointer_size_ + offset);
        return result;
    }
    
    jlong GetLongParam(JNIEnv *env, jclass clazz, jlong sp, jint offset) {
        jlong result = (jlong)ReadInt64((unsigned char *)sp + pointer_size_ + offset);
        return result;
    }
    
    jfloat GetFloatParam(JNIEnv *env, jclass clazz, jlong sp, jint offset) {
        jfloat result = (jfloat)ReadFloat((unsigned char *)sp + pointer_size_ + offset);
        return result;
    }
    
    jdouble GetDoubleParam(JNIEnv *env, jclass clazz, jlong sp, jint offset) {
        jdouble result = (jdouble)ReadDouble((unsigned char *)sp + pointer_size_ + offset);
        return result;
    }
    
    jobject GetObjectParam(JNIEnv *env, jclass clazz, jlong sp, jint offset) {
        void *obj = (void *)ReadInt32((unsigned char *)sp + pointer_size_ + offset);
        jobject result = new_local_ref_(env,obj);
        return result;
    }
    

    三、创建任意方法的代理方法

    3.1 创建任意方法代理类

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)
    

    newProxyInstance为Java动态代理接口,可以明显看出Java对于动态代理的范围限制在了接口上,非接口方法不能代理
    为了实现AOP,需要想办法绕过JAVA接口限制,实现创建任意方法的代理方法的功能。

    private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
                                                     ClassLoader loader, Method[] methods,
                                                     Class<?>[][] exceptions);
    

    generateProxy进行实际代理类创建,可以接口方法最后是以Method对象的形式传入,做一个大胆的尝试,将Method[]替换为我们想要的任意方法,这样变实现了创建任意方法的代理方法的功能。如下所示:

    private static native Class<?> generateProxy(name, null,loader, methods,null);
    

    interfaces设置为null即可。

    3.2 构造方法处理

    构造方法的类型为Constructor,而不是Method,因此构造方法不能用generateProxy生成对应代理方法。需要想个办法将Constructor转为Method。
    在ART里,所有方法对对应一个ArtMethod对象,构造方法也不例外。在前面的分析可知,ArtMethod对象和Java对象是可以相互转化的,如果ArtMethod是构造方法则转化为Constructor对象,反之,则转化为Method对象
    如果想将Constructor对象转化为Method对象,可以这么做
    1. 获取Constructor对象
    2. 将其转化为ArtMethod对象
    3. 将ArtMethod设置为非构造方法,通过清除kAccConstructor标志位实现
    4. 将ArtMethod对象转化为Method对象
    5. 还原ArtMethod为构造方法

    jobject ConstructorToMethod(JNIEnv *env, jclass clazz, jobject method) {
        jobject result = NULL;
        void *art_method = (void *)(*env)->FromReflectedMethod(env, method);
    
        ClearArtMethodAccessFlag(art_method,kAccConstructor);
        result = (*env)->ToReflectedMethod(env,clazz,(void *)art_method,JNI_FALSE);
    
        return result;
    }
    
    void MethodToConstructor(JNIEnv *env, jclass clazz, jobject method) {
        void *art_method = (void *)(*env)->FromReflectedMethod(env, method);
    
        AddArtMethodAccessFlag(art_method,kAccConstructor);
    }
    

    至此,可以得到任意方法的代理方法,即Forward方法,在需要的时候反射调用即可。

    3.3 代理方法调用

    假设有一个Test类

    public class Test {
      public void test() {}
    }
    

    为其创建了代理类ProxyTest,如果需要调用原方法,只需调用ProxyTest的代理方法,并传入原方法参数。

    Method test = ProxyTest.class.getMethod("test");
    test.invoke(testObject);
    

    1. 通过反射获取代理方法test
    2. 反射调用代理方法,传入原方法参数,testObject为Test实例,即this参数
    根据FastHook原理,这里调用必须是代理方法test,即Forward方法,不然将无法实现原方法的调用。而代理方法test是public方法,反射调用的实际方法将由testObject来决定,Test也有一个test方法,因此实际调用的将是Test类的test方法而不是ProxyTest的test方法
    因此无论在什么情况下,必须保证反射调用代理方法时,调用的都是其本身,即代理方法必须是Direct方法

    方法类型 isDirect
    static true
    private true
    constructor true

    当代理方法不是构造方法时,强制将其设置为private方法,以实现静态分派代理方法

    void SetDirectMethod(JNIEnv *env, jclass clazz, jobject method) {
        void *art_method = (void *)(*env)->FromReflectedMethod(env, method);
    
        AddArtMethodAccessFlag(art_method,kAccPrivate);
    }
    

    代理方法test属于ProxyTest类,因此需要一个ProxyTest类型的实例,而现在传入的testObject是Test类型的,类型不匹配。要想办法让testObject继承与ProxyTest类
    FastHook采用一个巧妙的办法,将ProxyTest的父类置空,让ART误认为这是Object类,众所周知,任何对象都继承与Object类,完美解决类型不匹配问题

    void PoseAsObject(JNIEnv *env, jclass clazz, jclass target_class) {
        int super_class = 0;
        void *art_target_class = decode_jobject_(CurrentThread(),target_class);
        memcpy((unsigned char *)art_target_class + kClassSuperOffset,&super_class,4);
    }
    

    四、使用

    4.1 FastHookCallback

    public interface FastHookCallback {
        void beforeHookedMethod(FastHookParam param);
        void afterHookedMethod(FastHookParam param);
    }
    
    • beforeHookedMethod:原方法调用前调用
    • afterHookedMethod:原方法调用后调用

    4.2 FastHookParam

    public class FastHookParam {
        public Object receiver;
        public Object[] args;
        public Object result;
        public boolean replace;
    }
    
    • receiver:this对象,static方法则为null
    • args:方法参数
    • result:方法返回值
    • replace:是否替换方法,如果为true,则不会调用原方法,并以result返回

    4.3 接口

    /**
     *
     *@param className 目标方法类名
     *@param classLoader 目标方法所在ClassLoader,如果为null,代表当前ClassLoader
     *@param methodName 目标方法方法名
     *@param methodSig 目标方法参数签名,不包括返回类型
     *@param callback hook回调方法
     *@param mode hook模式
     *@param jitInline 是否内联,false,禁止内联;true,允许内联
     *
     */
     FastHookManager.doHook(String className, ClassLoader classLoader, String methodName, String methodSig, FastHookCallback callback, int mode, boolean jitInline)
    
    • className:目标方法类名
    • classLoader:目标方法所在ClassLoader,如果为null,代表当前ClassLoader
    • methodName:目标方法方法名
    • methodSig:目标方法参数签名,不包括返回类型
    • callback:hook回调方法
    • mode:hook模式,FastHookManager.MODE_REWRITE和FastHookManager.MODE_REPLACE
    • jitInline:是否内联,false,禁止内联;true,允许内联

    4.4 调用

    FastHookManager.doHook(className,classLoader, methodName, methodSig, new FastHookCallback() {
                @Override
                public void beforeHookedMethod(FastHookParam param) {
                    
                }
    
                @Override
                public void afterHookedMethod(FastHookParam param) {
    
                }
            },FastHookManager.MODE_REWRITE,false);
    

    五、参考

    FastHook:https://github.com/turing-technician/FastHook/tree/callback

    FastHook系列

    FastHook——一种高效稳定、简洁易用的Android Hook框架
    FastHook——远超YAHFA的优异稳定性
    如何使用FastHook免root hook微信
    FastHook——实现.dynsym段和.symtab段符号查询

    相关文章

      网友评论

        本文标题:FastHook——巧妙利用动态代理实现非侵入式AOP

        本文链接:https://www.haomeiwen.com/subject/xseebqtx.html