Such as starting an activity in an external apk

Understanding the past, knowing that the coming can be pursued

background

In the last article How to start an activity that is not registered in AndroidManifest It briefly describes how to start an activity that is not declared in AndroidManifest.xml by bypassing the check of ActivityManagerSerivce(AMS).It doesn't make much sense to start an activity written inside an application, however. To dynamically post a new activity, you must find a way to get the activity from outside and start it.

A brief introduction to ClassLoader

Everyone who knows Java must know that Java loads classes by using ClassLoader. First, look at what CLassLoader did when it was initialized.createSystemClassLoader() generates a ClassLoader when initialized.

static private class SystemClassLoader {
    public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}

An instance of PathClassLoader() is returned in the createSystemClassLoader() method. From the construction method, you can see that the parent passed in by the PathClassLoader object is an instance of BootClassLoader.

private static ClassLoader createSystemClassLoader() {
    String classPath = System.getProperty("java.class.path", ".");
    String librarySearchPath = System.getProperty("java.library.path", "");
    return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}

The implementation of pathClassLoader is simple, inheriting BaseDexClassLoader with the following code

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    
    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
}

Here PathClassLoader is used to load the installed apk file.There is another kind of ClassLoader in android, DexClassLoader, which is mainly used to load external APK or jar files.The DexClassLoader implementation is simple, with the following code.

public class DexClassLoader extends BaseDexClassLoader {
  
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

You can see that PathClassLoader and DexClassLoader both inherit BaseDexClassLoader, whose main code is as follows.

public class BaseDexClassLoader extends ClassLoader {
    
    ...
    
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.originalPath = dexPath;
        this.pathList =
            new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = pathList.findClass(name);
        if (clazz == null) {
            throw new ClassNotFoundException(name);
        }
        return clazz;
    }
    
    ...
}

In the construction method, dex's lassLoader, path, and so on are stored in the DexPathList.findClass first finds the class from the pathList.
After learning about these ClassLoaders, let's see how ClassLoader works.The loadClass method code is as follows

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
    Class<?> clazz = findLoadedClass(className);
    if (clazz == null) {
        try {
            clazz = parent.loadClass(className, false);
        } catch (ClassNotFoundException e) {
            
        }
        if (clazz == null) {
            clazz = findClass(className);
        }
    }
    return clazz;
}

You can see from the code that the loadClass is first found in the BootClassLoader, if it is not found then in the parent's lassLoader, if it is not already found in the current ClassLoader.The process is illustrated below


Let's take another look at the dependencies of the ClassLoader above. You can see from the code that PathClassLoader's parent is a BootClassLoader. Here's a code to verify it.

ClassLoader classLoader = getClassLoader();
        Log.d(TAG, "ClassLoader: " + (classLoader == null ? " null" : classLoader.toString()));

ClassLoader parentClassLoader = classLoader.getParent();
Log.d(TAG, "parentClassLoader: " +  (parentClassLoader == null ? "null" : parentClassLoader.toString()));

ClassLoader pParentClassLoader = parentClassLoader.getParent();
Log.d(TAG, "parent's parentClassLoader: " +  (pParentClassLoader == null ? "null" : pParentClassLoader.toString()));
    

After running this code, the result is as follows

ClassLoader: dalvik.system.PathClassLoader

parentClassLoader: java.lang.BootClassLoader@de975c2

parent's parentClassLoader: null

The current class Loader is PathClassLoader, Parent's lassLoader is BootClassLoader, and BootClassLoader has no Parent's lassLoader, and verifies the conclusion of the previous code.This is the parent delegation mechanism in Java, which looks up classes from parent's lassLoader first, from top to bottom.For example, we have written a class java.lang.String, which has the same name as the String package name class in Java. When a virtual machine calls a String object, it does not call our custom class. Instead, it prefers String in Java. Interested students can write their own code to try it out.

So the problem is that, as mentioned above, DexClassLoader is required to load classes of external apks. From the above results, it can be seen that the process of running app has nothing to do with DexClassLoader, and the parent delegation mechanism has nothing to do with DexClassLoader. So how do I load classes of external apks?Then look down.

How to load classes of external apk s

The parent delegation mechanism mentioned above, and associating one of the parameters in ClassLoader's construction method with parent, is there a way to replace PathClassLoader's parent with the DexClassLoader we want, set DexClassLoader's parent to BootClassLoader, and add the mechanism of parent delegation, the process of finding classes becomes BootClassLoader->DexClassLoader->PathClassLoader->PathClassLOader, so that you can load the classes of the external apk?Let's try it out next

public static void loadApk(Context context, String apkPath) {
    File dexFile = context.getDir("dex", Context.MODE_PRIVATE);

    File apkFile = new File(apkPath);

    ClassLoader classLoader = context.getClassLoader();
    DexClassLoader dexClassLoader = new DexClassLoader(apkFile.getAbsolutePath(),
            dexFile.getAbsolutePath(), null, classLoader.getParent());

    try {
        Field fieldClassLoader = ClassLoader.class.getDeclaredField("parent");
        if (fieldClassLoader != null) {
            fieldClassLoader.setAccessible(true);
            fieldClassLoader.set(classLoader, dexClassLoader);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

First get the current ClassLoader, then create a new DexClassLoader and set the parent of the ClassLoader to the parent of the DexClassLoader, which is the BootClassLoader. This completes the first step by setting the parent of the DexClassLoader to the BootCLassLoader.Second, remove the parent in the current ClassLoader by reflection. The current ClassLoader is PathClassLoader. After removing the parent, set the new DelexClassLoader created in the previous step to parent, which makes it easy to insert DexCLassLoader into the process of finding a class when the class is loaded. Here is a code test.We customize an Application to load an external apk at an earlier time, which has only one activity to experiment with.

public class HookApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        LoadApkUtils.loadApk(base, Environment.getExternalStorageDirectory().getPath()
                + File.separator + "lib.apk");
        HookUtils.hookActivityManagerService(getClassLoader());
        HookUtils.hookActivityThreadHandler();
    }
}

Start the application again with the following results

ClassLoader: dalvik.system.PathClassLoader

parentClassLoader: dalvik.system.DexClassLoader

parent's parentClassLoader: java.lang.BootClassLoader@4546fd3

As you can see here, DexClassLoader has been successfully inserted between PathCLassLoader and BootClassLoader.From the previous article Method to start an activity that is not registered with AndroidManifest.xml To load the activity of an external apk.

Summary

With the above methods, you can dynamically load an activity from an external apk, which can be used to dynamically download the APK for new functionality.So far, however, external activitys cannot contain resources because resource files generate an id when compiled, and loading external activitys cannot find an id.In a later article, you will step by step describe how to get external resources through hook AssetManager to load a complete activity.

After the summary

After running the above code on android 8.0, we can't find a single gDefault when hooking Activity Manager Native. If you don't know gDefault, take a look Previous article By looking at the source code, it is found that the ActivityManagerNative does not host related cases, but puts IActivityManager's cases in the Activity Manager, so that the hooks will also change accordingly, which exposes a disadvantage of the dynamic proxy hook method and needs to be adapted for each version.The above code only passed the tests on android5.1 and 6.0.Attach Android 8.0 related change source address

Keywords: Java Android xml

Added by Roble on Thu, 30 May 2019 19:32:37 +0300