Androidプラグイン化の原理

5928 ワード

Activity起動プロセス:
1.startActivityの場合は最終的にAMSのstartActivityメソッドに進みます.
2.システムは、このActivityが合法かどうかを検証する情報の山をチェックします.
3.Activity ThreadのHandlerのhandleLaunchActivityにコールバックします.
4.ここでperformLaunchActivityメソッドを使用してActivityを作成し、一連のライフサイクルをコールバックする方法について説明します.
5.Activityを作成するとLoaderApkオブジェクトが作成され、そのオブジェクトのgetClassLoaderを使用してActivityが作成されます.
6.getClassLoader()メソッドを参照すると、PathClassLoaderが返され、BaseDexClassLoaderから継承されていることがわかります.
7.次に、BaseDexClassLoaderが作成時にDexPathListタイプのpathListオブジェクトを作成したことを確認します.
そしてfindClassでpathListを呼び出す.findClassの方法.
8.次にDexPathListクラスのfindClassを見てみると、彼の内部にはElement[]dexElementsのdex配列が維持されていることがわかります.
findClassの場合は配列から遍歴して検索されます.
 
プラグインの実装手順:
1.まず、DexClassloaderで独自のDexClassloaderオブジェクトを作成してプラグインapkをロードします.
//dex 
String cachePath = MainActivity.this.getCacheDir().getAbsolutePath();

// apk 
String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/chajian.apk";

// ClassLoader, DexClassLoader
DexClassLoader mClassLoader = new DexClassLoader(apkPath, cachePath,cachePath, getClassLoader());   

2.ホストapkのClassLoaderのpathListオブジェクトとうちのClassloaderのpathListを取得します.最終ロード時にpathListが実行するためです.findClassメソッド.
// ClassLoader
PathClassLoader pathLoader = (PathClassLoader) MyApplication.getContext().getClassLoader();

// pathList
Object suZhuPathList = getPathList(pathLoader);

// pathList
Object chaJianPathList = getPathList(loader);

3.次に、ホストpathListオブジェクトのElement[]と作成したClassloaderのElement[]を取得します.
// 
Object suzhuElements = getDexElements(suZhuPathList)

// 
Object chajianElements = getDexElements(chaJianPathList)

4.dexファイルを追加するため、元の配列の長さが増加し、新しいElementタイプの配列を新規作成します.長さは新しいものと古いものです.
// 
Class> localClass = suzhu.getClass().getComponentType();

// 
int i = Array.getLength(suzhu);

// 
int j = i + Array.getLength(chajian);

// 
Object result = Array.newInstance(localClass, j);       

5.プラグインdexファイルとホストの元のdexファイルを新しい配列に入れてマージします.
// dex 
for (int k = 0; k < j; ++k) {
    if (k < i) {
        Array.set(result, k, Array.get(suzhu, k));
    } else {
        Array.set(result, k, Array.get(chajian, k - i));
    }
}

6.我々の新しい配列をpathListオブジェクトに設定
setField(suZhuPathList, suZhuPathList.getClass(), "dexElements", result);

7.エージェントシステムがActivityを起動する方法を起動し、起動するActivityを私たちのピットを占めるActivityに置き換えて、システムをだまして検査する目的を達成しました.
// ActivityManagerNative 
Class> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");

// gDefault 
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");

gDefaultField.setAccessible(true);

// gDefault 
Object gDefault = gDefaultField.get(null);

// gDefault  android.util.Singleton ;  
Class> singleton = Class.forName("android.util.Singleton");

// gDefault Singleton , Singleton AMS 
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
//ams 
Object rawIActivityManager = mInstanceField.get(gDefault);

今、私たちはすでにこのamsのエージェントオブジェクトを手に入れました.今、私たちは自分のエージェントオブジェクトを作成して元のamsをブロックする方法が必要です.
class IActivityManagerHandler implements InvocationHandler {
    ...

     @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if ("startActivity".equals(method.getName())) {
            Log.e("Main","startActivity ");

            //  Intent  
            Intent raw;
            int index = 0;

            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            raw = (Intent) args[index];
            // Intent
            Intent newIntent = new Intent();
            //  Activity ,  " "
            String stubPackage = MyApplication.getContext().getPackageName();

            //  Activity  ZhanKengActivitiy
            ComponentName componentName = new ComponentName(stubPackage, ZhanKengActivitiy.class.getName());
            newIntent.setComponent(componentName);

            //  TargetActivity 
            newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, raw);

            //  Intent,  AMS 
            args[index] = newIntent;
            Log.e("Main","startActivity  hook  ");
            Log.e("Main","args[index] hook = " + args[index]);
            return method.invoke(mBase, args);
        }

        return method.invoke(mBase, args);
    }
}

次に、動的エージェントを使用して、上で取得したamsをエージェントします.
//  ,  ,  , ,

// , ams IActivityManager
Class> iActivityManagerInterface = Class.forName("android.app.IActivityManager");

// ,IActivityManagerHandler , demo
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
       new Class>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));

// singleton 
mInstanceField.set(gDefault, proxy);

8.システムチェックが完了した後、再度エージェントブロックシステムがActivityを作成する方法で、元の私たちが交換したActivityを再び交換し、AndroidManifestに登録しない目的に達しました.
try {
    //  
    Field intent = obj.getClass().getDeclaredField("intent");
    intent.setAccessible(true);
    Intent raw = (Intent) intent.get(obj);

    Intent target = raw.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
    raw.setComponent(target.getComponent());
    Log.e("Main","target = " + target);
} catch (Exception e) {
    throw new RuntimeException("hook launch activity failed", e);
}

9.最後に私たちが前に書いたコードを呼び出すには、早ければ早いほどアプリケーションで呼び出すのもいいし、ActivityのattachBaseContextメソッドでもいいです.Demo:https://github.com/suyimin/Plugin