一、概述
FastHook框架要求呼叫者準備與原方法引數一致的Hook方法和Forward方法,這些限制將業務邏輯和Hook邏輯耦合在一起。若不瞭解FastHook原理,請移步FastHook——一種高效穩定、簡潔易用的Android Hook框架
因此可能需要一種新實現方式,其可將業務邏輯和Hook邏輯解耦。一種簡單的方案便是動態生成Hook方法和Forword方法。然而該方案對效能的影響比較大,動態生成dex檔案並載入是耗時操作。
本文將介紹一種新的思路,統一Hook方法,利用動態代理建立Forward方法。該方案比上述方案更加高效、簡潔。
二、Hook方法
Hook方法必須能實現以下兩點需求
- 能正確識別原方法
- 能正確解析原方法引數
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
* +-------------------------+
*/
複製程式碼
- r0暫存器存放被呼叫方法ArtMethod指標
- r1~r3存放前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)
複製程式碼
- ToReflectedMethod將ArtMethod指標轉化為JNI物件jobject
- FromReflectedMethod將JNI物件jobject轉化為ArtMethod指標
- 物件實際指標與JNI物件jobject轉化介面,JNI不提供,需要解析相應符號
jobject NewLocalRef(mirror::Object* obj)
ObjPtr<mirror::Object> Thread::DecodeJObject(jobject obj)
複製程式碼
- NewLocalRef將物件實際指標轉化為JNI物件jobject,符號為_ZN3art9JNIEnvExt11NewLocalRefEPNS_6mirror6ObjectE
- 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物件,可以這麼做
- 獲取Constructor物件
- 將其轉化為ArtMethod物件
- 將ArtMethod設定為非構造方法,通過清除kAccConstructor標誌位實現
- 將ArtMethod物件轉化為Method物件
- 還原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);
複製程式碼
- 通過反射獲取代理方法test
- 反射呼叫代理方法,傳入原方法引數,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