Android advanced decryption ④-plug-in principle

Before learning plug-inization, you need to read the previous articles:

Dynamic loading technology:

When the program is running, some executable files that do not exist in the program are dynamically loaded and run. With the development of application technology, dynamic loading technology gradually derives two branches, hot fix and plug-in;

  • Hot fix: used to fix bugs
  • Plug-in: Resolve the huge application, decoupling of functional modules, and reuse the code of other apk
Plug-in ideas:

Use the reused apk as a plug-in and insert it into another apk. For example, there will be a page of salted fish in Taobao, and use Taobao to drain the salted fish. Using plug-in technology, you can directly use the dex file in the salted fish apk, which saves it The cost of developing a set of salted fish pages again, and effectively reduce the coupling degree of Taobao apk;

Activity plug-in principle:

The purpose of the plug-in activity is to directly use the activity of another apk, and the startup and life cycle management of the activity need to be processed by AMS. The activity of the other apk is not registered in the manifest of this project, and it must not be passed, so we You need to hook the startActivity process to bypass the verification of ams. You can use a pit activity in this project. Before sending to ams, replace the plug-in activity with the pit activity to pass the verification of ams. After the verification is completed, the real startup will be repeated Replace the plug-in activity back;

step:
  • Prepare the activity in this project in advance
  • Use pit activity to bypass ams verification
  • Restore plugin activity

1. Prepare to occupy the activity

Just prepare a blank activity directly in the original project, remember to register in the manifest, call him SubActivity below

2. Use plug-in activity to replace the pit activity

Before handing over to the ams process for verification, the user process will pass through two classes, Instrumentation and iActivityManager. Both classes can be used as hook points. Here is a method of hooking iActivityManager;

2.1 Create a proxy class for hook points, iActivityManagerProxy
public class IActivityManagerProxy implements InvocationHandler {
    
    

    private Object realActivityManager;

    public IActivityManagerProxy(Object realActivityManager) {
    
    
        this.realActivityManager = realActivityManager;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        if ("startActivity".equals(method.getName())){
    
    
            //  首先找到,原本需要启动的插件activity的原始intent
            Intent originIntent = null;
            int index = 0;
            for (int i = 0;i<args.length;i++){
    
    
                if (args[i] instanceof Intent){
    
    
                    originIntent = (Intent) args[i];
                    index = i;
                    break;
                }
            }
            //  新建欺骗ams的占坑activity的intent
            Intent fakeIntent = new Intent();
            fakeIntent.setClass("xxx.xxx.xxx",SubActivity.class);
            //  将真实的intent保存在fakeIntent中用于第三步的还原操作
            fakeIntent.putExtra("real_intent",originIntent);
            //  将fakeIntent写回原来的arges数组中
            args[index] = fakeIntent;
        }
        return method.invoke(realActivityManager,args);
    }
}

The dynamic proxy used here creates the proxy of iActivityManager. First find the Intent of the plug-in Activity that was originally started, and then create a new intent to start SubActivity to replace it;

2.2 Replace the original iActivityManager:
    public void hookAMS() throws Exception {
    
    
        // 获取ActivityManager getService 返回的单例
        Class ActivityManagerClazz = ActivityManager.class;
        Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
        Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);

        //  通过单例.get()获取iActivityManager, 这两步需要参考源码的iActivityManager的获取
        Class singleClazz = IActivityManagerSingleton.getClass();
        Method getMethod = singleClazz.getDeclaredMethod("get");
        Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
        
        // 生成动态代理对象
        Object proxyInstance = Proxy.newProxyInstance(
                ActivityManagerClazz.getClassLoader(),
                ActivityManagerClazz.getInterfaces(),
                new IActivityManagerProxy(iActivityManager));

        // 将代理对象设置到单例上
        Field mInstanceField = singleClazz.getField("mInstance");
        mInstanceField.set(IActivityManagerSingleton,proxyInstance);
    }
  • This method needs to be called before startActivity

3. Restore plugin Activity

After bypassing the ams verification, we also need to actually start TargetActivity, and after learning the Handler mechanism, we know that the processing order of the message is to first judge whether the current message.callback has logic, and execute the callback first; we can use Message as a hook point

3.1 Create a custom CallBack and replace fakeIntent with a real intent before handleMessage is processed
  class MCallBack implements android.os.Handler.Callback {
    
    
        @Override
        public boolean handleMessage(Message msg) {
    
    
            try {
    
    
                Object activityClientRecord = msg.obj;
                // 获取fakeIntent
                Class acrClazz = activityClientRecord.getClass();
                Field intentField = acrClazz.getDeclaredField("intent");
                Intent intent = (Intent) intentField.get(activityClientRecord);
                // 取出targetActivity的Intent
                Intent realIntent = intent.getParcelableExtra("real_intent");
                // 将realIntent的内容设置到fakeIntent
                intent.setComponent(realIntent.getComponent());
                
            } catch (NoSuchFieldException e) {
    
    
                e.printStackTrace();
            } catch (IllegalAccessException e) {
    
    
                e.printStackTrace();
            }
            msg.getTarget().handleMessage(msg);
            return true;
        }
    }
3.2 hook ActivityThread, modify the CallBack attribute of H (Handler) of the main thread,原理参考dispatchMessage方法
    private void hookActivityThread() throws Exception {
    
    
        Class activityThreadClass = Class.forName("android.app.ActivityThread");
        Field singleInstanceField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
        Object activityThreadInstance = singleInstanceField.get(null);
        
        Field mHField = activityThreadClass.getDeclaredField("mH");
        Handler handler = (Handler) mHField.get(activityThreadInstance);
        
        // 修改handler 的callback
        Class handlerClazz = handler.getClass();
        Field callbackField = handlerClazz.getDeclaredField("mCallback");
        callbackField.set(handler,new MCallBack());
    }

There are two callbacks in the Handler mechanism, one is Handler.mCallback and the other is Message.callback

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

When the message is polled in the loop, dispatchMessage will be called; if Message.callback is not null, the Runnable callback will be processed and then ended. If msg.callback is null, the Handler's mCallback will be executed first, and according to the Handler's mCallback.handleMessage. The return value determines whether to execute Handler.handleMessage;
根据上面的流程,我们可以在ActivityThread的H处理startActivity这个Message的handleMessage前,在H的Callback中插入修改intent的代码,做到真实的开启TargetActivity

3.3 Life cycle management of plug-in Activity:

The above operation only enables the activity, how to manage the life cycle of the plug-in activity, AMS uses the token to identify and manage the activity, and the binding of the plug-in activity token is not affected, so the plug-in activity has a life cycle ;

Service plug-in principle

Agent distribution implementation:

When the plug-in service is started, the proxy service will be started first, and when the proxy service is running, the plug-in service is started in its onStartCommand;

step:
  • Agent Service is ready in the project
  • hook iActivityManager starts the proxy Service
  • Agent distribution:
  1. ProxyService takes a long time to distribute the plug-in Service, so you need to return START_STICKY ProxyService to recreate
  2. Create plug-in Service, attach, onCreate;

1. Create a ProxyService in the project and register it in the manifest;

2. Hook iActivityManager, replace the TargetService to be started with ProxyService

2.1 Create a custom iActivityManagerProxy
public class IActivityManagerProxy implements InvocationHandler {
    
    

    private Object realActivityManager;

    public IActivityManagerProxy(Object realActivityManager) {
    
    
        this.realActivityManager = realActivityManager;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        if ("startService".equals(method.getName())){
    
    
            Intent targetIntent = null;
            int index = 0;
            for (int i = 0;i<args.length;i++){
    
    
                if (args[i] instanceof Intent){
    
    
                    targetIntent = (Intent) args[i];
                    index = i;
                    break;
                }
            }
            Intent proxyIntent = new Intent();
            proxyIntent.setClassName("com.xx.xx","com.xx.xx.ProxyService");
            proxyIntent.putExtra("target_intent",targetIntent);
            args[index] = proxyIntent;
        }
        return method.invoke(realActivityManager,args);
    }
}
2.2 hook AMS replaces the original IActivityManager 同上
 public void hookAMS() throws Exception {
    
    
        // 获取ActivityManager getService 返回的单例
        Class ActivityManagerClazz = ActivityManager.class;
        Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
        Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);

        //  通过单例.get()获取iActivityManager, 这两步需要参考源码的iActivityManager的获取
        Class singleClazz = IActivityManagerSingleton.getClass();
        Method getMethod = singleClazz.getDeclaredMethod("get");
        Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
        
        // 生成动态代理对象
        Object proxyInstance = Proxy.newProxyInstance(
                ActivityManagerClazz.getClassLoader(),
                ActivityManagerClazz.getInterfaces(),
                new IActivityManagerProxy(iActivityManager));

        // 将代理对象设置到单例上
        Field mInstanceField = singleClazz.getField("mInstance");
        mInstanceField.set(IActivityManagerSingleton,proxyInstance);
    }

As long as this code is called before startService, the proxyService will be started. Next, we will distribute the targetService in the proxyService;

2.3 Start TargetService in proxyService:
  • Call attach to bind Context
  • Call onCreate
public class ProxyService extends Service {
    
    
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
    
    
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    
    
        try {
    
    
            // 准备attach方法的参数
            Class activityThreadClazz = null;
            activityThreadClazz = Class.forName("android.app.ActivityThread");
            Method getApplicationMethod = activityThreadClazz.getDeclaredMethod("getApplicationMethod");
            Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadField.setAccessible(true);
            // activityThread
            Object activityThread = sCurrentActivityThreadField.get(null);
            // applicationThread
            Object applicationThread = getApplicationMethod.invoke(activityThread, null);
            Class iInterFaceClazz = Class.forName("android.os.IInterface");
            Method asBinderMethod = iInterFaceClazz.getDeclaredMethod("asBinder");
            asBinderMethod.setAccessible(true);
            // token
            Object token = asBinderMethod.invoke(applicationThread);
            // iActivityManager
            Class ActivityManagerClazz = ActivityManager.class;
            Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
            Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
            Class singleClazz = IActivityManagerSingleton.getClass();
            Method getMethod = singleClazz.getDeclaredMethod("get");
            Object iActivityManager = getMethod.invoke(IActivityManagerSingleton, null);

            // targetService
            Class serviceClazz = Class.forName("android.app.Service");
            Service targetService = (Service) serviceClazz.newInstance();

            // attach
            Method attachMethod = serviceClazz.getDeclaredMethod("attach");
            attachMethod.invoke(targetService, this,
                    activityThread, intent.getComponent().getClassName(),
                    token, getApplication(), iActivityManager);
            targetService.onCreate();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return START_STICKY;
    }
}

  • First prepare the parameters needed for attach and obtain them through reflection
  • Call the attach method of targetService
  • Call the onCreate method of targetService

Pluginization of resources

Refer to the skinning article here: Principles of Android skinning

Some Dong’s plug-in practice

1. What plug-in does:

The purpose of plug-in is to use the plug-in project 代码/类and the main project资源

1.1 The processing of the four major components in the plug-in:

When using the four major component classes of plug-ins, such as the use of plug-in activities, special treatment must be done. The general treatment methods are:

  • hook AMS, bypass AMS verification of the four major components, and manually manage the life cycle
  • Register the plug-in activity directly in the manifest of the main project

Method 1 is characterized by high difficulty in implementation and high flexibility, but with Google’s restrictions on system non-sdk calls, this method may fail in the future;
Method 2 is characterized by simple implementation, but not flexible enough, it must be written in the manifest dead

某东采用的方式2,直接在manifest中写死插件的四大组件注册

1.2 The loading and use of classes in the plug-in:

Each plug-in is set up with a ClassLoader, the purpose is that the entire plug-in class is loaded by a loader, and the ClassLoader of all plug-ins inherit another ClassLoader in the parent delegation. This purpose is to facilitate the unified management of the main project;

1.3 Replacement of DelegateClassLoader:

Replace the ClassLoader of LoadedAPK with DelegateClassLoader;

1.4 Reference to plugin resources

Just add a path to AssetManager, see above for details

2. How to package the plug-in into the main project, that is, how to integrate the plug-in package in the main project:

  • Put the plug-in apk into the asset directory and load it through assetManager
  • Put the modified suffix of the plug-in apk file to .so into the lib/armeabi directory, and the main project apk will automatically load the files in this directory to the data/data/< package_name >/lib/ directory when installing, and you can get it directly;

某东使用的第二种以so的形式放入lib目录自动加载,因为在运行时去使用AssetManager加载asset资源会影响程序的运行时速度

3. How to communicate between plug-in and main project

The DeepLinkDispatch of airbnb mainly borrowed from

DeepLinkDispatch is similar to the Android native scheme protocol, used to jump to another APP page, such as opening Gaode map in Meituan, or opening JD in WeChat,

The realization idea of ​​DeepLinkDispatch: first add an annotation to the Activity that needs to be opened in the plug-in project, then call the DeepLinkDispatch.startActivityDirect()parameters set in the input annotation in the main project , and finally Context.startActivity()open the page through the system api

4. The future of plug-in

With Google’s stricter and stricter restrictions on system APIs, and now it has been divided into blacklists, dark gray lists, and light gray lists to allow time for developers to adjust, plug-in should have no future. Let’s think about what the plug-in is for :

  • Independent compilation to improve development efficiency
  • Module decoupling, reuse code
Dongdong’s solution:

组件化:
The traditional component is to create a new Android Library, switch between Application and Library during development, debugging and actual reference. Dongdong’s componentization is to make each component a separate project, and then retain Application and Library in the project structure. Android Library, library is used to implement component functions, app is used for development and debugging, and when the main project is used, it directly depends on maven sync from the cloud;

Guess you like

Origin blog.csdn.net/weixin_46824291/article/details/109648854