Android Hook tells you how to start an unregistered Activity

preface

Android Hook plug-in is not really what new technology, I wonder if you have thought, so many small software in Alipay: panning ticket, train tickets and other software, is Alipay software written by itself? It must not be written for ten years. The software has reached tens of gigabytes, but there are not so many skin packages when playing games. Users are willing to download which skin package they use regularly.

Can an Activity not registered in the configuration file be started?

I learned from 0 that the Activity must be registered in the configuration file, otherwise it cannot be started and an error is reported. But Hook tells you that an Activity can be started if it is not registered in the configuration file. Are you surprised? Are you surprised?

Through this article, you can learn:

1. Add a log for the startActivity method by hooking the startActivity method.

1.1 Hook the Instrumentation

1.2 Hook AMN

2. How to start an Activity that is not registered in the configuration file to realize plug-in

This article is based on Java reflection mechanism and App startup process analysis , it is suggested that little friends who don't know much can move to these two articles first.

Second, Hook the startActivity method

By consulting the source code of startActivity, we can see that startActivity will eventually go to the startactivityfoesult method

public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
    if(this.mParent == null) {
        ActivityResult ar = this.mInstrumentation.execStartActivity(this, this.mMainThread.getApplicationThread(), this.mToken, this, intent, requestCode, options);
        if(ar != null) {
            this.mMainThread.sendActivityResult(this.mToken, this.mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
        }

        if(requestCode >= 0) {
            this.mStartedActivity = true;
        }
    } else if(options != null) {
        this.mParent.startActivityFromChild(this, intent, requestCode, options);
    } else {
        this.mParent.startActivityFromChild(this, intent, requestCode);
    }

}

Via mimstrumentation Execstartactivity call (ps: detailed source code analysis has been explained in the previous article), and then see mminstrumentation The source code of execstartactivity method is as follows:

public Instrumentation.ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
    IApplicationThread whoThread = (IApplicationThread)contextThread;
    if(this.mActivityMonitors != null) {
        Object e = this.mSync;
        synchronized(this.mSync) {
            int N = this.mActivityMonitors.size();

            for(int i = 0; i < N; ++i) {
                Instrumentation.ActivityMonitor am = (Instrumentation.ActivityMonitor)this.mActivityMonitors.get(i);
                if(am.match(who, (Activity)null, intent)) {
                    ++am.mHits;
                    if(am.isBlocking()) {
                        return requestCode >= 0?am.getResult():null;
                    }
                    break;
                }
            }
        }
    }

    try {
        intent.setAllowFds(false);
        intent.migrateExtraStreamToClipData();
        int var16 = ActivityManagerNative.getDefault().startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null?target.mEmbeddedID:null, requestCode, 0, (String)null, (ParcelFileDescriptor)null, options);
        checkStartActivityResult(var16, intent);
    } catch (RemoteException var14) {
        ;
    }

    return null;
}

Finally, it will be handed over to int var16 = activitymanagernative getDefault(). startActivity (whothread, intent,...), so if we want to Hook the startActivity method, we can start from these two places (in fact, there are more than these two places, we only explain them, and the reflection encapsulation class used below is also given in the previous article).

  • 2.1 Hook mminstrumentation

In activity Private variables are defined in the class

private Instrumentation mInstrumentation;

The first thing we need to do is to modify the value of the private variable and print a line of log before executing the method. First, we get the private variable through reflection.

Instrumentation instrumentation = (Instrumentation) Reflex.getFieldObject(Activity.class,MainActivity.this,"mInstrumentation");

What we need to do is to replace this Instrumentation with our own Instrumentation, so let's create a new MyInstrumentation that inherits from Instrumentation, and the execStartActivity method of MyInstrumentation remains unchanged.

public class MyInstrumentation extends Instrumentation {

    private Instrumentation instrumentation;

    public MyInstrumentation(Instrumentation instrumentation) {
        this.instrumentation = instrumentation;
    }

    public  ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {


        Log.d("-----","Lala, I am hook Come in!");
        Class[] classes = {Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class};
        Object[] objects = {who,contextThread,token,target,intent,requestCode,options};
        Log.d("-----","Lala, I am hook Come in!!");
        return (ActivityResult) Reflex.invokeInstanceMethod(instrumentation,"execStartActivity",classes,objects);

    }

We call this method directly through reflection. The parameters are Class[] classes = {Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class}, which is consistent with the method name

(ActivityResult) Reflex.invokeInstanceMethod(instrumentation,"execStartActivity",classes,objects)

If we do not call its own execStartActivity method here, the startActivity will be invalid.

Then we replace the customized with the original Instrumentation

Reflex.setFieldObject(Activity.class,this,"mInstrumentation",instrumentation1);

The complete code is

Instrumentation instrumentation = (Instrumentation) Reflex.getFieldObject(Activity.class,MainActivity.this,"mInstrumentation");
MyInstrumentation instrumentation1 = new MyInstrumentation(instrumentation);
Reflex.setFieldObject(Activity.class,this,"mInstrumentation",instrumentation1);

The operation log is as follows:

At this time, we successfully Hook the startActivity method

2.2 Hook AMN

The execStartActivity method will eventually go to activitymanagernative getDefault(). Startactivity method

try {
    intent.setAllowFds(false);
    intent.migrateExtraStreamToClipData();
    int var16 = ActivityManagerNative.getDefault().startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null?target.mEmbeddedID:null, requestCode, 0, (String)null, (ParcelFileDescriptor)null, options);
    checkStartActivityResult(var16, intent);
} catch (RemoteException var14) {
    ;
}

You might say that as mentioned above, replace activitymanagernative Getdefault(), overriding the startActivity method, let's look at activitymanagernative getDefault()

public static IActivityManager getDefault() {
    return (IActivityManager)gDefault.get();
}
public final T get() {
    synchronized(this) {
        if(this.mInstance == null) {
            this.mInstance = this.create();
        }

        return this.mInstance;
    }
}

You can see that IActivityManager is an interface, gdefault Get () returns a generic type. We can't start with the above scheme, so we'll use the dynamic proxy scheme here

We define an AmsHookHelperUtils class to handle reflection code in the AmsHookHelperUtils class

gDefault is a field of final static type. First, we get the gDefault field

Object gDefault = Reflex.getStaticFieldObject("android.app.ActivityManagerNative","gDefault");

gDefault is an object of type Singleton < iactivitymanager >, and Singleton is a Singleton mode

public abstract class Singleton<T> {
    private T mInstance;

    public Singleton() {
    }

    protected abstract T create();

    public final T get() {
        synchronized(this) {
            if(this.mInstance == null) {
                this.mInstance = this.create();
            }

            return this.mInstance;
        }
    }
}

Next, let's take out the msinstance field

Object mInstance = Reflex.getFieldObject("android.util.Singleton",gDefault,"mInstance");

Then create a proxy object

Class<?> classInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(classInterface.getClassLoader(),
        new Class<?>[]{classInterface},new AMNInvocationHanlder(mInstance));

Our proxy object is new AMNInvocationHanlder(mInstance), (ps: the proxy mode is divided into static proxy and dynamic proxy. If you don't know about the proxy mode, you can baidu Yibo or follow me and wait for my proxy mode related articles)

public class AMNInvocationHanlder implements InvocationHandler {


    private String actionName = "startActivity";

    private Object target;

    public AMNInvocationHanlder(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if (method.getName().equals(actionName)){
            Log.d("---","Lala, I am hook AMN Come in");
            return method.invoke(target,args);
        }

        return method.invoke(target,args);
    }
}

All proxy classes should implement the InvocationHandler interface, and method. In the invoke method invoke(target,args); It means to execute the method corresponding to the proxy object. Because AMN Singleton does a lot of things, only the startActivity method hook is used here

if (method.getName().equals(actionName)){
    Log.d("---","Lala, I am hook AMN Come in");
    return method.invoke(target,args);
}

Then we replace the gDefault field with our proxy class

Reflex.setFieldObject("android.util.Singleton",gDefault,"mInstance",proxy);

The overall method of AmsHookHelperUtils is as follows:

public class AmsHookHelperUtils {

    public static void hookAmn() throws ClassNotFoundException {
        Object gDefault = Reflex.getStaticFieldObject("android.app.ActivityManagerNative","gDefault");
        Object mInstance = Reflex.getFieldObject("android.util.Singleton",gDefault,"mInstance");

        Class<?> classInterface = Class.forName("android.app.IActivityManager");
        Object proxy = Proxy.newProxyInstance(classInterface.getClassLoader(),
                new Class<?>[]{classInterface},new AMNInvocationHanlder(mInstance));
        Reflex.setFieldObject("android.util.Singleton",gDefault,"mInstance",proxy);
    }
}

We call amshookhelperutils hookAmn(); Then start a new Activity, and the running log is as follows:

In this way, we successfully Hook the getDefault method of AMN.

2.3 how to start an unregistered Activity

How to start an unregistered Activity? First, let's understand the startup process of Activity. The startup process of App has been explained in the previous article, APP startup process analysis , if you don't know anything about it, you can move to the previous article first. Suppose there are mainactivities, Main2Activity and Main3Activity, where Main3Activity is not registered, we start Main3Activity in MainActivity. When starting Main3Activity, AMS will check whether there is configuration information of Main3Activity in the configuration file. If it does not exist, an error will be reported, and if it exists, start Main3Activity.

So what we can do is to replace the unregistered Activity Main2Activity with the Activity to be started before sending the Activity to AMS, so that AMS can pass the inspection. When AMS wants to start the target Activity, we can replace the Main2Activity with the Activity to be started.

First, we Hook the startActivity method according to the above logic. Here, we use AMN Hook. Like the above code, the difference is that the proxy class of mInstance is different.

Create a new AMNInvocationHanlder1 object, which also inherits from InvocationHandler and only intercepts the startActivity method.

if (method.getName().equals(actionName)){}

What we need to do here is to replace the Main3Activity to be started with Main2Activity, so as to bypass the verification of AMS. First, we take the target Activity from the target method.

Intent intent;
int index = 0;
for (int i = 0;i<args.length;i++){
    if (args[i] instanceof Intent){
        index = i;
        break;
    }
}

You may ask how you know that there must be an intent parameter in args, because the invoke method will eventually execute

return method.invoke(target,args);

Indicates that the original method will be executed. Let's look at the original startActivity method as follows:

int var16 = ActivityManagerNative.getDefault().startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null?target.mEmbeddedID:null, requestCode, 0, (String)null, (ParcelFileDescriptor)null, options);

So we say that there must be an intent parameter in args. After obtaining the real target Activity, we get the package name of the target

intent = (Intent) args[index];
String packageName = intent.getComponent().getPackageName();

Create a new intent and set the intent as the relevant information of the impersonator Main2Activity

Intent newIntent = new Intent();
ComponentName componentName = new ComponentName(packageName,Main2Activity.class.getName());
newIntent.setComponent(componentName);
args[index] = newIntent;

In this way, the target Activity is replaced with Main2Activity, but the impersonator must carry the original target and replace it when it is really opened, otherwise the impersonator will really start

newIntent.putExtra(AmsHookHelperUtils.TUREINTENT,intent);

At this time, we call this method and do nothing. At this time, we start Main3Activity

startActivity(new Intent(this,Main3Activity.class));

In fact, Main2Activity is displayed, as shown in the figure:

This shows that our impersonator has successfully replaced the real target, so we need to replace the impersonator with the target again at startup, and the ActivityThread sends a message to AMS through mH

synchronized(this) {
    Message msg = Message.obtain();
    msg.what = what;
    msg.obj = obj;
    msg.arg1 = arg1;
    msg.arg2 = arg2;
    this.mH.sendMessage(msg);
}

AMS will process the message after receiving it

public void handleMessage(Message msg) {
    ActivityThread.ActivityClientRecord data;
    switch(msg.what) {
    case 100:
        Trace.traceBegin(64L, "activityStart");
        data = (ActivityThread.ActivityClientRecord)msg.obj;
        data.packageInfo = ActivityThread.this.getPackageInfoNoCheck(data.activityInfo.applicationInfo, data.compatInfo);
        ActivityThread.this.handleLaunchActivity(data, (Intent)null);
        Trace.traceEnd(64L);

mH is a Handler type message processing class, so the sendMessage method will call callback. See my previous blog for the Handler message processing mechanism

In depth understanding of Android messaging mechanism So we can Hook the callback field.

Create a new hookActivityThread method. First, we get the current ActivityThread object

Object currentActivityThread = Reflex.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");

Then get the mH object of the object

Handler mH = (Handler) Reflex.getFieldObject(currentActivityThread, "mH");

Replace mH with our own custom MyCallback.

Reflex.setFieldObject(Handler.class, mH, "mCallback", new MyCallback(mH));

Customize MyCallback handler first Callback interface, reprocess the handleMessage method

@Override
public boolean handleMessage(Message msg) {

    switch (msg.what) {
      
        case 100:
            handleLaunchActivity(msg);
            break;
        default:
            break;

    }

    mBase.handleMessage(msg);
    return true;
}

We get the passed target object

Object obj = msg.obj;
Intent intent = (Intent) Reflex.getFieldObject(obj, "intent");

Then take out the real object from the target object and modify the intent to the information of the real target object, so that the real target Activity can be started

Intent targetIntent = intent.getParcelableExtra(AmsHookHelperUtils.TUREINTENT);
intent.setComponent(targetIntent.getComponent());
MyCallbackt as follows
**
 * Created by Huanglinqing on 2019/4/30.
 */

public class MyCallback implements Handler.Callback {

    Handler mBase;

    public MyCallback(Handler base) {
        mBase = base;
    }

    @Override
    public boolean handleMessage(Message msg) {

        switch (msg.what) {
         
            case 100:
                handleLaunchActivity(msg);
                break;
            default:
                break;

        }

        mBase.handleMessage(msg);
        return true;
    }

    private void handleLaunchActivity(Message msg) {
    
        Object obj = msg.obj;
        Intent intent = (Intent) Reflex.getFieldObject(obj, "intent");
        Intent targetIntent = intent.getParcelableExtra(AmsHookHelperUtils.TUREINTENT);
        intent.setComponent(targetIntent.getComponent());
    }

}

At this time, start the unregistered Main3Activity and you can start it successfully

startActivity(new Intent(this,Main3Activity.class));

In this way, we will successfully start the unregistered Activity

Added by ch3m1st on Sat, 22 Jan 2022 22:55:23 +0200