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