preface
Android Hook plug-in is not a new technology, I do not know if you have thought, alipay so many small software: tao tickets, train tickets and other software, is it written by alipay this software? It would have taken ten years, and the software would have reached tens of gigabytes, but it didn’t, and there were so many skin packs in the game that people would have to download whichever one they were using.
Can an Activity not registered in a configuration file be started?
An Activity must be registered in a configuration file. Otherwise, it will fail to start and an error will be reported. But Hook tells you that you can start an Activity without registering it in the configuration file. Isn’t that a surprise? Are you surprised?
You can learn from this article:
1. Hook the startActivity method to add logs to the startActivity method.
1.1 Hook Instrumentation
1.2 Hook AMN
2. How do I plug-in an Activity that is not registered in the configuration file
This article is based on Java reflection mechanism and App startup process analysis. It is recommended that those who are not familiar with it can move to these two articles first.
Hook the startActivity method
By looking at the startActivity source code, you can see that startActivity ends up in the startActivityFoResult 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); }}Copy the code
Through mInstrumentation. ExecStartActivity calls (ps: detailed source code parsing has set up a file in the previous article), see mInstrumentation. ExecStartActivity method source code 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; }Copy the code
Eventually to int var16 = ActivityManagerNative. GetDefault () startActivity (whoThread, intent,… So if we want to Hook the startActivity method, there are two places to start. (Actually, there are more than two places. We only cover two places, and the reflection wrapper class used below was also presented in the previous article.)
- 2.1 Hook the mInstrumentation
Private variables are defined in the activity. class class
private Instrumentation mInstrumentation;
Copy the code
The first thing we need to do is change the value of this private variable and print a log line before executing the method. First we get the private variable by reflection.
Instrumentation instrumentation = (Instrumentation) Reflex.getFieldObject(Activity.class,MainActivity.this,"mInstrumentation");
Copy the code
So what we’re going to do is replace this Instrumentation with our own Instrumentation, so let’s create a new MyInstrumentation inherited 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("-----", ); 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("-----"," la la la I hook in!!" ); return (ActivityResult) Reflex.invokeInstanceMethod(instrumentation,"execStartActivity",classes,objects); }Copy the code
We call this method directly through reflection with the Class[] classes = argument {the Context class, IBinder. Class, IBinder. Class, Activity, class, Intent. Class, int. J class, Bundle. The class} in agreement with the method name
(ActivityResult) Reflex.invokeInstanceMethod(instrumentation,"execStartActivity",classes,objects)
Copy the code
StartActivity is invalid if we don’t call its own execStartActivity method here.
Then we replace the custom with the original Instrumentation
Reflex.setFieldObject(Activity.class,this,"mInstrumentation",instrumentation1);
Copy the code
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);
Copy the code
Run logs are as follows:
At this point we successfully Hook the startActivity method
2.2 Hook the AMN
ExecStartActivity method will eventually go 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) { ; }Copy the code
You might say the above said ah, replace ActivityManagerNative. GetDefault (), rewrite startActivity method, we see ActivityManagerNative getDefault ()
public static IActivityManager getDefault() {
return (IActivityManager)gDefault.get();
}
Copy the code
public final T get() { synchronized(this) { if(this.mInstance == null) { this.mInstance = this.create(); } return this.mInstance; }}Copy the code
You can see that IActivityManager is an interface, and gdefault.get () returns a generic. We can’t do that, so we’ll use a dynamic proxy
We define an AmsHookHelperUtils class that handles reflection code in the AmsHookHelperUtils class
GDefault is a final statically typed field. First we get the gDefault field
Object gDefault = Reflex.getStaticFieldObject("android.app.ActivityManagerNative","gDefault");
Copy the code
GDefault is an object of type Singleton<IActivityManager>, 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; }}}Copy the code
Next we take out the mInstance field
Object mInstance = Reflex.getFieldObject("android.util.Singleton",gDefault,"mInstance");
Copy the code
Then create a proxy object
Class<? > classInterface = Class.forName("android.app.IActivityManager"); Object proxy = Proxy.newProxyInstance(classInterface.getClassLoader(), new Class<? >[]{classInterface},new AMNInvocationHanlder(mInstance));Copy the code
Our proxy object is new AMNInvocationHanlder(mInstance). (PS: The proxy mode is divided into static proxy and dynamic proxy. If you do not know the proxy mode, you can baidu wave, or pay attention to 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 ("-- "," I hook AMN "); return method.invoke(target,args); } return method.invoke(target,args); }}Copy the code
All proxy classes implement the InvocationHandler interface, in the invoke method. Invoke (Target,args); Represents the method that executes the proxied object. Because AMN Singleton does a lot of work, only the startActivity method is hooked here
If (method.getName().equals(actionName)){log.d ("-- "," hook AMN "); return method.invoke(target,args); }Copy the code
Then we replace the gDefault field with our proxy class
Reflex.setFieldObject("android.util.Singleton",gDefault,"mInstance",proxy);
Copy the code
The AmsHookHelperUtils method 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); }}Copy the code
We call AmsHookHelperUtils. HookAmn (); Then start a new Activity with the following run log:
We have successfully hooked AMN’s getDefault method.
2.3 How Do I Start an Unregistered Activity
How to start an unregistered Activity? First of all, we understand the process of starting an Activity. The process of starting an App has been explained in the previous article. Suppose now MainActivity, Main2Activity, Main3Activity, where Main3Activity is not registered, we start Main3Activity in MainActivity, and when we start Main3Activity, AMS will check the configuration file to see if there is Main3Activity configuration information. If there is no configuration information, an error will be reported. If there is, Main3Activity will be started.
So what we can do is, before we send the Activity that’s going to be started to AMS, replace the Activity that’s not registered with the Activity Main2Activity, so that AMS can verify that it passes, Replace Main2Activity with the actual Activity when AMS starts the target Activity.
First, we Hook the startActivity method according to the above logic. Here, we Hook the method of AMN Hook. As with the code above, the difference is that mInstance has a different proxy class.
Create a new AMNInvocationHanlder1 object that also inherits from InvocationHandler and intercepts only the startActivity method.
if (method.getName().equals(actionName)){}
Copy the code
What we need to do here is to replace the Main3Activity we started with the Main2Activity to bypass AMS’s verification. First, we retrieve 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; }}Copy the code
You might ask how do you know that args must have an Intent class parameter because the Invoke method will eventually execute it
return method.invoke(target,args);
Copy the code
The original startActivity method is executed 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);Copy the code
So we say there must be an intent type parameter in args. After we get the actual target Activity, we get the package name of the target
intent = (Intent) args[index];
String packageName = intent.getComponent().getPackageName();
Copy the code
Create an Intent and set the Intent to information about the impostor Main2Activity
Intent newIntent = new Intent();
ComponentName componentName = new ComponentName(packageName,Main2Activity.class.getName());
newIntent.setComponent(componentName);
Copy the code
args[index] = newIntent;
Copy the code
The target Activity is then replaced with Main2Activity, but the impostor must carry the original target and wait until it is actually opened before replacing it back, otherwise the impostor will actually start
newIntent.putExtra(AmsHookHelperUtils.TUREINTENT,intent);
Copy the code
At this point we call this method and do nothing, and at this point we start Main3Activity
startActivity(new Intent(this,Main3Activity.class));
Copy the code
This is actually Main2Activity, as shown:
This means that our impostor has successfully replaced the real target, so we will then replace the impostor with the target again at startup, and the ActivityThread sends a message to the AMS via mH
synchronized(this) {
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
this.mH.sendMessage(msg);
}
Copy the code
AMS receives the message and processes 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);
Copy the code
MH is a message handling class of the Handler type, so sendMessage calls callback. See my previous blog for the Handler message handling mechanism
With a deeper understanding of the Android messaging mechanism, we can Hook the callback field. What if you don’t understand? Pay attention to me!!!!!!!
To create a hookActivityThread method, first we get the current ActivityThread object
Object currentActivityThread = Reflex.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");
Copy the code
Then get the mH object of the object
Handler mH = (Handler) Reflex.getFieldObject(currentActivityThread, "mH");
Copy the code
Replace mH with our own custom MyCallback.
Reflex.setFieldObject(Handler.class, mH, "mCallback", new MyCallback(mH));
Copy the code
Customizing MyCallback starts with the Handler.Callback interface, which reprocesses the handleMessage method
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 100:
handleLaunchActivity(msg);
break;
default:
break;
}
mBase.handleMessage(msg);
return true;
}
Copy the code
We get the target object passed in
Object obj = msg.obj;
Intent intent = (Intent) Reflex.getFieldObject(obj, "intent");
Copy the code
The actual target Activity can then be started by fetching the actual object from the target object and modifying the intent to contain information about the actual target object
Intent targetIntent = intent.getParcelableExtra(AmsHookHelperUtils.TUREINTENT);
intent.setComponent(targetIntent.getComponent());
Copy the code
MyCallbackt as followsCopy the code
** * 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()); }}Copy the code
At this point, start the unregistered Main3Activity, and it will start successfully
startActivity(new Intent(this,Main3Activity.class));
Copy the code
We have successfully started the unregistered Activity