Hookメカニズム学習(四)-プラグインロードメカニズム

10129 ワード

weishu_ブログ

一:Classloaderロードの基本原理


基本原理:システムはClassLoaderによって必要なActivityクラスをロードし、反射呼び出しコンストラクタによってActivityオブジェクトを作成します.
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
        cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);

必要性:AndroidシステムはPathClassLoaderを使用してActivityなどのコンポーネントのロードを行う.apkがインストールされると、APKファイルのコードやリソースは、/data/app/package_nameなどの固定ディレクトリにシステムに格納されます.システムは、クラスのロードを行うときに、自動的にこのクラスを探しに行きます.しかし、システムはプラグインに存在するActivityコンポーネントの情報を知らないため、通常はプラグインのクラスをロードできません.LoakApk:LoadedApkオブジェクトは、メモリ内のAPKファイルの表示です.Apkファイルに関する情報、例えばApkファイルのコードやリソース、さらにはコード内のActivity、Serviceなどのコンポーネントの情報もこのオブジェクトから取得できます.

二:二つのロード方式


1.プラグインに対応するClassLoaderを構築してプラグインをロードする
基本原理:1まず反射呼び出しgetPackageInfoNoCheckによりLoadApkを生成し、このLoadApkに対応するClassLoaderのオブジェクトを作成し、ClassLoaderのパスをプラグインのパスに設定し、このLoadApkを早いActivity ThreadのmPackagesに保存する.これにより、Activityなどのプラグインコンポーネントを作成する際に、構築されたプラグインに対応するClassLoaderを使用してプラグインコンポーネントをロードします.2 getPackageInfoNoCheckには3つのパラメータが必要なので、まず各パラメータr.packageInfo:LoadApkを反射する必要があるので、プラグイン対応のClassLoaderを作成するには、まずプラグインLoadApkを作成します.
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
        cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);

**原理:**LoadApkのキャッシュr.packageInfoはgetPackageInfoNoCheckメソッドで取得
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
        r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);

getPackageInfoNoCheck簡単にgetPackageInfo()を呼び出しました
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
        CompatibilityInfo compatInfo) {
    return getPackageInfo(ai, compatInfo, null, false, true, false);
}

getPackageInfo:mPackagesによるLoadedApkキャッシュ
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
        ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
        boolean registerPackage) {
        //  userid 
    final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
    synchronized (mResourcesManager) {
    //  
        WeakReference ref;
        if (differentUser) {
            // Caching not supported across users
            ref = null;
        } else if (includeCode) {
            ref = mPackages.get(aInfo.packageName);
        } else {
            ref = mResourcePackages.get(aInfo.packageName);
        }

        LoadedApk packageInfo = ref != null ? ref.get() : null;
        if (packageInfo == null || (packageInfo.mResources != null
                && !packageInfo.mResources.getAssets().isUpToDate())) {
                //  , new
            packageInfo =
                new LoadedApk(this, aInfo, compatInfo, baseLoader,
                        securityViolation, includeCode &&
                        (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

        //  。。 
        return packageInfo;
    }
}

作り方:LoadApkはmPackagesでキャッシュするので、mPackagesを反射してプラグイン対応のLoadApkをmPackagesに保存することができます
ステップ1:反射Activity TheadのmPackagesを取得する
Class> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);

//   mPackages  ,  dex 
Field mPackagesField = activityThreadClass.getDeclaredField("mPackages");
mPackagesField.setAccessible(true);
Map mPackages = (Map) mPackagesField.get(currentActivityThread);

ステップ2:プラグイン対応のLoadApkを作成してmPackages 1に保存するには、publicメソッドの安定性と互換性がより良いため、getPackageInfoNoCheckではなくHook getPackageInfoNoCheckを採用します.2 getPackageInfoNoCheckは2つのパラメータを用意する必要があります:ApplicationInfo aInfo, CompatibilityInfo compatInfo第3歩:ApplicationInfo情報の準備:PackageParseを使用してAndroid manifestファイルのApplicationInfo情報を解析します.1 generateApplicationInfoによりApplicationを取得する.3つのパラメータを用意する必要があります
public static ApplicationInfo generateApplicationInfo(Package p, int flags,
   PackageUserState state)

1.1 PackageParserを構築する.Package:このクラスはPackageParserから解析したapkパケットの情報を表し、ディスク上のapkファイルのメモリ内のデータ構造表現である.したがって、このクラスを取得するには、apkファイル全体を解析する必要があります.PackageParserを使用します.parsePackage()で解析します.
//  ,  Package 
//   android.content.pm.PackageParser#parsePackage   Package 
//  PackageParser 
Object packageParser = packageParserClass.newInstance();
//   PackageParser.parsePackage  apk 
Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);

//   android.content.pm.PackageParser.Package  
Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, 0);



1.2 int flags:パラメータは解析パケットで使用するflagであり、解析のすべての情報、すなわち0を直接選択する.1.3 PackageUserStateの構築:異なるユーザーのパッケージの情報を表します.Androidはマルチタスクマルチユーザーシステムであるため、異なるユーザーが同じパッケージで異なる状態になる可能性があります.ここではパケットの情報を取得するだけなので、デフォルトを直接使用すればいいです.
/   mDefaultPackageUserState  
Object defaultPackageUserState = packageUserStateClass.newInstance();

//  !!!!!!!!!!!!!!
ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfoMethod.invoke(packageParser,
        packageObj, 0, defaultPackageUserState);
String apkPath = apkFile.getPath();
applicationInfo.sourceDir = apkPath;
applicationInfo.publicSourceDir = apkPath;

ステップ3:ClassLoader 1を置き換えgetPackageInfoNoCheckを呼び出してLoadedApkを取得する
// android.content.res.CompatibilityInfo
Class> compatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");
Method getPackageInfoNoCheckMethod = activityThreadClass.getDeclaredMethod("getPackageInfoNoCheck", ApplicationInfo.class, compatibilityInfoClass);

Field defaultCompatibilityInfoField = compatibilityInfoClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");
defaultCompatibilityInfoField.setAccessible(true);

Object defaultCompatibilityInfo = defaultCompatibilityInfoField.get(null);
ApplicationInfo applicationInfo = generateApplicationInfo(apkFile);

Object loadedApk = getPackageInfoNoCheckMethod.invoke(currentActivityThread, applicationInfo, defaultCompatibilityInfo);


2 LoadApkのClassLoaderを置き換え、Activity ThreadのmPackagesに追加します.
2.ホストClassloaderプラグインのパスを伝え、ホストClassloaderを使用してロードする
基本原理:1インストールされたApkはPathClassLoaderを使用してdata/packageディレクトリの下のクラスをロードし、PathClassLoaderはBaseDexClassLoaderに継承され、BaseDexClassLoaderはfindClass()スキームによってクラスをロードし、findClass()はpathListを呼び出す.findClass(). 2 DexPathList:DexElementsでBaseDexClassLoaderをロードする.findClass();
protected Class> findClass(String name) throws ClassNotFoundException {
    List suppressedExceptions = new ArrayList();
    Class c = pathList.findClass(name, suppressedExceptions);
    if (c == null) {
        ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
        for (Throwable t : suppressedExceptions) {
            cnfe.addSuppressed(t);
        }
        throw cnfe;
    }
    return c;
}

DexPathList.findClass
public Class findClass(String name, List suppressed) {
   for (Element element : dexElements) {
       DexFile dex = element.dexFile;

       if (dex != null) {
           Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
           if (clazz != null) {
               return clazz;
           }
       }
   }
   if (dexElementsSuppressedExceptions != null) {
       suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
   }
   return null;
}


3プラグインの情報をdexElementsに保存する:
public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
        throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
    //   BaseDexClassLoader : pathList
    Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList");
    pathListField.setAccessible(true);
    Object pathListObj = pathListField.get(cl);

    //   PathList: Element[] dexElements
    Field dexElementArray = pathListObj.getClass().getDeclaredField("dexElements");
    dexElementArray.setAccessible(true);
    Object[] dexElements = (Object[]) dexElementArray.get(pathListObj);

    // Element  
    Class> elementClass = dexElements.getClass().getComponentType();

    //  ,  
    Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);

    //  Element(File file, boolean isDirectory, File zip, DexFile dexFile)  
    Constructor> constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);
    Object o = constructor.newInstance(apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0));

    Object[] toAddElementArray = new Object[] { o };
    //  elements 
    System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
    //  element 
    System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);

    //  
    dexElementArray.set(pathListObj, newElements);

}

2つのロードスキームの比較


シナリオ1:ClassLoaderの利点を構築する:マルチClassLoaderメカニズム、各プラグインには対応するClassLoaderがあり、隔離性がよく、例えば2つの異なるプラグインが2つのライブラリの異なるバージョンを使用すると、衝突は発生しません.欠点:互換性が悪く、実現過程が複雑である.シナリオ2:パッチ・シナリオの利点:単純な欠点を実現する:単一ClassLoaderシナリオ、異なるプラグインはPathClassLoaderでロードされ、プラグイン間でプラグインとホスト間で使用されるクラス・ライブラリが衝突すると、タイプの衝突の結果が発生します.