【SDK热更系列】Android Hook 技术之 绕过系统对Activity验证

【SDK热更系列】Android Hook 技术之 绕过系统对Activity验证

前言:我为啥要搞这玩意儿呢?原因如下:
1. 需求,还是xxxx的需求问题
2. 公司主要的SDK项目需要实现SDK级别的热更新功能
3. 市场上所有的组件化、插件化类的项目有如下缺点

     1. 对主项目工程的依赖太大, 往往一些基本配置都要依赖于主要工程的项目源码才行
     2. 使用接入成本高:配置麻烦,而SDK接入方需要的是快速接入
     3. 插件化框架对系统原生代码的运行会造成不可预估的影响
     4. 有很多不需要的功能,但是属于框架的部分,不能不依赖

基于以上原因,我打算自己搞一个这种简易型,没有这么多负责功能的SDK热更框架,而SDK热更无可避免的遇到Activity必须要在AndroidManifest.xml注册才能使用的阻碍,今天我们就来解决这个坑点

主要思路是我们预先在AndroidManifest.xml 注册一个代理的Activity,如下所示

        <activity
            android:name="com.pudding.hook.proxy.GenProxyActivity"
            android:configChanges="orientation|keyboardHidden|navigation|screenSize"/>

但是我们要启动的Activity 是 UserCenterActivity,系统理论上是要去AndroidManifest.xml去找UserCenterActivity是否有注册的,这个时候我们将系统去寻找UserCenterActivity 这个动作导向 去寻找GenProxyActivity是否存在,这样就绕过了系统的Activity验证。主要采用的技术是Hook技术

1. 分析Activity启动源码

在我们的Activity中调用startActivity方法,会执行Activity中的startActivity, 然后在Activity中的startActivity方法体里调用了startActivity的重载方法,这里我们看一下其重载方法的实现:

    @Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            // Note we want to go through this call for compatibility with
            // applications that may have overridden the method.
            startActivityForResult(intent, -1);
        }
    }

好吧,我们继续往下看,startActivityForResult方法的具体实现:

public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
        if (mParent == null) {
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {
                // If this start is requesting a result, we can avoid making
                // the activity visible until the result is received.  Setting
                // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
                // activity hidden during this time, to avoid flickering.
                // This can only be done when a result is requested because
                // that guarantees we will get information back when the
                // activity is finished, no matter what happens to it.
                mStartedActivity = true;
            }

            cancelInputsAndStartExitTransition(options);
            // TODO Consider clearing/flushing other event sources and events for child windows.
        } else {
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                // Note we want to go through this method for compatibility with
                // existing applications that may have overridden it.
                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }

可以发现由于我们是第一次启动Activity,所以这里的mParent为空,所以会执行if分之,然后调用mInstrumentation.execStartActivity方法,并且这里需要注意的是,有一个判断逻辑:

if (requestCode >= 0) {
    mStartedActivity = true;
}

通过注释也验证了我们刚刚的说法即,调用startActivityForResult的时候只有requestCode的值大于等于0,onActivityResult才会被回调。

然后我们看一下mInstrumentation.execStartActivity方法的实现。在查看execStartActivity方法之前,我们需要对mInstrumentation对象有一个了解?什么是Instrumentation?Instrumentation是android系统中启动Activity的一个实际操作类,也就是说Activity在应用进程端的启动实际上就是Instrumentation执行的,那么为什么说是在应用进程端的启动呢?实际上acitivty的启动分为应用进程端的启动和SystemServer服务进程端的启动的,多个应用进程相互配合最终完成了Activity在系统中的启动的,而在应用进程端的启动实际的操作类就是Intrumentation来执行的,可能还是有点绕口,没关系,随着我们慢慢的解析大家就会对Instrumentation的认识逐渐加深的。

可以发现execStartActivity方法传递的几个参数:
this,为启动Activity的对象;
contextThread,为Binder对象,是主进程的context对象;
token,也是一个Binder对象,指向了服务端一个ActivityRecord对象;
target,为启动的Activity;
intent,启动的Intent对象;
requestCode,请求码;
options,参数;

这样就调用了Imstrument.execStartActivity方法了:

ublic ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        ...
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess();
            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;
    }

我们发现在这个方法中主要调用ActivityManagerNative.getDefault().startActivity方法,那么ActivityManagerNative又是个什么鬼呢?查看一下getDefault()对象的实现:

static public IActivityManager getDefault() {
        return gDefault.get();
    }

好吧,相当之简单直接返回的是gDefault.get(),那么gDefault又是什么呢?

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;
        }
    };

可以发现启动过asInterface()方法创建,然后我们继续看一下asInterface方法的实现:

static public IActivityManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IActivityManager in =
            (IActivityManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }

        return new ActivityManagerProxy(obj);
    }

由以上代码我们可以知道我们实际上是要找到 ActivityManagerNative里面的gDefault,然后替换他的mInstance 属性,达到真正去替换启动Activity的目的, 替换代码如下:

public void hookStartActivity() throws Exception {
        if (Build.VERSION.SDK_INT < 26) {
            // 3.1 获取ActivityManagerNative里面的gDefault
            Class<?> amnClass = Class.forName("android.app.ActivityManagerNative");

            Object gDefault;
            Field gDefaultField = amnClass.getDeclaredField("gDefault");
            // 设置权限
            gDefaultField.setAccessible(true);
            gDefault = gDefaultField.get(null);
            // 3.2 获取gDefault中的mInstance属性
            // mInstanceField 就是 android.util.Singleton 对象中的 mInstance 属性,此时还是一个属性
            // mInstance 属性 实际上是一个 类别为 IActivityManager 的属性
            // iamInstance 则是从 mInstance 属性中获取到的 IActivityManager 对象
            Class<?> singletonClass = Class.forName("android.util.Singleton");
            Field mInstanceField = singletonClass.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            Object iamInstance = mInstanceField.get(gDefault);

            // 重新依照原来的 iamInstance(IActivityManager 对象) 定义一个能够被我们自己 Hook 指向的 iamInstance
            Class<?> iamClass = Class.forName("android.app.IActivityManager");
            iamInstance = Proxy.newProxyInstance(HookStartActivityUtil.class.getClassLoader(),
                    new Class[]{iamClass},
                    // InvocationHandler 必须执行者,谁去执行
                    new StartActivityInvocationHandler(iamInstance));

            // 3.重新指定
            // 实际上是要把原来的 IActivityManager 对象替换成上面我们自己定义可以支持 Hook 的新 IActivityManager 对象
            mInstanceField.set(gDefault, iamInstance);
        } else {
            // 3.1 获取ActivityManager里面的IActivityManagerSingleton
            Class<?> amnClass = Class.forName("android.app.ActivityManager");

            Object gDefault;
            Field gDefaultField = amnClass.getDeclaredField("IActivityManagerSingleton");
            // 设置权限
            gDefaultField.setAccessible(true);
            gDefault = gDefaultField.get(null);
            // 3.2 获取gDefault中的mInstance属性
            // mInstanceField 就是 android.util.Singleton 对象中的 mInstance 属性,此时还是一个属性
            // mInstance 属性 实际上是一个 类别为 IActivityManager 的属性
            // iamInstance 则是从 mInstance 属性中获取到的 IActivityManager 对象
            Class<?> singletonClass = Class.forName("android.util.Singleton");
            Field mInstanceField = singletonClass.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            Object iamInstance = mInstanceField.get(gDefault);

            // 重新依照原来的 iamInstance(IActivityManager 对象) 定义一个能够被我们自己 Hook 指向的 iamInstance
            Class<?> iamClass = Class.forName("android.app.IActivityManager");
            iamInstance = Proxy.newProxyInstance(HookStartActivityUtil.class.getClassLoader(),
                    new Class[]{iamClass},
                    // InvocationHandler 必须执行者,谁去执行
                    new StartActivityInvocationHandler(iamInstance));

            // 3.重新指定
            // 实际上是要把原来的 IActivityManager 对象替换成上面我们自己定义可以支持 Hook 的新 IActivityManager 对象
            mInstanceField.set(gDefault, iamInstance);
        }


    }

接下来替换我们启动的Activty class,让系统去验证另外一个Activity对象在AndroidManifest.xml是否存在,类似360插件化框架的占坑思想

private class StartActivityInvocationHandler implements InvocationHandler {
        // 方法执行者
        private Object mObject;

        public StartActivityInvocationHandler(Object object) {
            this.mObject = object;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Log.e(TAG, method.getName());
            // 替换Intent,过AndroidManifest.xml检测
            if (method.getName().equals("startActivity")) {
                // 1.首先获取原来的Intent
                if (args[2] instanceof Intent) {
                    Intent originIntent = (Intent) args[2];
                    if (originIntent.getComponent() != null) {
                        Log.e(TAG, originIntent.getComponent().getClassName());
                        // 2.创建一个安全的可以被启动的Activity 的 Class
                        Class mProxyClass = ProxyUtils.getPluginActivityClass(originIntent.getComponent().getClassName());
                        if (mProxyClass != null) {
                            Intent safeIntent = new Intent(mContext, mProxyClass);
                            args[2] = safeIntent;
                            // 3.绑定原来的Intent
                            safeIntent.putExtra(EXTRA_ORIGIN_INTENT, originIntent);
                        }
                    }
                }
            }
            return method.invoke(mObject, args);
        }
    }

基本思路如上所示,经过试验可以使用,Android版本支持情况:

RuntimeAndroid VersionSupport
Dalvik2.2Yes
Dalvik2.3Yes
Dalvik3.0Yes
Dalvik4.0-4.4Yes
ARTL (5.0)Yes
ARTL MR1 (5.1)Yes
ARTM (6.0)Yes
ARTN (7.0)Yes
ARTN MR1 (7.1)Yes
ARTO (8.0)Yes
ARTO MR1(8.1)Yes

Demo献上:https://download.csdn.net/download/u012573920/10549527

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值