FastHook——巧妙利用動態代理實現非侵入式AOP

1、概述

FastHook框架要求調用者準備與原方法參數一致的Hook方法和Forward方法,這些限制將業務邏輯和Hook邏輯耦合在一塊兒。若不瞭解FastHook原理,請移步FastHook——一種高效穩定、簡潔易用的Android Hook框架git

所以可能須要一種新實現方式,其可將業務邏輯和Hook邏輯解耦。一種簡單的方案即是動態生成Hook方法和Forword方法。然而該方案對性能的影響比較大,動態生成dex文件並加載是耗時操做。github

本文將介紹一種新的思路,統一Hook方法,利用動態代理建立Forward方法。該方案比上述方案更加高效、簡潔bash

Hook方案

2、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方法ide

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方法post

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字節解析ui

Java是沒有指針概念的,因此必須將傳入的指針轉化爲Java對象,而並無直接將指針轉化爲Java的接口,只能先將指針轉化爲JNI對象jobject,從JNI返回Java時會將jobject轉化爲Java對象this

  • 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、建立任意方法的代理方法

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、使用

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);
複製代碼

5、參考

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

FastHook系列

  1. FastHook——一種高效穩定、簡潔易用的Android Hook框架
  2. FastHook——遠超其餘同類框架的優異穩定性
  3. FastHook——如何使用FastHook免root hook微信
  4. FastHook——實現.dynsym段和.symtab段符號查詢
相關文章
相關標籤/搜索