Android中插件化实现的原理,hook Activity(二)

https://blog.csdn.net/lin20044140410/article/details/104204109

继续分析Android中插件化实现的原理

这里的场景是通过Java层的Hook技术,实现Activity插件化,以api29为例,如果其他的api版本,需要根据具体代码做兼容.

Hook技术,通常就是用反射,代理模式改变系统的调用流程,或者说拦截事件的传递,做一些特定的处理.这样就可以在应用进程,通过hook技术改变系统进程的执行流程.

要实现Hook,就要先找到Hook的点,Hook点的选择,尽量是静态变量,单例,并且是public的对象和方法,因为这些是不容易变化的属性.

找到Hook点后,就可以通过代理方式,用代理对象替换原始对象.

下面的测试场景,是通过Hook启动一个没有注册过的Activity.具体就是startActivity传入的activity信息没有在manifext中注册,正常的启动流程肯定会报类找不到的异常.通过hook技术,就是可以逃过ams的这些权限检查,最后正常的启动这个没有注册的activity.

Activity的启动过程,这里不详细分析.

Activity的启动过程中,从

public void startActivity(Intent intent, @Nullable Bundle options) @Activity.java{}开始,往下的流程,都会调用到:

   public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, String resultWho,
            Intent intent, int requestCode, Bundle options, UserHandle user) @Instrumentation.java{
//这里ctivityManager.getService()就是我们要找的一个Hook点,它是一个单例,是一个接口,
//又是一个static方法,符合我们找hook点条件.
            int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
 
}


找到这个Hook点后,就考虑用代理方式去hook它,那是用静态代理,还是动态代理呢?因为这里ActivityManager.getService()返回的值是IActivityManager,是一个接口,所以适合用动态代理实现.

代理类中,必然要保存一个真实对象的引用,这里的真实对象就是ActivityManager.getService()返回的真实引用.

下面就 看具体代码实现:

注释已经加在了代码中:

//当前实现基于api29.

//当前实现基于api29.
public class HookActivityUtil {
    private static final String TAG = HookActivityUtil.class.getName();
    private static final String REPLACE_INTENT = "hook.replace.intent";
 
    public static void hookIActivityManager() {
        try {
            Class<?> activityManager = Class.forName("android.app.ActivityManager");
            //通过getService方法拿到IActivityManager对象,
            Method getServiceMethod = activityManager.getDeclaredMethod("getService",
                    new Class[]{});
            Object realIActivityManagerTmp = getServiceMethod.invoke(null);
 
            //还有一种方法拿到IActivityManager对象,先拿到IActivityManagerSingleton这个单例类,
            // 然后从这个单例中拿到他代表的实例.之所以要使用下面这个方式来获取,
            // 是因为后面要用具体的代理类来替换掉IActivityManager这个对象.
 
            //先拿到单例变量IActivityManagerSingleton,
            Field iActivityManagerSingletonField =
                    activityManager.getDeclaredField("IActivityManagerSingleton");
            iActivityManagerSingletonField.setAccessible(true);
 
            //拿到这个field代表的一个对象. Singleton<IActivityManager>类型的
            Object singletonObj =  iActivityManagerSingletonField.get(null);
 
            Class<?> singletonClass = Class.forName("android.util.Singleton");
            Field mInstanceField = singletonClass.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            //获取单例中代表的真正的IActivityManager对象
            Object realIActivityManager = mInstanceField.get(singletonObj);
 
            Log.d(TAG,"hookIActivityManager,Build.VERSION.SDK_INT :"+ (Build.VERSION.SDK_INT)
                    +  ",realIActivityManager="+realIActivityManager);
 
            //应用动态代理
            //先拿到类加载器
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            //拿到代理类需要实现的接口IActivityManager,这个接口是通过aidl工具生成的.
            Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
            //创建具体的realIActivityManager的代理对象
            Object proxy = Proxy.newProxyInstance(cl,
                    new Class[]{iActivityManagerInterface},
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method,
                                             Object[] args) throws Throwable {
                            Log.d(TAG,"hookIActivityManager,proxy.invode:method="+method.getName());
                            //这里是hook的是 startactivity方法.通过替换args中的参数,达到跳过ams权限的检查,
                            // 启动没有注册的activity,
                            // 这里要启动的组件也可以是插件中自定义的,并通过类加载器加载进来
                            //args参数中的信息,可以通过log查看
                            //其他方法的调用不做处理
                            if ("startActivity".equals(method.getName())) {
                                int index = 0;
                                //被替换的intent信息先保存下,因为这个realIntent才是最终要启动的,
                                // 下面做替换只是为了骗过ams.
                                Intent realIntent = null;
                                for (; index < args.length; index++) {
                                    Log.d(TAG, "hookIActivityManager,proxy.invode:args["
                                            + index + "]=" + args[index]);
                                    if (args[index] instanceof Intent) {
                                        realIntent = (Intent) args[index];
                                        break;
                                    }
                                }
                                //替换要启动的Activity就是替换args[2]的intent参数,
                                //这里的包类名是将要执行的activity的.这个activity肯定是正常注册过的,
                                // 这样才能在接下来的权限检查中正常通过
                                Intent newIntent = new Intent();
//下面是activity的包名,类名,这个activity必须是正常注册过的.
                                String packageName = "";
                                String className = "";
                                newIntent.setComponent(new ComponentName(packageName, className));
                                //原来的intent带进去,以备后面恢复使用.
                                newIntent.putExtra(REPLACE_INTENT, realIntent);
                                args[index] = newIntent;
                            }
 
                            return method.invoke(realIActivityManager,args);
                        }
            });
 
            //做对象替换,用具体代理类,替换原来的IActivityManager对象.也就是重新设置单例中代表的对象.
            mInstanceField.set(singletonObj, proxy);
 
 
        } catch (ClassNotFoundException | NoSuchFieldException e) {
            Log.d(TAG,"11:"+e.getMessage());
        } catch (Exception e) {
            Log.d(TAG,"hookIActivityManager:"+e.getMessage());
            e.printStackTrace();
        }
    }
}


//上面的操作就可以跳过ams的权限检查了,接着就要把真正要启动的activity在替换回来.
//那应该在什么时候在替换回来呢?
//AMS完成activity的检查,及应用进程的启动后,会发一个启动activity的binder请求给应用进程.
//因为真正去启动一个activity是应用进程自己的事情,所以这个操作一定是在应用进程的处理逻辑中,
// 具体就是ActivityThread中的处理过程.
//那应用进程这边要hook的点,是哪里呢?
// 首先,hook点选择的一个原则,尽量是final,static,public的这类不容易变的属性,
// 其次,ams发过来的启动Activity请求,最早是由ActivityThread中mH来处理的,同时mH也符合hook点选择的条件.
//final H mH = new H();所以选择mH做应用进程这边的hook点.
//最后在选好hook点后,该如何把真正要启动的Activity替换回来呢?这就要看Handler处理消息的流程了
/*    public void dispatchMessage(Message msg) @Handler.java{
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }*/
//消息的处理,有一定的优先级,首先是msg自己的callback,这个不好去改动,其次是Handler中mCallback接口,
// 最后是自定义Handler重写的handleMessage方法
//这三级的处理流程中,Handler本身的mCallback接口,是最容易被使用的.所以我们就在这个mCallback中实现把
// 真实的Activity信息替换回来.
//当前实现基于api29.

//当前实现基于api29.
public class HookActivityUtil {
    private static final String TAG = HookActivityUtil.class.getName();
    private static final String REPLACE_INTENT = "hook.replace.intent";
public static void hookATHandler(){
        try {
            Class<?> activityThread = Class.forName("android.app.ActivityThread");
            //拿到当前主线程的实例对象,private static volatile ActivityThread sCurrentActivityThread;
            Field sCurrentActivityThreadField =
                    activityThread.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadField.setAccessible(true);
            //sCurrentActivityThread是一个static的,所以参数给null即可
            Object sCurrentActivityThread = sCurrentActivityThreadField.get(null);
 
            //拿到ActivityThread中mH对象
            Field mHField = activityThread.getDeclaredField("mH");
            mHField.setAccessible(true);
            Handler mH = (Handler) mHField.get(sCurrentActivityThread);
 
            //拿到Handler中mCallback
            Field mCallbackField = Handler.class.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);
            //为这个mH设置一个Callback.在这个Callback中还原Activity,
            //这里还有一个逻辑,在这个callback中,只是还原了真实的Activity,接下来ActivityThread中的mH的处理流程,如:
            // performLaunchActivity, performResumeActivity,等
            // 我们是不做修改的,所以在执行完这个Callback后,还是调用mH的handleMessage方法
            mCallbackField.set(mH, new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    Log.d(TAG, "hookATHandler,msg=" + msg);
                    //这个code:public static final int EXECUTE_TRANSACTION = 159;是ams请求启动activity的.
                    switch (msg.what) {
                        case 159: {
                            try {
                                //下面要做的事情,就是把Intent中put进去的真正要启动的activiyt的intent信息在拿出来.
                                // 怎么拿呢?可以打印msg.obj的信息看,intent保存的位置.
                                //具体为什么这么拿,在代码外面做解释
                                Object object = msg.obj;
                                Log.d(TAG, "hookATHandler,object=" + object);
                                //拿到ClientTransaction中的列表:mActivityCallbacks
                                Field mActivityCallbacksField =
                                        object.getClass().getDeclaredField("mActivityCallbacks");
                                mActivityCallbacksField.setAccessible(true);
                                List<Object> mActivityCallbacks = (List<Object>)mActivityCallbacksField.get(object);
                                //拿到LaunchActivityItem的实例对象.
                                String itemName = "android.app.servertransaction.LaunchActivityItem";
                                for(Object obj : mActivityCallbacks) {
                                    if (obj.getClass().getCanonicalName().equals(itemName)) {
                                        //拿到LaunchActivityItem中的mIntent.
                                        Field mIntentField = obj.getClass().getDeclaredField("mIntent");
                                        mIntentField.setAccessible(true);
                                        Intent sugerBullet = (Intent)mIntentField.get(obj);
                                        Intent realIntent = sugerBullet.getParcelableExtra(REPLACE_INTENT);
                                        //把真实的Activity信息写回去
                                        sugerBullet.setComponent(realIntent.getComponent());
                                        break;
                                    }
                                }
 
                            } catch (NoSuchFieldException | IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                        break;
 
                        default:
                            break;
                    }
 
                    //处理完,返回true
                    mH.handleMessage(msg);
                    return true;
                }
            });
 
        } catch (ClassNotFoundException | NoSuchFieldException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
 
}


怎么在应用进程中拿回来intent中保存的那个真实的activity信息呢?

从ActivityThread的启动Activity的流程看起:

class H extends Handler #ActivityThread.java{
	public void handleMessage(Message msg) {
		case EXECUTE_TRANSACTION:
//intent信息是保存在 ClientTransaction对象中,具体怎么保存的,这个有点深,要跟进去再看
		                   final ClientTransaction transaction = (ClientTransaction) msg.obj;
                    mTransactionExecutor.execute(transaction);
	}
}


从mTransactionExecutor.execute(transaction);的处理看,intent信息在这个列表相关的元素中:

//final List<ClientTransactionItem> callbacks = transaction.getCallbacks();

 public void execute(ClientTransaction transaction) #TransactionExecutor.java{
    executeCallbacks(transaction);
}
这个列表中元素表示不同状态的activity,具体有那些状态呢?可以从ClientTransactionItem的实现类看出:

以activity的生命周期来分析,我们关注:

LaunchActivityItem;

PauseActivityItem;

ResumeActivityItem;

public class ClientTransaction implements Parcelable, ObjectPoolItem #ClientTransaction.java{
    private List<ClientTransactionItem> mActivityCallbacks;
}
对于一个新启动的activity,需要的intent信息,就是LaunchActivityItem中的intent.这一点也可以从ams侧realStartActivityLocked 的处理得要验证, realStartActivityLocked这个函数名字起的很贴切,就是启动一个Activity的前期准备,检查都没问题了,接下来才是真的去启动一个Activity.

final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
            boolean andResume, boolean checkConfig) throws RemoteException #ActivityStackSupervisor.java{
                // Create activity launch transaction.
                final ClientTransaction clientTransaction = ClientTransaction.obtain(app.thread,
                        r.appToken);
                clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                        System.identityHashCode(r), r.info,
                        // TODO: Have this take the merged configuration instead of separate global
                        // and override configs.
                        mergedConfiguration.getGlobalConfiguration(),
                        mergedConfiguration.getOverrideConfiguration(), r.compat,
                        r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
                        r.persistentState, results, newIntents, mService.isNextTransitionForward(),
                        profilerInfo));
}


从上面的实现可以看出,插件化的原理实际就是代理模式加上反射,但是真正需要注意的地方,是对要hook的源码熟悉,要怎样选好hook点,要调整什么样的处理流程.

这个例子的使用就是在startactivity前,调用下这两个hook方法即可.

     HookActivityUtil.hookIActivityManager();
        HookActivityUtil.hookATHandler();
        Intent intent = new Intent(this,NotRegisterActivity.class);
        startActivity(intent);
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值