HOOK式插件化框架-Activity

773 阅读5分钟

整体的流程

  • HOOK 绕过AMS对非注册的Acitivity的检查,使用一个已经注册过的代理activity充当傀儡,让其通过校验
  • HOOK 还原目标的Activity
  • HOOK 方式融合宿主和插件DexElement

1. AMS对Activity的检查如何绕过去

  • android.app.Instrumentation#checkStartActivityResult 会对startActivity后的result进行检测并抛出异常(have you declared this activity in your AndroidManifest) 那么解决办法就是使用一个已经注册过的代理activity充当傀儡,让其通过校验

在调用startActivity的方法时会再调用binder方法之前调用

ActivityManagerNative.getDefault().startActivity

此方法会返回IActivityManager是一个接口 ,ActivityManagerNative.getDefault() 又是一个单例,那么只需要使用动态代理,hook此方法,发现是插件中的activity将其替换为宿主中的代理activity即可

Class mIActivityManagerClass = Class.forName("android.app.IActivityManager");

Class mActivityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
final Object mIActivityManager = mActivityManagerNativeClass2.getMethod("getDefault").invoke(null);

Object mIActivityManagerProxy = Proxy.newProxyInstance(
    getClassLoader(),
    new Class[]{mIActivityManagerClass},
    new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("startActivity".equals(method.getName())) {
                // 用ProxyActivity 绕过了 AMS检查
                Intent intent = new Intent(MyApplication.this, ProxyActivity.class);
                intent.putExtra("actionIntent", ((Intent) args[2])); // 把之前跳转参数传递过去方便还原
                args[2] = intent;
            }
            return method.invoke(mIActivityManager, args);
        }
    });

Class mActivityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = mActivityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null);

// 替换掉系统ActivityManager单例中的方法
Class mSingletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = mSingletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
mInstanceField.set(gDefault, mIActivityManagerProxy);

2. 还原目标Activity

在经过了AMS 校验之后,真正启动是通过应用进程的ActivityThread进行启动的,然后最终会通过H类(extends Handler)的 msg.what为 ActivityThread.H#LAUNCH_ACTIVITY 来启动Activity的

public void handleMessage(Message msg) {
    switch (msg.what) {
        case LAUNCH_ACTIVITY: {
            final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

            r.packageInfo = getPackageInfoNoCheck(
                    r.activityInfo.applicationInfo, r.compatInfo);
            handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
        } break;
        ...
    }
}

然后发现ActivityClientRecord此类是记录了要启动activity的信息的,还有Intent 那我们就可以将H类(extends Handler) 进行HOOK(Handler类有一个地方如果自定义了mCallBack 就优先给mCallBack继续处理), 发现有Intent 中包含了actionIntent字段,我们就将其替换为真正要启动的Activity

Field mCallbackFiled = Handler.class.getDeclaredField("mCallback");
mCallbackFiled.setAccessible(true); // 授权

/**
 * handler对象怎么来
 * 1.寻找H,先寻找ActivityThread
 *
 * 执行此方法 public static ActivityThread currentActivityThread()
 *
 * 通过ActivityThread 找到 H
 *
 */
Class mActivityThreadClass = Class.forName("android.app.ActivityThread");
// 获得ActivityThrea对象
Object mActivityThread = mActivityThreadClass.getMethod("currentActivityThread").invoke(null);

Field mHField = mActivityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
// 获取真正对象
Handler mH = (Handler) mHField.get(mActivityThread);

mCallbackFiled.set(mH, new MyCallback(mH)); // 替换 增加我们自己的实现代码
public static final int LAUNCH_ACTIVITY         = 100;
class MyCallback implements Handler.Callback {

    private Handler mH;

    public MyCallback(Handler mH) {
        this.mH = mH;
    }

    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {

            case LAUNCH_ACTIVITY:
                // 做我们在自己的业务逻辑(把ProxyActivity 换成  TestActivity)
                Object obj = msg.obj;

                try {
                    // 我们要获取之前Hook携带过来的 TestActivity
                    Field intentField = obj.getClass().getDeclaredField("intent");
                    intentField.setAccessible(true);

                    // 获取 intent 对象,才能取出携带过来的 actionIntent
                    Intent intent = (Intent) intentField.get(obj);
                    // actionIntent == TestActivity的Intent
                    Intent actionIntent = intent.getParcelableExtra("actionIntent");

                    if (actionIntent != null) {
                        /*
                        if (activityList.contains(actionIntent.getComponent().getClassName())) {
                            intentField.set(obj, actionIntent); // 把ProxyActivity 换成  TestActivity
                        } else { // 没有权限
                            intentField.set(obj, new Intent(HookApplication.this, PermissionActivity.class));
                        }
                        */
                        intentField.set(obj, actionIntent); // 把ProxyActivity 换成  TestActivity
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
        }


        mH.handleMessage(msg);
        // 让系统继续正常往下执行
        // return false; // 系统就会往下执行
        return true; // 系统不会往下执行
    }
}

3. HOOK 类加载流程,将插件中的类加载

在执行到ActivityThread H类的LANUCH_ACTIVITY 之后会调用 handleLaunchActivity->performLaunchActivity->Instrumentation#newActivity(java.lang.ClassLoader, java.lang.String, android.content.Intent) 方法,里面调用了classLoader.loadClass 进行创建Activity对象

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        // First, check if the class has already been loaded
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
            }
        }
        return c;
    }

这就是经典的双亲委派了,那么我们发现如果都找不到会执行findClass方法,这里肯定会调用PathClassLoader findClass方法,但是PathClassLoader并没有覆盖实现此方法所以执行的是BaseDexClassLoader的findClass方法

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    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;
}

也就是通过pathList进行查找的

DexPathList.java 

public Class<?> findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        Class<?> clazz = element.findClass(name, definingContext, suppressed);
        if (clazz != null) {
            return clazz;
        }
    }

    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

又通过遍历Element进行查找,Element 实际上封装的就是我们app安装时的classes.dex classes2.dex 等

Element.java

public Class<?> findClass(String name, ClassLoader definingContext,
        List<Throwable> suppressed) {
    return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
            : null;
}

最终调用DexFile loadClassBinaryName (此方法以下就是native了)

那我们就可以将PathClassLoader进行HOOK ,使用DexClassLoader load插件APK,然后反射获取到PathList中的dexElements,添加到原来的classLoader中DexPathList中的dexElements,即可实现插件和宿主的类加载融合起来

// 第一步:找到宿主 dexElements 得到此对象   PathClassLoader代表是宿主
PathClassLoader pathClassLoader = (PathClassLoader) this.getClassLoader(); // 本质就是PathClassLoader
Class mBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
// private final DexPathList pathList;
Field pathListField = mBaseDexClassLoaderClass.getDeclaredField("pathList");
pathListField.setAccessible(true);
Object mDexPathList = pathListField.get(pathClassLoader);

Field dexElementsField = mDexPathList.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
// 本质就是 Element[] dexElements
Object dexElements = dexElementsField.get(mDexPathList);

/*** ---------------------- ***/


// 第二步:找到插件 dexElements 得到此对象,代表插件 DexClassLoader--代表插件
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
if (!file.exists()) {
    throw new FileNotFoundException("没有找到插件包!!");
}
String pluginPath = file.getAbsolutePath();
File fileDir = this.getDir("pluginDir", Context.MODE_PRIVATE); // data/data/包名/pluginDir/
DexClassLoader dexClassLoader = new
        DexClassLoader(pluginPath, fileDir.getAbsolutePath(), null, getClassLoader());

Class mBaseDexClassLoaderClassPlugin = Class.forName("dalvik.system.BaseDexClassLoader");
// private final DexPathList pathList;
Field pathListFieldPlugin = mBaseDexClassLoaderClassPlugin.getDeclaredField("pathList");
pathListFieldPlugin.setAccessible(true);
Object mDexPathListPlugin = pathListFieldPlugin.get(dexClassLoader);

Field dexElementsFieldPlugin = mDexPathListPlugin.getClass().getDeclaredField("dexElements");
dexElementsFieldPlugin.setAccessible(true);
// 本质就是 Element[] dexElements
Object dexElementsPlugin = dexElementsFieldPlugin.get(mDexPathListPlugin);


// 第三步:创建出 新的 dexElements []
int mainDexLeng =  Array.getLength(dexElements);
int pluginDexLeng =  Array.getLength(dexElementsPlugin);
int sumDexLeng = mainDexLeng + pluginDexLeng;

// 参数一:int[]  String[] ...  我们需要Element[]
// 参数二:数组对象的长度
// 本质就是 Element[] newDexElements
Object newDexElements = Array.newInstance(dexElements.getClass().getComponentType(),sumDexLeng); // 创建数组对象


// 第四步:宿主dexElements + 插件dexElements =----> 融合  新的 newDexElements
for (int i = 0; i < sumDexLeng; i++) {
    // 先融合宿主
    if (i < mainDexLeng) {
        // 参数一:新要融合的容器 -- newDexElements
        Array.set(newDexElements, i, Array.get(dexElements, i));
    } else { // 再融合插件的
        Array.set(newDexElements, i, Array.get(dexElementsPlugin, i - mainDexLeng));
    }

}

// 第五步:把新的 newDexElements,设置到宿主中去
// 宿主
dexElementsField.set(mDexPathList, newDexElements);

4. 额外的知识

Android中有三个classLoad 比较常用

  • BootClassLoader 加载系统类,处于最顶级的位置
  • PathClassLoader 只能加载系统中已经安装过的apk
  • DexClassLoader 能够加载未安装的jar/apk/dex

从启动了解下

  1. 内核启动
  2. init进程启动(系统的第一个进程,也称为守护进程)
  3. init进程会启动zygote进程(zygote进程会孵化其他的所有子进程)
  4. zygote会孵化SystemServer进程
  5. SystemServer会启动很多服务(AMS, PMS)

在启动zygote进程时会调用ZygoteInit#main 里面的preload-> preloadClasses() ,里面会最终调用

loader = BootClassLoader.getInstance();

还是会调用startSystemServer -> createSystemServerClassLoader-> PathClassLoaderFactory.createClassLoader 生成pathClassLoader