Android個人項目插件化總結——方式一(Hook IActivityManager)

文章目錄

整體流程

本篇主要基於Hook代理對象IActivityManager,Activity啓動流程不清楚的可以看看我分析Activity流程的文章。

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
    ...
    int result = ActivityManagerNative.getDefault()
        .startActivity(whoThread, who.getBasePackageName(), intent,
                intent.resolveTypeIfNeeded(who.getContentResolver()),
                token, target != null ? target.mEmbeddedID : null,
                requestCode, 0, null, options);
    checkStartActivityResult(result, intent);
    ...
}  

啓動Activity交給了代理對象ActivityManagerNative,他接着會調用AMS啓動Activity。

下面checkStartActivityResult(result, intent);是對result和intent做一個檢測,也就是看要啓動的intent是不是合法。

所以我們需要hook這個ActivityManagerNative的startActivity方法。

我們需要提前創建一個佔坑的Activity,用於欺騙系統。

在這個方法裏面將intent的信息替換成佔坑的Activity信息。剩下流程等就交給AMS,因爲我們啓動的Activity是合法的,所以不會出現問題。

根據activity的啓動流程我們也可以知道,到最後啓動activity的準備工作做完後,會調用ApplicationThread的scheduleLaunchActivity方法會給主線程發送一個message。

private class H extends Handler {
    ...
    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case LAUNCH_ACTIVITY: {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
                r.packageInfo = getPackageInfoNoCheck(
                        r.activityInfo.applicationInfo, r.compatInfo);
                handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;
            ...
    }
}  

通過handleLaunchActivity方法就正式創建啓動Activity了。

在這裏就又要藉助Handler的消息處理機制,將我們的Intent替換回來了。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
} 

handler最先調用message的CallBack,然後會調用自己的mCallBack,最後才調用handleMessage(msg)。

H類中實現了handleMessage方法,並沒有實現mCallBack,所以我們需要通過反射創建我們的mCallback 並賦值給H,這樣我們就可以在他處理消息之前做一些我們的處理,也就是將Intent替換回來。

還有一個問題,就是系統如何才能從我們提供的APK中加載class文件呢?

這裏我們就要使用ClassLoader了。

這裏直接給出結論,Android裏面PathClassLoader用於加載系統和主dex文件,DexClassLoader用於其他Dex文件。

所以我們只要創建我們的ClassLoader,加載我們的apk。然後通過反射將最後的結果合併至PathClassLoader裏面就可以。

基本流程就是如此

接着我們看一下實現

實現

首先創建我們的classloader

    private void hook() throws IllegalAccessException, NoSuchFieldException,
            ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
        String cachePath = getCacheDir().getAbsolutePath();
        String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/chajian" +
                ".apk";
        DexClassLoader dexClassLoader = new DexClassLoader(apkPath, cachePath, cachePath,
                getClassLoader());
        RejectPluginHelper.loadPlugin(dexClassLoader, getApplicationContext());
        HookHelper.hookActivityManager(this);
        HookHelper.hookActivityThread();
    }

在loadPlugin(dexClassLoader, getApplicationContext());裏面,我們合併兩個dex的list

    //加載插件apk
    public static void loadPlugin(DexClassLoader dexClassLoader, Context applicationContext) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

        //獲取PathClassLoader
        PathClassLoader pathClassLoader = (PathClassLoader) applicationContext.getClassLoader();

        //分別獲取宿主和插件的PathList
        Object suzhuPathList = getPathList(pathClassLoader);
        Object chajianPathList = getPathList(dexClassLoader);

        //獲得PathList的element併合並
        Object newElements = mergeElements(getElements(suzhuPathList),
                getElements(chajianPathList));
        Log.d("Lpp", "newElements: " + Array.getLength(newElements));

        //重新設置給宿主的dexElement
        Field field = suzhuPathList.getClass().getDeclaredField("dexElements");
        field.setAccessible(true);
        field.set(suzhuPathList, newElements);
    }

    private static Object mergeElements(Object elements2, Object elements1) {
        Log.d("Lpp", "suzhuPathList: " + Array.getLength(elements1));
        Log.d("Lpp", "chajianPathList: " + Array.getLength(elements2));
        int len1 = Array.getLength(elements1);
        int len2 = Array.getLength(elements2);
        Object newArr = Array.newInstance(elements1.getClass().getComponentType(), len1 + len2);
        for (int i = 0; i < len1; i++) {
            Array.set(newArr, i, Array.get(elements1, i));
        }
        for (int i = len1; i < len1 + len2; i++) {
            Array.set(newArr, i, Array.get(elements2, i - len1));
        }
        return newArr;
    }

    // 獲取DexPathList 中的dexElements
    private static Object getElements(Object suzhuPathList) throws NoSuchFieldException,
            IllegalAccessException {
        Class cl = suzhuPathList.getClass();
        Field field = cl.getDeclaredField("dexElements");
        field.setAccessible(true);
        return field.get(suzhuPathList);
    }

    // 獲取DexPathList
    private static Object getPathList(Object loader) throws ClassNotFoundException,
            NoSuchFieldException, IllegalAccessException {
        Class cl = Class.forName("dalvik.system.BaseDexClassLoader");
        Field field = cl.getDeclaredField("pathList");
        field.setAccessible(true);
        return field.get(loader);
    }

然後通過

    HookHelper.hookActivityManager(this);
    HookHelper.hookActivityThread();

分別hook ActivityManager 和 Handler的CallBack

對Hook不熟悉的得先了解了解hook。

    public static void hookActivityManager(Context context) throws ClassNotFoundException, NoSuchFieldException,
            IllegalAccessException {

        //得到ActivityManagerNative的class對象
        Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
        //得到他的gDefault字段,他是Singleton類型
        Field field = activityManagerNativeClass.getDeclaredField("gDefault");
        field.setAccessible(true);
        //靜態直接傳null即可
        Object gDefaultObj = field.get(null);

        //得到Singleton class
        Class singletonCLass = Class.forName("android.util.Singleton");
        //得到他的成員mInstance 他是IActivityManager
        Field mInstanceField = singletonCLass.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);
        //得到gDefault的 mInstance
        Object activityManagerObj = mInstanceField.get(gDefaultObj);

        //給IActivityManager設置代理
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[]{Class.forName("android.app.IActivityManager")},
                new IActivityManagerProxy(activityManagerObj, context));

        //將代理對象設置給gDefault
        mInstanceField.set(gDefaultObj, proxy);
        Log.d("Lpp", "已經替換掉了Intent");
    }

代理類

/**
 * @author liupeidong
 * Created on 2019/10/9 11:08
 */
public class IActivityManagerProxy implements InvocationHandler {

    private Context context;

    private Object obj;

    public IActivityManagerProxy(Object obj, Context context) {
        this.context = context;
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("startActivity")) {
            //獲得傳入的Intent
            Intent targetIntent = null;
            int index;

            for (index = 0; index < args.length; index++) {
                if (args[index] instanceof Intent) {
                    targetIntent = (Intent) args[index];
                    break;
                }
            }

            //創建假Intent
            Intent fakeIntent = new Intent(context, FakeActivity.class);
            //把真的先存裏面
            fakeIntent.putExtra("targetIntent", targetIntent);

            //設置到參數裏面
            args[index] = fakeIntent;
            return method.invoke(obj, args);
        }
        return method.invoke(obj, args);
    }
}

主要做的任務就是Intent替換,然後反射賦值代理類。

接着通過反射,給H賦值我們封裝的CallBack

    public static void hookActivityThread() throws ClassNotFoundException, NoSuchMethodException,
            InvocationTargetException, IllegalAccessException, NoSuchFieldException {

        //先得到ActivityThread對象,他有一個返回自己本身的方法
        Class activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(
                "currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
        Object activityThreadObj = currentActivityThreadMethod.invoke(null);

        //得到他的成員 mH
        Field mHField = activityThreadClass.getDeclaredField("mH");
        mHField.setAccessible(true);
        Object handleObj = mHField.get(activityThreadObj);

        //給mH設置mCallback
        Field callBackField = Handler.class.getDeclaredField("mCallback");
        callBackField.setAccessible(true);
        ChangeCallBack changeCallBack = new ChangeCallBack((android.os.Handler) handleObj);
        Log.d("Lpp", "hookActivityThread 1: " + changeCallBack);
        callBackField.set(handleObj, changeCallBack);
        Log.d("Lpp", "替換回Intent");

        Class activityThreadClass2 = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod2 = activityThreadClass2.getDeclaredMethod(
                "currentActivityThread");
        currentActivityThreadMethod2.setAccessible(true);
        Object activityThreadObj2 = currentActivityThreadMethod2.invoke(null);

        //得到他的成員 mH
        Field mHField2 = activityThreadClass2.getDeclaredField("mH");
        mHField2.setAccessible(true);
        Object handleObj2 = mHField2.get(activityThreadObj2);

        //給mH設置mCallback
        Field callBackField2 = Handler.class.getDeclaredField("mCallback");
        callBackField2.setAccessible(true);
        Log.d("Lpp", "hookActivityThread 2: " + callBackField2.get(handleObj2));

    }

我們的CallBack

/**
 * @author liupeidong
 * Created on 2019/10/9 13:05
 */
public class ChangeCallBack implements Handler.Callback {

    private Handler handler;

    public ChangeCallBack(Handler handler) {
        this.handler = handler;
    }

    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case 100:
                try {
                    handleLaunchActivity(msg);
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                break;
        }
        handler.handleMessage(msg);
        return true;
    }

    private void handleLaunchActivity(Message msg) throws NoSuchFieldException,
            IllegalAccessException {
        Object obj = msg.obj;

        Field intent = obj.getClass().getDeclaredField("intent");
        intent.setAccessible(true);
        Intent fakeIntent = (Intent) intent.get(obj);
        Intent targetIntent = fakeIntent.getParcelableExtra("targetIntent");
        fakeIntent.setComponent(targetIntent.getComponent());
//        intent.set(obj, targetIntent);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章