Xposed学习二:实现机制
阅读原文时间:2023年07月09日阅读:2

在上一篇我们学习了如何在AS中创建Xposed模块,本篇来分析下官方教程中redClock的实现原理。本系列文章基于version-51

public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
if(!lpparam.packageName.equals("com.android.systemui")) return;
XposedBridge.log("we are in systemui !");

    findAndHookMethod("com.android.systemui.statusbar.policy.Clock", lpparam.classLoader, "updateClock", new XC\_MethodHook() {  
        @Override  
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {  
            super.beforeHookedMethod(param);  
        }

        @Override  
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {  
            TextView tv = (TextView) param.thisObject;  
            String text = tv.getText().toString();  
            tv.setText(text + ")");  
            tv.setTextColor(Color.RED);  
        }  
    });  
}

上面的代码可以将原先在状态栏的时钟文本颜色变成红色,且在后面加")"。看下图:

主要的实现代码在findAndHookMethod函数中,查看函数定义:

findAndHookMethod:

——>findMethodExact(clazz,methodName,paramterClasses);

——>XposedBridge.hookMethod(method,callback);

先看findMethodExact,

代码很简单就是要得到methodName在android中对应的函数对象,根据findAndHookMethod的参数得到字符串sb(格式参考注释行),用sb在methodCache这个hashMap查找有没有对应的method;若没有则根据methodName和parameterTypes利用getDeclaredMethod得到对应的method。

public static Method findMethodExact(Class clazz, String methodName, Class… parameterTypes) {
StringBuilder sb = new StringBuilder(clazz.getName());
sb.append('#');
sb.append(methodName);
sb.append(getParametersString(parameterTypes));
sb.append("#exact");
// sb = com.android.systemui.statusbar.policy.Clock#updateClock(参数1,参数2)#exact

    String fullMethodName = sb.toString();  
    Method e;  
//methodCache键值对存放fullMethodName,method对象  
    if(methodCache.containsKey(fullMethodName)) {  
        e = (Method)methodCache.get(fullMethodName);  
        if(e == null) {  
            throw new NoSuchMethodError(fullMethodName);  
        } else {  
            return e;  
        }  
    } else {  
        try {  
            e = clazz.getDeclaredMethod(methodName, parameterTypes);  
            e.setAccessible(true);  
            methodCache.put(fullMethodName, e);  
            return e;  
        } catch (NoSuchMethodException var6) {  
            methodCache.put(fullMethodName, (Object)null);  
            throw new NoSuchMethodError(fullMethodName);  
        }  
    }  
}

  看来重点在XposedBridge.hookMethod:

public static Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
if(!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor)) {
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
} else if(hookMethod.getDeclaringClass().isInterface()) {
throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString());
} else if(Modifier.isAbstract(hookMethod.getModifiers())) {
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
} else {
//上面代码分步检查hookMethod的类型
//else中的代码得到hookMethod对应的键值对
boolean newMethod = false;
Map declaringClass = sHookedMethodCallbacks;
XposedBridge.CopyOnWriteSortedSet callbacks;
synchronized(sHookedMethodCallbacks) {
callbacks = (XposedBridge.CopyOnWriteSortedSet)sHookedMethodCallbacks.get(hookMethod);
if(callbacks == null) {
callbacks = new XposedBridge.CopyOnWriteSortedSet();
sHookedMethodCallbacks.put(hookMethod, callbacks);
newMethod = true;
}
}

        callbacks.add(callback);  
//替换hookMehod的callbacks为callback,其实callback是存放的是hookMethod所有的callback,看定义:</span></span>  
final XposedBridge.CopyOnWriteSortedSet<XC\_MethodHook> callbacks;  
//以上代码就是为hookMethod建立对应的callback list  
//sHookedMethodCallbacks存放hookMethod和callback

        if(newMethod) {  
            Class declaringClass1 = hookMethod.getDeclaringClass();  
            int slot = XposedHelpers.getIntField(hookMethod, "slot");  
            Class\[\] parameterTypes;  
            Class returnType;  
            if(hookMethod instanceof Method) {  
                parameterTypes = ((Method)hookMethod).getParameterTypes();  
                returnType = ((Method)hookMethod).getReturnType();  
            } else {  
                parameterTypes = ((Constructor)hookMethod).getParameterTypes();  
                returnType = null;  
            }  
    //以上代码得到method的参数和返回值,在AdditionalHookInfo下使用  
    //把callback、method参数、method返回值汇总在AdditionalHookInfo类下  
            XposedBridge.AdditionalHookInfo additionalInfo = new XposedBridge.AdditionalHookInfo(callbacks, parameterTypes, returnType, (XposedBridge.AdditionalHookInfo)null);  
           //本地函数在libxposed\_dalvik.cpp  

       hookMethodNative(hookMethod, declaringClass1, slot, additionalInfo);
}

        callback.getClass();  
        return new Unhook(callback, hookMethod);  
//为callback绑定hookMthod  
    }  
}

上面乱七八糟的走了这么多,登记了2个hashmap{(fullMethodName,method对象),(hookmethod,callback)}。看来java层只是管理这些结构并没有实质性的操作,进入native代码----Xposed.cpp:

//参数:reflectedMethodIndirect==>hookmethod,declaredClassIndirect==>hookmethod所在的类
// slot==>slot,additionalInfoIndirect==>结构体包含callback、parameterTypes、returnType
void XposedBridge_hookMethodNative(JNIEnv* env, jclass clazz, jobject reflectedMethodIndirect,
jobject declaredClassIndirect, jint slot, jobject additionalInfoIndirect) {
// Usage errors?
if (declaredClassIndirect == NULL || reflectedMethodIndirect == NULL) {
dvmThrowIllegalArgumentException("method and declaredClass must not be null");
return;
}

// Find the internal representation of the method  
//获得dalvik中的classObject对象  
ClassObject\* declaredClass = (ClassObject\*) dvmDecodeIndirectRef(dvmThreadSelf(), declaredClassIndirect);  
//获得dalvik中的Method,被dalvik执行的函数体不同于java层的Method,位于Object.h  
Method\* method = dvmSlotToMethod(declaredClass, slot);  
if (method == NULL) {  
    dvmThrowNoSuchMethodError("Could not get internal representation for method");  
    return;  
}  
//若method已被hook则直接返回  
if (isMethodHooked(method)) {  
    // already hooked  
    return;  
}

// Save a copy of the original method and other hook info  
XposedHookInfo\* hookInfo = (XposedHookInfo\*) calloc(1, sizeof(XposedHookInfo));  
memcpy(hookInfo, method, sizeof(hookInfo->originalMethodStruct));  
hookInfo->reflectedMethod = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(reflectedMethodIndirect));  
hookInfo->additionalInfo = dvmDecodeIndirectRef(dvmThreadSelf(), env->NewGlobalRef(additionalInfoIndirect));

// Replace method with our own code  将method替换成我们自己的代码  
//设置method->accessFlags = ACC\_NATIVE;表示method为native代码  
//下面几行代码都是为这行代码作补充  
//For a native method, we compute the size of the argument list,  
//and set "insSize" and "registerSize" equal to it.  
SET\_METHOD\_FLAG(method, ACC\_NATIVE);  
//给method添加callback函数表示已被hooked  
method->nativeFunc = &hookedMethodCallback;  
//原本的insns中存放的是dex指令,现变为hookinfo为hookedMethodCallback准备  
method->insns = (const u2\*) hookInfo;  
method->registersSize = method->insSize;  
method->outsSize = 0;

if (PTR\_gDvmJit != NULL) {  
    // reset JIT cache  
    char currentValue = \*((char\*)PTR\_gDvmJit + MEMBER\_OFFSET\_VAR(DvmJitGlobals,codeCacheFull));  
    if (currentValue == 0 || currentValue == 1) {  
        MEMBER\_VAL(PTR\_gDvmJit, DvmJitGlobals, codeCacheFull) = true;  
    } else {  
        ALOGE("Unexpected current value for codeCacheFull: %d", currentValue);

代码主要的就是红色标注的,它将Method标为native code。dalvik虚拟机在执行Method时,则会直接调用其成员变量hookedMethodCallback执行。注意,这个时候已经改变了原本的Method的执行步骤了(Xposed在此刻觉醒啦啦啦)。看下面dalvik代码,/dalvik/vm/interp/Stack.c

void dvmCallMethodV(Thread* self, const Method* method, Object* obj,
bool fromJni, JValue* pResult, va_list args)
{
……

if (dvmIsNativeMethod(method)) {  
    TRACE\_METHOD\_ENTER(self, method);  
    /\*  
     \* Because we leave no space for local variables, "curFrame" points  
     \* directly at the method arguments.  
     \*/  
    (\*method->nativeFunc)(self->curFrame, pResult, method, self);  
    TRACE\_METHOD\_EXIT(self, method);  
} else {</span>  
//这里是在Inter.cpp中直接解析method  
    dvmInterpret(self, method, pResult);  
}

......  
}

   这一路走来感觉有点偏离主线类,回看下主题,在执行完 findAndHookMethod("com.android.systemui.statusbar.policy.Clock", lpparam.classLoader,"updateClock", new XC_MethodHook() );后。updateClock(hookmethod)的accessflag = ACC_NATIVE,dalvik在执行updateClock方法时发现其为native code,则执行nativeFunc 函数体即hookedMethodCallback。ok,找到"元凶"了,继续看代码:

/* This is called when a hooked method is executed. */
void hookedMethodCallback(const u4* args, JValue* pResult, const Method* method, ::Thread* self) {
……
//call the Java handler function
JValue result;

dvmCallMethod(self, xposedHandleHookedMethod, NULL, &result,  
    originalReflected, (int) original, additionalInfo, thisObject, argsArray);  
......  

}

hookedMethodCallback函数中主要是调用dvmCallMethod去执行xposedHandleHookedMethod,而xposedHandleHookedMethod是classXposedBridge里的handleHookedMethod方法。ok,重头戏来了

private static Object handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj, Object thisObject, Object[] args) throws Throwable {
XposedBridge.AdditionalHookInfo additionalInfo = (XposedBridge.AdditionalHookInfo)additionalInfoObj;
if(disableHooks) {
try {
//hook不使能,执行原method
return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes, additionalInfo.returnType, thisObject, args);
……
}
} else {
/得到hookmethod的callback,在之前的XposedBridge.hookMethod中为callbacks添加了callback
Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot();
int callbacksLength = callbacksSnapshot.length;
if(callbacksLength == 0) {
//hookmethod的callback为空,hooked无意义,执行原method
try {
return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes, additionalInfo.returnType, thisObject, args);
……
}
} else {
……
do {
label65: {
try {
((XC_MethodHook)callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
} catch (Throwable var18) {
log(var18);
param.setResult((Object)null);
param.returnEarly = false;
break label65;
}

                    if(param.returnEarly) {  
                        ++beforeIdx;  
                        break;  
                    }  
                }

                ++beforeIdx;  
    //hookmethod有几个callback就循环几次  
            } while(beforeIdx < callbacksLength);

            if(!param.returnEarly) {  
                try {  
        //在beforeHookedMethod后执行原method  
                    param.setResult(invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));  
                } catch (InvocationTargetException var16) {  
                    param.setThrowable(var16.getCause());  
                }  
            }

            int afterIdx = beforeIdx - 1;

            do {  
                Object lastResult = param.getResult();  
                Throwable lastThrowable = param.getThrowable();

                try {  
                    ((XC\_MethodHook)callbacksSnapshot\[afterIdx\]).afterHookedMethod(param);  
                } catch (Throwable var17) {  
                    log(var17);  
                    if(lastThrowable == null) {  
                        param.setResult(lastResult);  
                    } else {  
                        param.setThrowable(lastThrowable);  
                    }  
                }

                --afterIdx;  
            } while(afterIdx >= 0);  
            ......  
        }  
    }  
}

别看handleHookedMethod代码老长了,其实它很单纯。

第一步:是否需要执行callback,否则直接执行原method,gameover;

第二步:执行callbacks里的beforeHookedMethod方法,有几个callback执行几次beforeHookedMethod;

第三步:执行原method;

第四步:执行callbacks里的afterHookedMethod方法,类同beforeHookedMethod。

需要注意的是如果method有多个callback,其beforeHookedMethod和afterHookedMethod执行顺序:

A1.before->A2.before->原method->A2.after->A1.after,也是蛮符合客观规律的嘛。

好,关于findAndHookMethod()函数也算是从上倒下看了个遍,但你上面添这么多代码是算怎么回事呢?下面就简单总结下罗:

好了,本篇就先这样吧,太长了也不好看。下篇再分析下其余枝节:

1 handleLoadPackage 怎么生效

2 dalvik层如何回到java层(数据类型如何回传)

3 XC_MethodHook中会执行原来的java函数体,如何执行;

其他想到再分析啰,大家也可以对上述执行流程提问,我们一起探讨。

参考资料:

1 Xposed框架Java部分

2 Dalvik虚拟机的运行过程分析

版权声明:本文为博主原创文章,未经博主允许不得转载。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章