안드로이드 고급 복호화 ④- 플러그인 원리

플러그인을 배우기 전에 이전 기사를 읽어야합니다.

동적 로딩 기술 :

프로그램이 실행 중일 때 프로그램에 존재하지 않는 일부 실행 파일을 동적으로로드하여 실행하고, 응용 기술의 발전에 따라 동적로드 기술은 점차적으로 핫 픽스와 플러그인의 두 가지 분기를 파생시킵니다.

  • 핫픽스 : 버그 수정에 사용
  • 플러그인 : 거대한 애플리케이션을 해결하고 기능 모듈을 분리하고 다른 APK의 코드를 재사용합니다.
플러그인 아이디어 :

재사용 된 apk를 플러그인으로 사용하고 다른 apk에 삽입합니다. 예를 들어 Taobao에 소금에 절인 생선 페이지가 있고 Taobao를 사용하여 소금에 절인 생선을 배출합니다. 플러그인 기술을 사용하면 소금에 절인 생선 apk에서 dex 파일을 직접 사용할 수 있습니다. 소금에 절인 생선 페이지 세트를 다시 개발하고 Taobao apk의 결합 정도를 효과적으로 줄이는 비용;

활동 플러그인 원칙 :

플러그인 활동의 목적은 다른 apk의 활동을 직접 사용하는 것이며 활동의 시작 및 수명주기 관리는 AMS에서 처리해야합니다. 다른 apk의 활동은이 프로젝트의 매니페스트에 등록되어 있지 않으며 전달되지 않아야합니다. ams 검증을 우회하려면 startActivity 프로세스를 연결해야합니다.이 프로젝트에서 pit 활동을 사용할 수 있습니다. ams로 보내기 전에 플러그인 활동을 pit 활동으로 교체하여 ams 검증을 통과하십시오. 검증이 완료되면 실제 시작이 반복됩니다. 플러그인 활동을 다시 교체하십시오.

단계:
  • 이 프로젝트의 피트 활동을 미리 준비하십시오.
  • 구덩이 활동을 사용하여 ams 확인 우회
  • 플러그인 활동 복원

1. 활동을 할 준비를하십시오

원래 프로젝트에서 직접 빈 활동을 준비하고 매니페스트에 등록하고 아래 SubActivity라고 부르는 것을 잊지 마십시오.

2. 플러그인 활동을 사용하여 피트 활동 대체

검증을 위해 ams 프로세스로 넘어 가기 전에 사용자 프로세스는 Instrumentation과 iActivityManager의 두 클래스를 통과합니다. 두 클래스 모두 후크 포인트로 사용할 수 있습니다. 다음은 iActivityManager를 후크하는 방법입니다.

2.1 후크 포인트에 대한 프록시 클래스, 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);
    }
}

여기에 사용 된 동적 프록시는 iActivityManager 프록시를 작성하고, 먼저 원래 시작된 플러그인 활동의 의도를 찾은 다음이를 대체 할 SubActivity를 시작하는 새 의도를 작성합니다.

2.2 원래 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);
    }
  • 이 메서드는 startActivity 전에 호출해야합니다.

3. 플러그인 활동 복원

ams 검증을 우회 한 후 실제로 TargetActivity를 시작해야하며 Handler 메커니즘을 학습 한 후 메시지의 처리 순서가 먼저 현재 message.callback에 논리가 있는지 여부를 판단하고 콜백을 먼저 실행하는 것임을 알고 있습니다 .Message를 후크로 사용할 수 있습니다. 포인트

3.1 사용자 정의 콜백을 생성하고 handleMessage가 처리되기 전에 fakeIntent를 실제 인 텐트로 대체
  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 후크 ActivityThread, 메인 스레드 H (Handler)의 CallBack 속성 수정,原理参考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());
    }

Handler 메커니즘에는 두 개의 콜백이 있습니다. 하나는 Handler.mCallback이고 다른 하나는 Message.callback입니다.

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

루프에서 메시지가 폴링되면 dispatchMessage가 호출되고 Message.callback이 null이 아니면 Runnable 콜백이 처리 된 후 종료됩니다. msg.callback이 null이면 핸들러의 mCallback이 먼저 실행되고 핸들러의 mCallback.handleMessage에 따라 실행됩니다. 반환 값은 Handler.handleMessage를 실행할지 여부를 결정합니다.
根据上面的流程,我们可以在ActivityThread的H处理startActivity这个Message的handleMessage前,在H的Callback中插入修改intent的代码,做到真实的开启TargetActivity

3.3 플러그인 활동의 라이프 사이클 관리 :

위의 작업은 활동, 플러그인 활동의 수명주기 관리 방법 만 활성화하고 AMS는 토큰을 사용하여 활동을 식별하고 관리하며 플러그인 활동 토큰의 바인딩은 영향을받지 않으므로 플러그인 활동에는 수명주기가 있습니다. ;

서비스 플러그인 원칙

에이전트 배포 구현 :

플러그인 서비스가 시작되면 프록시 서비스가 먼저 시작되고 프록시 서비스가 실행 중이면 플러그인 서비스가 onStartCommand에서 시작됩니다.

단계:
  • 프로젝트에서 에이전트 서비스가 준비되었습니다.
  • 후크 iActivityManager는 프록시 서비스를 시작합니다.
  • 에이전트 배포 :
  1. ProxyService는 플러그인 서비스를 배포하는 데 시간이 오래 걸리므로 다시 만들려면 START_STICKY ProxyService를 반환해야합니다.
  2. 플러그인 서비스 생성, 첨부, onCreate;

1. 프로젝트에서 ProxyService를 만들고 매니페스트에 등록합니다.

2. iActivityManager를 연결하고 ProxyService로 시작할 TargetService를 바꿉니다.

2.1 사용자 정의 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 후크 AMS가 원래 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);
    }

startService 전에이 코드를 호출하면 proxyService가 시작되고 다음으로 proxyService에 targetService를 배포합니다.

2.3 proxyService에서 TargetService 시작 :
  • 연결을 호출하여 컨텍스트 바인딩
  • 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;
    }
}

  • 먼저 부착에 필요한 매개 변수를 준비하고 반사를 통해 가져옵니다.
  • targetService의 첨부 메소드 호출
  • targetService의 onCreate 메서드를 호출합니다.

자원 플러그인 화

여기에서 스키닝 문서를 참조하십시오 : Android 스키닝의 원리

Dong의 플러그인 연습

1. 플러그인의 기능 :

플러그인의 목적은 플러그인 프로젝트 代码/类메인 프로젝트를 사용하는 것입니다.资源

1.1 플러그인의 네 가지 주요 구성 요소 처리 :

플러그인 활동 사용과 같은 플러그인의 네 가지 주요 구성 요소 클래스를 사용하는 경우 특수 처리를 수행해야합니다. 일반적인 처리 방법은 다음과 같습니다.

  • AMS를 연결하고 4 가지 주요 구성 요소의 AMS 검증을 우회하고 수명주기를 수동으로 관리합니다.
  • 기본 프로젝트의 매니페스트에 플러그인 활동을 직접 등록합니다.

방법 1은 구현의 어려움과 높은 유연성을 특징으로하지만 시스템에 대한 비 SDK 호출에 대한 Google의 제한으로 인해이 방법은 향후 실패 할 수 있습니다.
방법 2는 간단한 구현이 특징이지만 충분히 유연하지 않으며 매니페스트에 작성되어야합니다. 죽은

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

1.2 플러그인에서 클래스로드 및 사용 :

각 플러그인은 ClassLoader로 설정되며, 그 목적은 전체 플러그인 클래스가 로더에 의해로드되고 모든 플러그인의 ClassLoader가 상위 위임에서 다른 ClassLoader를 상속하는 것입니다.이 목적은 메인 프로젝트의 통합 관리를 용이하게하기위한 것입니다.

1.3 DelegateClassLoader 교체 :

LoadedAPK의 ClassLoader를 DelegateClassLoader로 바꿉니다.

1.4 플러그인 리소스 참조

AssetManager에 경로를 추가하기 만하면됩니다. 자세한 내용은 위를 참조하십시오.

2. 플러그인을 메인 프로젝트에 패키징하는 방법, 즉 메인 프로젝트에 플러그인 패키지를 통합하는 방법 :

  • 플러그인 apk를 자산 디렉토리에 넣고 assetManager를 통해로드합니다.
  • 플러그인 apk 파일의 수정 된 접미사를 lib / armeabi 디렉토리에 .so에 넣으면 기본 프로젝트 apk가 설치 중에이 디렉토리의 파일을 data / data / <package_name> / lib / 디렉토리에 자동으로로드하며 직접 가져올 수 있습니다.

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

3. 플러그인과 메인 프로젝트 간의 통신 방법

에어 비앤비 의 DeepLinkDispatch는 주로

DeepLinkDispatch는 Meituan에서 Gaode 맵을 열거 나 WeChat에서 JD를 여는 것과 같이 다른 APP 페이지로 이동하는 데 사용되는 기본 Android 체계 프로토콜과 유사합니다.

DeepLinkDispatch의 실현 아이디어 : 먼저 플러그인 프로젝트에서 열어야하는 Activity에 주석을 추가 한 다음 기본 프로젝트의 입력 주석에 DeepLinkDispatch.startActivityDirect()설정된 매개 변수 를 호출 하고 마지막으로 시스템 API를 통해 Context.startActivity()페이지 엽니 다.

4. 플러그인의 미래

시스템 API에 대한 Google의 더 엄격하고 엄격한 제한으로 인해 이제는 개발자가 조정할 수있는 시간을 허용하기 위해 블랙리스트, 짙은 회색 목록 및 밝은 회색 목록으로 구분되었으므로 플러그인은 미래가 없어야합니다. 플러그인의 용도에 대해 생각해 보겠습니다. :

  • 개발 효율성 향상을위한 독립 컴파일
  • 모듈 디커플링, 재사용 코드
Dongdong의 솔루션 :

组件化:
기존의 구성 요소는 새로운 Android 라이브러리를 만들고 개발, 디버깅 및 실제 참조 중에 애플리케이션과 라이브러리간에 전환하는 것입니다. 동동의 구성 요소 화는 각 구성 요소를 별도의 프로젝트로 만든 다음 프로젝트 구조에 애플리케이션과 라이브러리를 유지하는 것입니다. Android 라이브러리, 라이브러리는 구성 요소 기능을 구현하는 데 사용되며 앱은 개발 및 디버깅에 사용되며 메인 프로젝트가 사용되는 경우 클라우드의 maven 동기화에 직접 의존합니다.

추천

출처blog.csdn.net/weixin_46824291/article/details/109648854