美文网首页Android开发经验·源码分析
Android插件化之Hook Activity

Android插件化之Hook Activity

作者: 程序员三千_ | 来源:发表于2020-04-06 00:37 被阅读0次

    插件化:Android插件化技术,可以实现功能模块的按需加载和动态更新(从服务器上下载),其本质是动态加载未安装的apk。从而减小apk的大小。其中最主要的就是Activity的插件化技术,主要是通过hook来实现的。因为Activity的启动是要经过AMS的校验的,所以就需要对AMS下功夫。

    • Step1: 在宿主工程的AndroidManifest.xml中预先注册一个没有任何功能的Activity进行占坑。

    • Step2.:使用占坑Activity绕过AMS验证:Activity的启动,实际会调用Instrumentation类的execStartActvity方法,所以可以对其进行hook,将启动插件Activity的Intent替换成宿主预注册的插桩Activity,从而绕过ASM的验证,并将插件Activity的Intent保存起来。

    • Step3: 还原插件Activity:在 AMS 校验完毕的时候,通过 binder 告知我们的应用启动相应 activity 的时候,我们将 插件Activity的intent 的信息再取出来,还原。

    本文以 API 27 的源码为基础分析

    Hook 的选择点:

    静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。

    Hook 过程:

    1、寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。
    选择合适的代理方式,如果是接口可以用动态代理。
    2、用代理对象替换原始对象。
    3、Android 的 API 版本比较多,方法和类可能不一样,所以要做好 API 的兼容工作。

    Hook AMS

    我们先来 mInstrumentation.execStartActivity 方法

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        Uri referrer = target != null ? target.onProvideReferrer() : null;
        if (referrer != null) {
            intent.putExtra(Intent.EXTRA_REFERRER, referrer);
        }
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    ActivityResult result = null;
                    if (am.ignoreMatchingSpecificIntents()) {
                        result = am.onStartActivity(intent);
                    }
                    if (result != null) {
                        am.mHits++;
                        return result;
                    } else if (am.match(who, null, intent)) {
                        am.mHits++;
                        if (am.isBlocking()) {
                            return requestCode >= 0 ? am.getResult() : null;
                        }
                        break;
                    }
                }
            }
        }
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }
    
    
    

    这里我们留意 ActivityManager.getService().startActivity 这个方法

    public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }
    
    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };
    
    
    
    

    可以看到 IActivityManagerSingleton 是一个单例对象,因此,我们可以 hook 它。

    public static void hookAMSAfter26() throws Exception {
        // 第一步:获取 IActivityManagerSingleton
        Class<?> aClass = Class.forName("android.app.ActivityManager");
        Field declaredField = aClass.getDeclaredField("IActivityManagerSingleton");
        declaredField.setAccessible(true);
        Object value = declaredField.get(null);
        
        Class<?> singletonClz = Class.forName("android.util.Singleton");
        Field instanceField = singletonClz.getDeclaredField("mInstance");
        instanceField.setAccessible(true);
        Object iActivityManagerObject = instanceField.get(value);
        
        // 第二步:获取我们的代理对象,这里因为 IActivityManager 是接口,我们使用动态代理的方式
        Class<?> iActivity = Class.forName("android.app.IActivityManager");
        InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new
                Class<?>[]{iActivity}, handler);
        
        // 第三步:偷梁换柱,将我们的 proxy 替换原来的对象
        instanceField.set(value, proxy);
    
    }
    
    
    
    
    public class AMSInvocationHandler implements InvocationHandler {
    
        private static final String TAG = "AMSInvocationHandler";
    
        Object iamObject;
    
        public AMSInvocationHandler(Object iamObject) {
            this.iamObject = iamObject;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //            Log.e(TAG, method.getName());
            if ("startActivity".equals(method.getName())) {
                Log.i(TAG, "ready to startActivity");
                for (Object object : args) {
                    Log.d(TAG, "invoke: object=" + object);
                }
            }
            return method.invoke(iamObject, args);
        }
    }
    
    
    

    执行以下测试代码

    
    try {
        HookHelper.hookAMS();
    } catch (Exception e) {
        e.printStackTrace();
    }
    Intent intent = new Intent(this,TestActivity.class);
    startActivity(intent);
    

    接下来我们一起来看一下 API 25 Instrumentation 的代码(自 API 26 开始 ,Instrumentation execStartActivity 方法有所改变)

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        Uri referrer = target != null ? target.onProvideReferrer() : null;
        if (referrer != null) {
            intent.putExtra(Intent.EXTRA_REFERRER, referrer);
        }
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    if (am.match(who, null, intent)) {
                        am.mHits++;
                        if (am.isBlocking()) {
                            return requestCode >= 0 ? am.getResult() : null;
                        }
                        break;
                    }
                }
            }
        }
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }
    
    
    
    
    

    可以看到这里启动 activity 是调用 ActivityManagerNative.getDefault().startActivity 启动的。

    public abstract class ActivityManagerNative extends Binder implements IActivityManager
    {
       
        /**
         * Retrieve the system's default/global activity manager.
         */
        static public IActivityManager getDefault() {
            return gDefault.get();
        }
    
        private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
            protected IActivityManager create() {
                IBinder b = ServiceManager.getService("activity");
                if (false) {
                    Log.v("ActivityManager", "default service binder = " + b);
                }
                IActivityManager am = asInterface(b);
                if (false) {
                    Log.v("ActivityManager", "default service = " + am);
                }
                return am;
            }
        };
        
    }
    
    

    同理我们看到 ActivityManagerNative 的 gDefault 是一个静态变量,因此,我们可以尝试 hook gDefault.

    public static void hookAmsBefore26() throws Exception {
        // 第一步:获取 IActivityManagerSingleton
        Class<?> forName = Class.forName("android.app.ActivityManagerNative");
        Field defaultField = forName.getDeclaredField("gDefault");
        defaultField.setAccessible(true);
        Object defaultValue = defaultField.get(null);
    
        Class<?> forName2 = Class.forName("android.util.Singleton");
        Field instanceField = forName2.getDeclaredField("mInstance");
        instanceField.setAccessible(true);
        Object iActivityManagerObject = instanceField.get(defaultValue);
    
        // 第二步:获取我们的代理对象,这里因为 IActivityManager 是接口,我们使用动态代理的方式
        Class<?> iActivity = Class.forName("android.app.IActivityManager");
        InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{iActivity}, handler);
    
        // 第三步:偷梁换柱,将我们的 proxy 替换原来的对象
        instanceField.set(defaultValue, proxy);
    }
    
    

    启动一个没有在 AndroidManifest 声明的 Activity

    我们知道我们启动的 activity 信息都储存在 intent 中,那么我们若想要 启动一个没有在 AndroidManifest 声明的 Activity,那我们只需要在 某个时机,即调用 startActivity 方法之前欺骗 AMS ,我们的 activity 已经注册(即替换 intent)。

    这里我们重新理一下 Activity 大概的启动流程:
    app 调用 startActivity 方法 -> Instrumentation 类通过 ActivityManagerNative 或者 ActivityManager( API 26以后)将启动请求发送给 AMS -> AMS 进行一系列检查并将此请求通过 Binder 派发给所属 app -> app 通过 Binder 收到这个启动请求 -> ActivityThread 中的实现将收到的请求进行封装后送入 Handler -> 从 Handler 中取出这个消息,开始 app 本地的 Activity 初始化和启动逻辑。

    public class HookHelper {
        private static final String TAG = "xiaosanye";
    
        public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
    
        public static void hookIActivityManager() {
            //TODO:
    //        1. 找到了Hook的点
    //        2. hook点 动态代理 静态?
    //        3. 获取到getDefault的IActivityManager原始对象
    //        4. 动态代理 准备classloader 接口
    //        5  classloader, 获取当前线程
    //        6. 接口 Class.forName("android.app.IActivityManager");
    //        7. Proxy.newProxyInstance() 得到一个IActivityManagerProxy
    //        8. IActivityManagerProxy融入到framework
    
            try {
                Field gDefaultField = null;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    Class<?> activityManager = Class.forName("android.app.ActivityManager");
                    gDefaultField = activityManager.getDeclaredField("IActivityManagerSingleton");
                } else {
                    Class<?> activityManager = Class.forName("android.app.ActivityManagerNative");
                    //拿到 Singleton<IActivityManager> gDefault
                    gDefaultField = activityManager.getDeclaredField("gDefault");
                }
    
                gDefaultField.setAccessible(true);
                //Singlon<IActivityManager>
               //所有静态对象的反射可以通过传null获取。如果是实列必须传实例
                Object gDefault = gDefaultField.get(null);
    
                //拿到Singleton的Class对象
                Class<?> singletonClass = Class.forName("android.util.Singleton");
                Field mInstanceField = singletonClass.getDeclaredField("mInstance");
                mInstanceField.setAccessible(true);
                //获取到ActivityManagerNative里面的gDefault对象里面的原始的IActivityManager对象
                final Object rawIActivityManager = mInstanceField.get(gDefault);
    
                //进行动态代理
                ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
                //生产IActivityManager的代理对象
                Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{iActivityManagerInterface}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Log.i(TAG, "invoke: method " + method.getName());
                        if ("startActivity".equals(method.getName())) {
                            Log.i(TAG, "准备启动activity");
                            for (Object obj : args) {
                                Log.i(TAG, "invoke: obj= " + obj);
                            }
    
                            //偷梁换柱 把Target 换成我们的Stub,欺骗AMS的权限验证
                            //拿到原始的Intent,然后保存
                            Intent raw = null;
                            int index = 0;
                            for (int i = 0; i < args.length; i++) {
                                if (args[i] instanceof Intent) {
                                    raw = (Intent) args[i];
                                    index = i;
                                    break;
                                }
                            }
                            Log.i(TAG, "invoke: raw= " + raw);
    
                            //替换成Stub
                            Intent newIntent = new Intent();
                            String stubPackage = "com.xiaosanye.activityhookdemo";
                            newIntent.setComponent(new ComponentName(stubPackage, StubActivity.class.getName()));
                            //把这个newIntent放回到args,达到了一个欺骗AMS的目的
                            newIntent.putExtra(EXTRA_TARGET_INTENT, raw);
                            args[index] = newIntent;
    
                        }
                        return method.invoke(rawIActivityManager, args);
                    }
                });
    
                //把我们的代理对象融入到framework
                mInstanceField.set(gDefault, proxy);
    
    
            } catch (Exception e) {
                Log.e(TAG, "hookIActivityManager: " + e.getMessage());
                e.printStackTrace();
            }
        }
    
        /**
         * hook ActivityThread 的 mH,还原未注册的Activity
         */
        public static void hookHandler() {
            //TODO:
            try {
                Class<?> atClass = Class.forName("android.app.ActivityThread");
                Field sCurrentActivityThreadField = atClass.getDeclaredField("sCurrentActivityThread");
                sCurrentActivityThreadField.setAccessible(true);
                Object sCurrentActivityThread = sCurrentActivityThreadField.get(null);
                //ActivityThread 一个app进程 只有一个,获取它的mH
                Field mHField = atClass.getDeclaredField("mH");
                mHField.setAccessible(true);
                final Handler mH = (Handler) mHField.get(sCurrentActivityThread);
    
                //获取mCallback
                Field mCallbackField = Handler.class.getDeclaredField("mCallback");
                mCallbackField.setAccessible(true);
    
                mCallbackField.set(mH, new Handler.Callback() {
    
                    @Override
                    public boolean handleMessage(Message msg) {
                        Log.i(TAG, "handleMessage: " + msg.what);
                        switch (msg.what) {
                            case 100: {
                                // final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
    //                            static final class ActivityClientRecord {
    //                                IBinder token;
    //                                int ident;
    //                                Intent intent;//hook 恢复
                                //恢复真身
                                try {
                                    Field intentField = msg.obj.getClass().getDeclaredField("intent");
                                    intentField.setAccessible(true);
                                    Intent intent = (Intent) intentField.get(msg.obj);
                                    Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT);
                                    intent.setComponent(targetIntent.getComponent());
    
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
    
                            }
                            break;
                            case 159: {
                                Object obj = msg.obj;
                                Log.i(TAG, "handleMessage: obj=" + obj);
                                try {
                                    Field mActivityCallbacksField = obj.getClass().getDeclaredField("mActivityCallbacks");
                                    mActivityCallbacksField.setAccessible(true);
                                    List mActivityCallbacks = (List)mActivityCallbacksField.get(obj);
                                    Log.i(TAG, "handleMessage: mActivityCallbacks= " + mActivityCallbacks);
                                    if(mActivityCallbacks.size()>0){
                                        Log.i(TAG, "handleMessage: size= " + mActivityCallbacks.size());
                                        String className = "android.app.servertransaction.LaunchActivityItem";
                                        if(mActivityCallbacks.get(0).getClass().getCanonicalName().equals(className)){
                                            Object object = mActivityCallbacks.get(0);
                                            Field intentField =object.getClass().getDeclaredField("mIntent");
                                            intentField.setAccessible(true);
                                            Intent intent = (Intent) intentField.get(object);
                                            Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT);
                                            intent.setComponent(targetIntent.getComponent());
                                        }
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
    
                            }
                            break;
                        }
                        mH.handleMessage(msg);
                        return true;
                    }
                });
    
            } catch (Exception e) {
                Log.e(TAG, "hookHandler: " + e.getMessage());
                e.printStackTrace();
            }
        }
    
    }
    
    
     public void onBtnHookClicked() {
            HookHelper.hookIActivityManager();
            HookHelper.hookHandler();
            Intent intent = new Intent(this,TestActivity.class);
            startActivity(intent);
        }
    

    运行以上代码,可以看到我们可以正常启动没有在 AndroidManifest 的 activity

    小结

    启动没有在 AndroidManifest 注册的 Activity 可以分为连个步骤

    • 1、在 AMS 通过 intent 校验 activity 是否注册的时候,用已经在 AndroidManifet 注册的 Activity 欺骗 AMS,绕过 原有 activity 的校验,并将原有的 intent 信息储存起来
    • 2、在 AMS 校验完毕的时候,通过 binder 告知我们的应用启动相应 activity 的时候,我们将 intent 的信息取出来,还原。

    参考文章:https://blog.csdn.net/gdutxiaoxu/article/details/81459910

    相关文章

      网友评论

        本文标题:Android插件化之Hook Activity

        本文链接:https://www.haomeiwen.com/subject/iximphtx.html