Android开发经验·源码分析

Android插件化之Hook Activity

2020-04-06  本文已影响0人  程序员三千_

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

本文以 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 可以分为连个步骤

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

上一篇下一篇

猜你喜欢

热点阅读