A preface.

Part of the content of this article comes from the book advanced Decryption of Android. The difference is that the hook in the book is implemented before Android 9.0. In Android 9.0, the startup process of the activity will be somewhat different, so this article will not make a specific analysis of the activity startup process. This is mainly about 9.0 hooks.

Two. Look for hook points

There are two hook points to look for: one is the replacement of an Activity in an Intent, and the other is the restoration of an Activity in an Intent

1. Replace the Activity

Android8 and 9 startup process, to obtain AMS remote call process is the same, here can be dynamic proxy for AMS, can also be proxy for Instrumentation, Android8 for AMS hook process is a little different from 9, There are too many hook articles for AMS on android8, here only introduces the AMS proxy class for 9

public class IActivityManagerProxy implements InvocationHandler {
    private Object activityManager;
    private static final String TAG="IActivityManagerProxy";
    public IActivityManagerProxy(Object activityManager){
        this.activityManager=activityManager;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("startActivity")){
            Intent intent =null;
            int index=0;
            for(int i=0; i<args.length; i++){if(args[i] instanceof Intent){
                    index=i;
                    break;
                }
            }
            intent = (Intent) args[index];
            String packageName = "com.suny.hooktest"; Intent subIntent = new Intent(); // Replace the activity with a registered pit activity, where the first parameter is the package in which the application resides. The second parameter is the package in which the Activity is located +activityName subIntent.setClassName(packageName,packageName+)".SubActivity"); // Store the actual intent in the subintent, bypass AMS, and replace the actual intent with subIntent.putextra ("target_intent",intent);
            args[index]=subIntent;
            Log.d(TAG, "invoke: subIntent="+subIntent+"inteent="+intent);
        }
        returnmethod.invoke(activityManager,args); }}Copy the code

Hook operation

public class HookHelper {
    public static void hookAms() throws Exception {
        Class clazz = ActivityManager.class;
        Field singletonIAMS = clazz.getDeclaredField("IActivityManagerSingleton");
        singletonIAMS.setAccessible(true);
        Object defultSingleton = singletonIAMS.get(null);
        Class singletonClazz = Class.forName("android.util.Singleton");
        Field mInstance = singletonClazz.getDeclaredField("mInstance");
        mInstance.setAccessible(true);
        Object iAMs = mInstance.get(defultSingleton);
        Class iAmClazz =Class.forName("android.app.IActivityManager"); Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{iAmClazz},new IActivityManagerProxy(iAMs)); mInstance.set(defultSingleton,proxy); }}Copy the code

2. The Activity reduction

Without further analysis of the startup process, the last step in the startup process is all the related methods in ActivityStackSupervisor that make the cross-process calls through The ApplicationThread, since the AMS cross-process calls are made

private class ApplicationThread extends IApplicationThread.Stub
Copy the code

If you are familiar with AIDL, you can see that this is the same structure as the classes that AIDL helped us generate

(1) Processing in.8.0

From ActivityStackSupervisor, call realStartActivityLocked

final boolean realStartActivityLocked (...) {... app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, 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, ! andResume, mService.isNextTransitionForward(), profilerInfo); . }Copy the code

AppThreadApplication sends a message through Handle

public final void scheduleLaunchActivity(...) {... sendMessage(H.LAUNCH_ACTIVITY, r); . }Copy the code

Processing of messages

public void handleMessage(Message msg) {
        
    switch (msg.what) {
        case LAUNCH_ACTIVITY: {
            final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

            r.packageInfo = getPackageInfoNoCheck(
                    r.activityInfo.applicationInfo, r.compatInfo);
            handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }
        break; .Copy the code

As you can see in Android8, restoring an Activity can be done here because the ActivityClientRecord contains the Intent variable

(2) processing in 9.0

The intent of finding

From ActivityStackSupervisor, call realStartActivityLocked

final boolean realStartActivityLocked (...) {... 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)); // Schedule transaction. mService.getLifecycleManager().scheduleTransaction(clientTransaction); . }Copy the code

MService. GetLifecycleManager () for ClientLifecycleManager, namely ClientLifecycleManager. ScheduleTrasaction method invocation process

Void scheduleTransaction(ClientTransaction Transaction) throws RemoteException {// Note that the Client is ApplicationThread Final  IApplicationThread client = transaction.getClient(); transaction.schedule();if(! (client instanceof Binder)) { // If client is not an instance of Binder - it's a remote call and at this point it is // safe to recycle the object. All objects used for local calls will be recycled after // the transaction is executed on client in ActivityThread. transaction.recycle(); }}Copy the code

It calls the ClientTransaction Schedule method

public void schedule() throws RemoteException {
    mClient.scheduleTransaction(this);
}
Copy the code

The mClient is called ApplicationThread, and the calling process is then called locally through cross-process method calls

public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
    ActivityThread.this.scheduleTransaction(transaction);
}
Copy the code

ActivityThread inherited ClientTransactionHandler here, also sent a message through the handler

void scheduleTransaction(ClientTransaction transaction) {
    transaction.preExecute(this);
    sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
}
Copy the code

Now look at the processing of the message

public void handleMessage(Message msg) {
    case EXECUTE_TRANSACTION:
        final ClientTransaction transaction = (ClientTransaction) msg.obj;
        mTransactionExecutor.execute(transaction);
        if (isSystem()) {
            // Client transactions inside system process are recycled on the client side
            // instead of ClientLifecycleManager to avoid being cleared before this
            // message is handled.
            transaction.recycle();
        }
        // TODO(lifecycler): Recycle locally scheduled transactions.
        break;
}
Copy the code

The class does not have an intent in it. However, the intent must be in it. Otherwise, how can ActivityThread parse the intent? Look for TransactionExecutor’s execute method along the method call

public void execute(ClientTransaction transaction) { ... executeCallbacks(transaction); executeLifecycleState(transaction); . }Copy the code

In the executeCallbacks method

public void executeCallbacks(ClientTransaction transaction) { ... final List<ClientTransactionItem> callbacks = transaction.getCallbacks(); .for(int i = 0; i < size; ++i) { ... final ClientTransactionItem item = callbacks.get(i); . item.execute(mTransactionHandler, token, mPendingActions); item.postExecute(mTransactionHandler, token, mPendingActions); . }}Copy the code

We found that item.execute() can be a key method in the startup process, where methods related to ActivityThread are called to complete the Activity creation, but this ClientTransactionItem is an abstract class. You have to find one of his implementation classes

public abstract class ClientTransactionItem implements BaseClientRequest, Parcelable
Copy the code

Remember during startup of 9, there is this code in the realStartActivityLocked method in ActivityStackSupervisor before scheduleTransaction is called

final boolean realStartActivityLocked (...) {... 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)); // Schedule transaction. mService.getLifecycleManager().scheduleTransaction(clientTransaction); . }Copy the code

You can see that clientTransaction adds a LaunchActivityItem implementation class and places the Intent in the implementation class. To sum up: In 9, the intent is in a member variable of the LaunchActivityItem implementation class that implements the element in the member variable List

mActivityCallbacks in ClientTransaction

A hook operation restores the intent

You hook the Handler(mH) member variable in the ActivityThread and set a callback

public class HookHelper {
    public static void hookHandler() throws Exception{
        Class acThreadClazz = Class.forName("android.app.ActivityThread");
        Field currentActivityThread = acThreadClazz.getDeclaredField("sCurrentActivityThread");
        currentActivityThread.setAccessible(true);
        Object currThread = currentActivityThread.get(null);
        Field mHField = acThreadClazz.getDeclaredField("mH");
        mHField.setAccessible(true);
        Handler mH = (Handler) mHField.get(currThread);
        Field mCallback = Handler.class.getDeclaredField("mCallback");
        mCallback.setAccessible(true); McAllback.set (mH,new HCallback(mH)); // Add McAllback.set (mH,new HCallback(mH)); }}Copy the code

Q: The sCurrentActivityThread is a static variable in ActivityThread, but the initial value is null. Does this result in the null value obtained by reflection? The sCurrentActivityThread is initialized in the attachBaseContext of the Application. The Value of the sCurrentActivityThread is assigned in the Main method of the ActivityThread. The attach method is used for assignment, and the application is created after assignment

Private void attach(Boolean system) {// Assign the value of sCurrentActivityThread = this; . // Application created here, after another remote call, will be calledbindApplication method, // initializes the ContentProvider and creates the Application, by the way, //ContentProvider's onCreate is executed before Application's onCreate. AttachApplication (mAppThread); // But attachBaseContext of the Application is executed before onCreate of the ContentProvider. } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); }}Copy the code

HCallbck

public class HCallback implements Handler.Callback{
    private final String TAG="HCallback"; private Handler mHandler; public HCallback(Handler handler){ mHandler=handler; } @override public Boolean handleMessage(Message MSG) {// The value of the EXECUTE_TRANSACTION field is 159if(msg.what==159){
            //r实际为clienttransaction
            Object r= msg.obj;
            try {
                Class clientClazz = r.getClass();
                Field fCallbacks = clientClazz.getDeclaredField("mActivityCallbacks");
                fCallbacks.setAccessible(true); // Get the Callbacks in Transactionz as a list with elements like LaunActivityItem list <? > lists = (List) fCallbacks.get(r);for(int i=0; i<lists.size(); i++){ Object item = lists.get(i); Class itemClazz = item.getClass(); / / get the LaunActivityItem intent, to replace Field mIntent = itemClazz. GetDeclaredField ("mIntent");
                    mIntent.setAccessible(true);
                    Intent intent = (Intent) mIntent.get(item);
                    Intent target = intent.getParcelableExtra("target_intent");
                    if(target! =null){ intent.setComponent(target.getComponent()); } } } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } mHandler.handleMessage(msg);return true; }}Copy the code

The last

Initialize it in application

public class MyApplication extends Application { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { HookHelper.hookAms(); HookHelper.hookHandler(); } catch (Exception e) { e.printStackTrace(); }}}Copy the code

Think three.

In fact, hookActivity is mainly used to bypass AMS validation, so it can be replaced with an intent before calling THE AMS startActivity method, and can be restored with an intent after AMS. Therefore, there are many hook methods. Instrumenting an intent, instrumenting an intent, instrumenting an intent, instrumenting an intent, instrumenting an intent, instrumenting an intent, instrumenting an intent, instrumenting an intent, instrumenting an intent, instrumenting an intent, instrumenting an intent. Because there is a static variable sCurrentActivityThread in ActivityThread, you can get an ActivityThread object that can change mH, and maybe a better hook point