Android Hook tells you how to start unregistered Activeness

Preface

Android Hook plug-in is actually not a new technology. I wonder if you have ever thought that the software of Alipay, such as panning tickets, train tickets and so on, is written by Alipay software itself. It can not be written for ten years, and the software reaches dozens of G, but it doesn't have so many games. When the skin pack is sure, the user downloads the skin pack whenever he uses it.

 

Can Activation Start without Registering in the Configuration File?

Learn from 0 that Activity must be registered in the configuration file, otherwise it can't start and report an error. But what Hook tells you is that it can be started without registering Activity in the configuration file. Surprisingly? No surprise?

You can learn from this article:

1. By Hook the startActivity method, the log is added to the startActivity method.

1.1 Hook through Instrumentation

1.2 Hook AMN

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

This article is based on Java reflection and App Startup Process Resolution It is suggested that those who don't know much about it can move to these two articles first.

2. Hook the startActivity method

Looking at the source code of startActivity shows that startActivity eventually goes to the startActivity FoResult 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);
    }

}

Through the mInstrumentation.execStartActivity call (ps: detailed source code parsing has been explained in the previous article), and then look at the mInstrumentation.execStartActivity method source code 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;
}

It will eventually be handed over to int var16 = ActivityManager Native. getDefault (). StartActivity (whoThread, intent,... Processing), so if we want to Hook the startActivity method, we can start from these two places (actually not only these two places, but also the reflection encapsulation class used below). It is given in the article.

  • 2.1 Hook mInstrumentation

Private variables are defined in the Activity.class class class

private Instrumentation mInstrumentation;

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

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

What we're going to do is replace this Instrumentation with our own Instrumentation, so let's create a new MyInstrumentation that inherits from Instrumentation, and MyInstrumentation's execStartActivity method 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("-----","Come on, 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("-----","Come on, I am. hook Come in!!");
        return (ActivityResult) Reflex.invokeInstanceMethod(instrumentation,"execStartActivity",classes,objects);

    }

We call this method parameter directly through reflection, which is the same as in the method name {Context. class, IBinder. class, IBinder. class, Activity. class, Intent. class, int. class, Bundle. class}.

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

If we don't call its own execStartActivity method here, then startActivity is invalid.

Then we replace the custom 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 running log is as follows:

At this point, we succeeded in Hook's startActivity method.

2.2 Hook AMN

The execStartActivity method eventually goes to the 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 ActivityManager Native. getDefault () and rewrite the startActivity method. Let's look at ActivityManager Native. 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;
    }
}

As you can see, IActivityManager is an interface and gDefault.get() returns a generic type. We can't start with the above scheme, so we need to use dynamic proxy scheme here.

We define an AmsHookHelperUtils class that handles reflection code in the AmsHookHelperUtils class.

GDefault is a final static type field. 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 pattern

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 mInstance 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 AMNInvocation Hanlder (mInstance). (ps: The proxy model is divided into static proxy and dynamic proxy. If you do not know the proxy model, you can Baidu Yibo, you can also pay attention to me, waiting for my proxy model 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("---","Come on, I am. hook AMN Come in");
            return method.invoke(target,args);
        }

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

All proxy classes need to implement the InvocationHandler interface. In the invoke method, method.invoke(target,args); that means to execute the corresponding method of the proxy object. Because AMN Singleton does a lot of things, this is only for the startActivity method hook.

if (method.getName().equals(actionName)){
    Log.d("---","Come on, 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 AmsHook Helper Utils method as a whole 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, running the log as follows:

In this way, we succeed in Hook's getDefault method for AMN.

 

2.3 How to start an unregistered Activity

How to start an unregistered Activeness? First of all, we understand the start-up process of Activity. App's start-up process has been explained in the previous article. Analysis of APP Startup Process If you don't know something about it, you can move on to the last article. Suppose MainActivity, Main2Activity, Main3Activity, where Main3Activity is not registered, we start Main3Activity in MainActivity. When we start Main3Activity, AMS will check in the configuration file whether there is any configuration information of Main3Activity, if there is no error, then start Main3Activity if there is.

So what we can do is to replace active Activity that has not been registered with active Activity that will be started before sending it to AMS, so that AMS can pass the test and replace active Activity with active Activity that is really started when AMS wants to start the target Activity.

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

A new AMNInvocationHanlder1 object is also inherited from InvocationHandler, intercepting only the startActivity method.

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

What we need to do here is to replace Main2Activity with Main3Activity, which will be started, so that we can bypass the AMS test. First, we take out 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 intent class parameters in args, because the invoke method will eventually execute

return method.invoke(target,args);

Represents that the original method will be executed, and 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 we get 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 to set intent to 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 by Main2Activity, but the impersonator has to carry the original target over and replace it when it is really opened, otherwise the impersonator will really start.

newIntent.putExtra(AmsHookHelperUtils.TUREINTENT,intent);

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

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

The actual display is Main2Activity, as shown in the figure:

 

This means that our counterfeiter has successfully replaced the real target, so we need to replace the counterfeiter with the target when we start up. 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 processes messages after they are received

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 callback, Handler message processing mechanism can be seen in my previous blog.

Deep Understanding of Android Message Mechanisms So we can Hook the callback field. What if you don't understand? Pay attention to me!

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 the MyCallback interface, first Handler.Callback interface, to 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 target object passed in.

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

Then the real object is extracted from the target object and the intent is modified 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 is 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 point, start the unregistered Main3Activity again, and you can start it successfully.

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

So we successfully launched the unregistered Activity

 

Keywords: Android Java

Added by Orpheus13 on Mon, 05 Aug 2019 11:06:57 +0300