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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章