1. Background
If someone asks you, can not registered in the configuration file start . You may answer no at first, but if you think about it carefully, you will find that it is also possible to use technologies such as Android Hook to start unregistered activities, which is also the basis of the principle of Android Hook plug-in technology.
Using Android Hook technology to start unregistered Activity, you need to understand Java's reflection mechanism and Android App startup process is very familiar.
Below, we will explain the Android Hook technology to start unregistered Activity from two points:
- By Hooking Instrumentation
- By Hooking AMN
2. Hook the startActivity method
By looking at the source code of startActivity, we can see that startActivity will eventually go to the startActivityFoResult() method. The source code is as follows:
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);
}
}
Next, let's take a look at the mInstrumentation.execStartActivity() method.
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;
}
The execStartActivity() method will eventually go to the checkStartActivityResult() method. So, if we want to Hook the startActivity method, then we need to Hook before the checkStartActivityResult() method.
Hook mInstrumentation
Next, we use a simple example: print log to illustrate how to Hook mInstrumentation.
First, open the Activity.class class, in which we can define the private variable Instrumentation in the Activity.class class.
private Instrumentation mInstrumentation;
All we have to do is change the value of this private variable and print a log line before executing the execStartActivity() method. First, we get this private variable through reflection.
Instrumentation instrumentation = (Instrumentation) Reflex.getFieldObject(Activity.class,MainActivity.this,"mInstrumentation");
Then, 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("MyInstrumentation","Instrumentation Hook11111");
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("MyInstrumentation","Instrumentation Hook22222");
return (ActivityResult) ReflexUtil.invokeInstanceMethod(instrumentation,"execStartActivity",classes,objects);
}
}
Students who are familiar with Java reflection know that we can use Class.forName(name)
to get the class name, or getDeclaredMethod
to get the class parameters. For ease of use, we have encapsulated these commonly used reflections.
public class ReflexUtil {
/**
* 获取无参构造函数
* @param className
* @return
*/
public static Object createObject(String className) {
Class[] pareTyples = new Class[]{};
Object[] pareVaules = new Object[]{};
try {
Class r = Class.forName(className);
return createObject(r, pareTyples, pareVaules);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取无参构造方法
* @param clazz
* @return
*/
public static Object createObject(Class clazz) {
Class[] pareTyple = new Class[]{};
Object[] pareVaules = new Object[]{};
return createObject(clazz, pareTyple, pareVaules);
}
/**
* 获取一个参数的构造函数 已知className
*
* @param className
* @param pareTyple
* @param pareVaule
* @return
*/
public static Object createObject(String className, Class pareTyple, Object pareVaule) {
Class[] pareTyples = new Class[]{pareTyple};
Object[] pareVaules = new Object[]{pareVaule};
try {
Class r = Class.forName(className);
return createObject(r, pareTyples, pareVaules);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取单个参数的构造方法 已知类
*
* @param clazz
* @param pareTyple
* @param pareVaule
* @return
*/
public static Object createObject(Class clazz, Class pareTyple, Object pareVaule) {
Class[] pareTyples = new Class[]{pareTyple};
Object[] pareVaules = new Object[]{pareVaule};
return createObject(clazz, pareTyples, pareVaules);
}
/**
* 获取多个参数的构造方法 已知className
* @param className
* @param pareTyples
* @param pareVaules
* @return
*/
public static Object createObject(String className, Class[] pareTyples, Object[] pareVaules) {
try {
Class r = Class.forName(className);
return createObject(r, pareTyples, pareVaules);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取构造方法
*
* @param clazz
* @param pareTyples
* @param pareVaules
* @return
*/
public static Object createObject(Class clazz, Class[] pareTyples, Object[] pareVaules) {
try {
Constructor ctor = clazz.getDeclaredConstructor(pareTyples);
ctor.setAccessible(true);
return ctor.newInstance(pareVaules);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取多个参数的方法
* @param obj
* @param methodName
* @param pareTyples
* @param pareVaules
* @return
*/
public static Object invokeInstanceMethod(Object obj, String methodName, Class[] pareTyples, Object[] pareVaules) {
if (obj == null) {
return null;
}
try {
//调用一个private方法 //在指定类中获取指定的方法
Method method = obj.getClass().getDeclaredMethod(methodName, pareTyples);
method.setAccessible(true);
return method.invoke(obj, pareVaules);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取一个参数的方法
* @param obj
* @param methodName
* @param pareTyple
* @param pareVaule
* @return
*/
public static Object invokeInstanceMethod(Object obj, String methodName, Class pareTyple, Object pareVaule) {
Class[] pareTyples = {pareTyple};
Object[] pareVaules = {pareVaule};
return invokeInstanceMethod(obj, methodName, pareTyples, pareVaules);
}
/**
* 获取无参方法
* @param obj
* @param methodName
* @return
*/
public static Object invokeInstanceMethod(Object obj, String methodName) {
Class[] pareTyples = new Class[]{};
Object[] pareVaules = new Object[]{};
return invokeInstanceMethod(obj, methodName, pareTyples, pareVaules);
}
/**
* 无参静态方法
* @param className
* @param method_name
* @return
*/
public static Object invokeStaticMethod(String className, String method_name) {
Class[] pareTyples = new Class[]{};
Object[] pareVaules = new Object[]{};
return invokeStaticMethod(className, method_name, pareTyples, pareVaules);
}
/**
* 获取一个参数的静态方法
* @param className
* @param method_name
* @param pareTyple
* @param pareVaule
* @return
*/
public static Object invokeStaticMethod(String className, String method_name, Class pareTyple, Object pareVaule) {
Class[] pareTyples = new Class[]{pareTyple};
Object[] pareVaules = new Object[]{pareVaule};
return invokeStaticMethod(className, method_name, pareTyples, pareVaules);
}
/**
* 获取多个参数的静态方法
* @param className
* @param method_name
* @param pareTyples
* @param pareVaules
* @return
*/
public static Object invokeStaticMethod(String className, String method_name, Class[] pareTyples, Object[] pareVaules) {
try {
Class obj_class = Class.forName(className);
return invokeStaticMethod(obj_class, method_name, pareTyples, pareVaules);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 无参静态方法
* @param method_name
* @return
*/
public static Object invokeStaticMethod(Class clazz, String method_name) {
Class[] pareTyples = new Class[]{};
Object[] pareVaules = new Object[]{};
return invokeStaticMethod(clazz, method_name, pareTyples, pareVaules);
}
/**
* 一个参数静态方法
* @param clazz
* @param method_name
* @param classType
* @param pareVaule
* @return
*/
public static Object invokeStaticMethod(Class clazz, String method_name, Class classType, Object pareVaule) {
Class[] classTypes = new Class[]{classType};
Object[] pareVaules = new Object[]{pareVaule};
return invokeStaticMethod(clazz, method_name, classTypes, pareVaules);
}
/**
* 多个参数的静态方法
* @param clazz
* @param method_name
* @param pareTyples
* @param pareVaules
* @return
*/
public static Object invokeStaticMethod(Class clazz, String method_name, Class[] pareTyples, Object[] pareVaules) {
try {
Method method = clazz.getDeclaredMethod(method_name, pareTyples);
method.setAccessible(true);
return method.invoke(null, pareVaules);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static Object getFieldObject(String className, Object obj, String filedName) {
try {
Class obj_class = Class.forName(className);
return getFieldObject(obj_class, obj, filedName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
public static Object getFieldObject(Class clazz, Object obj, String filedName) {
try {
Field field = clazz.getDeclaredField(filedName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void setFieldObject(Class clazz, Object obj, String filedName, Object filedVaule) {
try {
Field field = clazz.getDeclaredField(filedName);
field.setAccessible(true);
field.set(obj, filedVaule);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void setFieldObject(String className, Object obj, String filedName, Object filedVaule) {
try {
Class obj_class = Class.forName(className);
setFieldObject(obj_class, obj, filedName, filedVaule);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Object getStaticFieldObject(String className, String filedName) {
return getFieldObject(className, null, filedName);
}
public static Object getStaticFieldObject(Class clazz, String filedName) {
return getFieldObject(clazz, null, filedName);
}
public static void setStaticFieldObject(String classname, String filedName, Object filedVaule) {
setFieldObject(classname, null, filedName, filedVaule);
}
public static void setStaticFieldObject(Class clazz, String filedName, Object filedVaule) {
setFieldObject(clazz, null, filedName, filedVaule);
}
As you can see, in the MyInstrumentation class, we directly reflect the execStartActivity method to be consistent with the default method.
(ActivityResult) Reflex.invokeInstanceMethod(instrumentation,"execStartActivity",classes,objects)
Then, use our custom MyInstrumentation to replace the original Instrumentation. The complete code is as follows:
Instrumentation instrumentation = (Instrumentation) ReflexUtil.getFieldObject(Activity.class,this,"mInstrumentation");
MyInstrumentation instrumentation1 = new MyInstrumentation(instrumentation);
ReflexUtil.setFieldObject(Activity.class,this,"mInstrumentation",instrumentation1);
2.2 Hooking AMN
If you look at the source code of the execStartActivity() method, you can see that the execStartActivity() method will eventually go 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) {
;
}
Continue to look at the getDefault() method of ActivityManagerNative.
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;
}
}
It can be seen that IActivityManager is an interface, and gDefault.get() returns a generic type. It is impossible to use reflection directly, so we use a dynamic proxy scheme here.
First, we define an AmsHookHelperUtils class to handle reflection code in the AmsHookHelperUtils class.
public class AMNInvocationHandler implements InvocationHandler {
private String actionName = "startActivity";
private Object target;
public AMNInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals(actionName)) {
Log.d("AMNInvocationHandler", "I Am AMN Hook");
return method.invoke(target, args);
}
return method.invoke(target, args);
}
}
All proxy classes must implement the InvocationHandler interface. In the invoke method, method.invoke(target,args);
indicates that the method corresponding to the proxy object is executed.
Then, we replace the gDefault field in the IActivityManager interface with our proxy class, as follows.
ReflexUtil.setFieldObject("android.util.Singleton",gDefault,"mInstance",proxy);
We define an AmsHookHelperUtil class, and then add a hook method, which uses a proxy method for hooking.
public class AmsHookHelperUtil {
public static void hookAmn() throws ClassNotFoundException {
Object gDefault = ReflexUtil.getStaticFieldObject("android.app.ActivityManagerNative","gDefault");
Object mInstance = ReflexUtil.getFieldObject("android.util.Singleton",gDefault,"mInstance");
Class<?> classInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(classInterface.getClassLoader(),
new Class<?>[]{classInterface},new AMNInvocationHandler(mInstance));
ReflexUtil.setFieldObject("android.util.Singleton",gDefault,"mInstance",proxy);
}
}
3. How to start an unregistered Activity
How to start an unregistered Activity, first we understand the Activity startup process, if you don't know the Activity startup process, you can refer to: Android Activity Startup Process Analysis .
Suppose, now MainActivity, Main2Activity, Main3Activity, among which Main3Activity is not registered, we start Main3Activity in MainActivity, when Main3Activity is started, AMS will check in the configuration file, if there is configuration information of Main3Activity, if it does not exist, it will report an error, if it exists, it will start Main3Activity, this is the regular flow we already know.
Therefore, if we want to start an unregistered Activity, then we can replace the unregistered Activity Main2Activity before sending the Activity to be started to AMS, so that AMS can pass the test. When AMS wants to start the target Activity, replace Main2Activity with The Activity that really needs to be started is enough, which is also the principle of Hook in many hot repair spaces.
We first hook the startActivity method according to the above logic, and here we use the method of AMN Hook. The same as the above code, the difference is that the proxy class of mInstance is different. First, create a new AMNInvocationHanlder object that also inherits from InvocationHandler and only intercepts the startActivity method.
if (method.getName().equals(actionName)){}
What we have to do here is to replace the Main3Activity to be started with Main2Activity, which can bypass the inspection of AMS. 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 do you know that there must be parameters of the intent class in args. Because the invoke method of Java reflection will eventually execute the following code:
return method.invoke(target,args);
The source code of Android's startActivity() method is 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);
Therefore, there must be a parameter of intent type in args. After obtaining the real target Activity, we can obtain the package name of the target.
intent = (Intent) args[index];
String packageName = intent.getComponent().getPackageName();
Next, we create a new Intent, and then set the intent to be the replacement for 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 successfully replaces Main2Activity, but the replacer has to carry the original target and replace it when it is actually opened, otherwise the replacer will be started.
newIntent.putExtra(AmsHookHelperUtils.TUREINTENT,intent);
startActivity(new Intent(this,Main3Activity.class));
Next, what we have to do is how to replace the impostor with the target. We can use ActivityThread to send a message to AMS through mH to achieve replacement.
synchronized(this) {
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
this.mH.sendMessage(msg);
}
Then, AMS processes 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 message processing class of Handler type, so the sendMessage method will call callback. To create a new hookActivityThread method, first we get the current ActivityThread object, then get the mH object of the object, and replace mH with our own custom MyCallback.
Object currentActivityThread = Reflex.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");
Handler mH = (Handler) Reflex.getFieldObject(currentActivityThread, "mH");
ReflexUtil.setFieldObject(Handler.class, mH, "mCallback", new MyCallback(mH));
Custom MyCallback needs to handle the Handler.Callback interface, and then handle the handleMessage method.
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 100:
handleLaunchActivity(msg);
break;
default:
break;
}
mBase.handleMessage(msg);
return true;
}
Then, get the passed target object, take out the real object carried over 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.
Object obj = msg.obj;
Intent intent = (Intent) ReflexUtil.getFieldObject(obj, "intent");
Intent targetIntent = intent.getParcelableExtra(AmsHookHelperUtils.TUREINTENT);
intent.setComponent(targetIntent.getComponent());
Here is the complete code of MyCallbackt:
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) ReflexUtil.getFieldObject(obj, "intent");
Intent targetIntent = intent.getParcelableExtra(AmsHookHelperUtils.TUREINTENT);
intent.setComponent(targetIntent.getComponent());
}
}
Then, start the unregistered Main3Activity to start successfully, isn't it very simple.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。