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