Android interception AMS request practice

Android interception AMS request practice

Overview

Following the last requirement of starting an Activity in the background, I followed some methods in Actual Combat | Android Background Start Activity Practice Road to handle it. Although there are still some problems on the Android Q version, the ability to start in the background is basically complete. Later, I unlocked the source code of Xiaomi ROM and found out how they implemented the permission of background start and how to bypass this permission. I found that the result was unexpectedly simple... (I will write a separate article on this part later if I have the opportunity).

[[377520]]

This article was written after the investigation of background startup. If the Activity page we want to start in the background is in a third-party SDK, and the action (startActivity) to start the page also occurs in the third-party SDK, then their direct startActivity method does not have the ability to start in the background. For some reasons, we cannot ask the SDK to modify the method of starting the Activity, so we need to find a way to enable it to have the ability to start in the background without modifying the third-party SDK's call to startActivity code. The first reaction is to intercept the request for startActivity. Referring to the Android system_server process and the Android-Activity startup process, we know that AMS is a thread in the system_server process. It is responsible for the specific work of starting the Activity. After its work is completed, it will call back the life cycle method of the Activity instance in the APP process through Binder. When the APP process calls startActivity, the Binder proxy of AMS will be obtained by Instrumentation, and then the relevant methods of AMS will be called across processes through it. The place where we can do Hook interception is this Binder proxy object!

Next, we will look at the implementation method of this process and how we intercept it in various Android versions. We will mainly look at the source code of Android P. Although the processes of other versions are different, the Hook method is similar.

Android P

The way to obtain the AMS agent in AOSP from Android 8 to Android 9 is the same. After calling context.startActivity, the APP process will come to the relevant method in Instrumentation and call the following code:

  1. int result = ActivityManager.getService().startActivity(whoThread, who.getBasePackageName(), intent, ...);

Here, we use Binder to call the relevant methods in AMS across processes. Let’s take a look at the implementation of ActivityManager.getService():

  1. /** @hide */
  2. public   static IActivityManager getService() {
  3. return IActivityManagerSingleton.get();
  4. }
  5.  
  6. private static final Singleton<IActivityManager> IActivityManagerSingleton = new Singleton<IActivityManager>() {
  7.      
  8.  
  9.  
  10. protected IActivityManager create () {
  11. // 1...
  12. }
  13. };

You can see that IActivityManagerSingleton is an instance of the Singleton type. Obviously, this Singleton is a lazy loaded singleton template class:

  1. public abstract class Singleton<T> {
  2. private T mInstance;
  3.  
  4. protected abstract T create ();
  5.  
  6. public final T get() {
  7. synchronized (this) {
  8. if (mInstance == null ) {
  9. mInstance = create ();
  10. }
  11. return mInstance;
  12. }
  13. }
  14. }

So we can know that what IActivityManagerSingleton.get() returns is the instance in the create method. Here is the create method code omitted in the above 1:

  1. final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
  2. final IActivityManager am = IActivityManager.Stub.asInterface(b);
  3. return am;

Students who are familiar with Binder can see at a glance that am here is a Binder proxy object. If there is a ServiceManager.getService method, there must be a ServiceManager.addService method. One is to query the Binder service from the ServiceManager, and the other is to register the service in the ServiceManager. The registration time is when the system starts the system_server process. Please refer to the AMS startup process, which will not be described in depth here.

So the ActivityManager.getService() method actually returns a Binder proxy object of AMS, which is used to call AMS related methods across processes. Therefore, we can use the JDK dynamic proxy method to create an am proxy Proxy object through the Proxy.newProxyInstance method, and replace the am object returned by the ActivityManager.getService() method with our Proxy object through reflection. Then, when the App process calls the ActivityManager.getService().XXX method, it will be intercepted by our Proxy and then processed. JDK dynamic proxy is also one of the commonly used design patterns in Java. Students who are not familiar with it can refer to the use of JDK dynamic proxy.

This process can be divided into three steps:

  1. Get the am object through reflection. Since ActivityManager.getService() is a hidden method, you can call it through reflection to get the original am object;
  2. Create a proxy object Proxy;
  3. Replace the am object with Proxy through reflection;

We can see that the am object is actually the mInstance property in Singleton (whose instance is IActivityManagerSingleton), so the third step is just to set the mInstance property to our Proxy object through reflection. The following AmsHooker is an abstract class with different implementations on different Android platforms. It is mainly used to obtain am objects of different Android platforms and replace am objects through reflection:

  1. abstract class AmsHooker {
  2. //Replace am with proxy through reflection
  3. fun hookAms(proxy: Any ?) {
  4. try {
  5. val hookObj = getHookObj()
  6. val hookField = getHookField()
  7. if (hookObj != null && hookField != null && proxy != null ) {
  8. hookField.set (hookObj, proxy)
  9. }
  10. } catch (e: Exception) {
  11. e.printStackTrace()
  12. }
  13. }
  14.  
  15. // That is, the IActivityManagerSingleton instance
  16. protected abstract fun getHookObj(): Any ?
  17.  
  18. // That is mInstance
  19. protected abstract fun getHookField(): Field?
  20.  
  21. // am
  22. abstract fun getTarget(): Any ?
  23.  
  24. //Interface used to create Proxy
  25. abstract fun getInterfaces(): Array<Class<*>>
  26. }

The implementation on the Android P platform is as follows, see the comments for details:

  1. class AmsPHooker : AmsHooker() {
  2. override fun getHookObj(): Any ? {
  3. val amClass = ReflectUtils.getClass( "android.app.ActivityManager" )
  4. // Get the IActivityManagerSingleton property
  5. return ReflectUtils.readStaticField(amClass, "IActivityManagerSingleton" )
  6. }
  7.  
  8. override fun getHookField(): Field? {
  9. // Get mInstance Field
  10. return ReflectUtils.getField(ReflectUtils.getClass( "android.util.Singleton" ), "mInstance" )
  11. }
  12.  
  13. override fun getTarget(): Any ? {
  14. // ActivityManager.getService() returns am
  15. return ReflectUtils.getClass( "android.app.ActivityManager" ).getDeclaredMethod( "getService" ).invoke( null )
  16. }
  17.  
  18. // Get interfaces to create dynamic proxies
  19. override fun getInterfaces(): Array<Class<*>> {
  20. return arrayOf(ReflectUtils.getClass( "android.app.IActivityManager" ))
  21. }
  22. }

Next, create the proxy class (the code has been deleted):

  1. public class AMSProxy implements InvocationHandler {
  2. private AmsHooker hooker; // Return different implementations based on different Android platforms
  3. private Object origAm; // original am object
  4.  
  5. private boolean ensureInit() {
  6. // ...
  7. hooker = getHooker();
  8. origAm = hooker.getTarget();
  9. }
  10.  
  11. private AmsHooker getHooker() {
  12. if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
  13. return new AmsQHooker();
  14. } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
  15. return new AmsPHooker();
  16. } else {
  17. return new AmsNHooker();
  18. }
  19. }
  20.  
  21.      
  22.  
  23.  
  24. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  25. // ...
  26. }
  27.  
  28. // Create a proxy
  29. Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
  30. hooker.getInterfaces(), this);
  31. //Replace the system am object
  32. hooker.hookAms(proxy);
  33. }

In the above, a proxy object Proxy is created with the AMSProxy instance as a parameter, and the am object is replaced by the Proxy object through the hookAms method. In this way, when the process calls the relevant method through ActivityManager.getService(), the above invoke method will be called, and interception can be done here:

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  2. try {
  3. if (callback.canIntercept(method, args)) {
  4. if (callback.autoRemove()) {
  5. // Restore the am object
  6. // ...
  7. }
  8. //Intercept am's request and do your own business processing
  9. return callback.intercept(origAm, method, args);
  10. }
  11. return method.invoke(origAm, args);
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. }
  15. return   null ;
  16. }

When there is code in this process that tries to call related methods (such as startActivity, etc.) through am, it will be intercepted by the invoke method, and then we will choose whether to intercept it through the interception condition (canIntercept) we set. It is recommended that after completing the interception business needs each time, the original am object should be restored through the hookAms method to prevent continuous interception of system requests in this process. It is emphasized here that this process is always the current process. Obviously, the method of replacing the am object through reflection will only work for this process.

Android Q

On Android Q, the call in the above Instrumentation becomes as follows:

  1. int result = ActivityTaskManager.getService().startActivity(whoThread, who.getBasePackageName(), intent, ...);

This becomes ActivityTaskManager.getService():

  1. /** @hide */
  2. public   static IActivityTaskManager getService() {
  3. return IActivityTaskManagerSingleton.get();
  4. }
  5.  
  6. private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton = new Singleton<IActivityTaskManager>() {
  7.      
  8.  
  9.  
  10. protected IActivityTaskManager create () {
  11. final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
  12. return IActivityTaskManager.Stub.asInterface(b);
  13. }
  14. };

You can see that on Android Q, the class has changed from ActivityManager to ActivityTaskManager series, so our AmsQHooker is implemented as follows:

  1. class AmsQHooker : AmsHooker() {
  2. override fun getHookObj(): Any ? {
  3. val amClass = ReflectUtils.getClass( "android.app.ActivityTaskManager" )
  4. // Get the IActivityTaskManagerSingleton property
  5. return ReflectUtils.readStaticField(amClass, "IActivityTaskManagerSingleton" )
  6. }
  7.  
  8. override fun getHookField(): Field? {
  9. return ReflectUtils.getField(ReflectUtils.getClass( "android.util.Singleton" ), "mInstance" )
  10. }
  11.  
  12. override fun getTarget(): Any ? {
  13. // Reflective access to getService is forbidden when targeting API 29 and above
  14. // val getServiceMethod = amClass.getDeclaredMethod( "getService" )
  15. return ReflectUtils.getClass( "android.util.Singleton" ).getDeclaredMethod( "get" ).invoke(getHookObj())
  16. }
  17.  
  18. override fun getInterfaces(): Array<Class<*>> {
  19. return arrayOf(ReflectUtils.getClass( "android.app.IActivityTaskManager" ))
  20. }
  21. }

The other steps are the same as Android P.

Android N

On Android 7.1 and below, the Instrumentation call is different:

  1. int result = ActivityManagerNative.getDefault().startActivity(whoThread, who.getBasePackageName(), intent, ...);

This becomes ActivityManagerNative.getDefault():

  1. static   public IActivityManager getDefault() {
  2. return gDefault.get();
  3. }
  4.  
  5. private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
  6. protected IActivityManager create () {
  7. IBinder b = ServiceManager.getService( "activity" );
  8. IActivityManager am = asInterface(b);
  9. return am;
  10. }
  11. };

You can see that although the class name and method have changed, the Singleton class is still used, so you only need to inherit AmsHooker and rewrite the relevant methods:

  1. class AmsNHooker : AmsHooker() {
  2. override fun getHookObj(): Any ? {
  3. val amNativeClass = ReflectUtils.getClass( "android.app.ActivityManagerNative" )
  4. // Get the gDefault instance
  5. return ReflectUtils.readStaticField(amNativeClass, "gDefault" )
  6. }
  7.  
  8. override fun getHookField(): Field? {
  9. return ReflectUtils.getField(ReflectUtils.getClass( "android.util.Singleton" ), "mInstance" )
  10. }
  11.  
  12. override fun getTarget(): Any ? {
  13. return getHookField()?.get(getHookObj())
  14. }
  15.  
  16. override fun getInterfaces(): Array<Class<*>> {
  17. return arrayOf(ReflectUtils.getClass( "android.app.IActivityManager" ))
  18. }
  19. }

The others also reuse the logic on Android P.

Summarize

Through the above method, it is possible to intercept the related methods called by the Binder proxy of AMS in this process, which can be used to implement some unconventional functions. Although the recent requirements are relatively unconventional (liumang), putting aside the requirements, it is still interesting for developers to investigate these technologies..ha~

Blogging is an interesting, rewarding, and difficult thing. You need to try to sort out the context and logic of the article and know how to write it in a clearer and easier to understand way. It's been a long time since I updated. I've been too busy recently and haven't had time to do these things. When I thought about the possibility that my articles might be liked, I instantly became motivated. So I wrote one in my busy schedule. The content is not very deep, so just treat it as your usual development notes.

<<:  There is a new way to play during the Spring Festival: WeChat can send "video red envelopes"

>>:  WeChat 8.0 released: Yellow face emojis can move and you can throw bombs to friends

Recommend

How much does it cost to be an agent of a catering mini program in Hengshui?

How much does it cost to be an agent of Hengshui ...

A complete list of pretentious vocabulary for app promotion!

Job Responsibilities: CP: Different from the CP p...

Jack Ma: "I had a sleepless night in Seattle last night"

[[150517]] On September 24, on the morning of Sep...

All-new Lexus LS spy photos revealed, debut in early 2017

The current Lexus LS has been launched for more t...

Why are humans right-handed?

"Why are humans right-handed?" is a que...

AI decides your university? Calculate your 7 personal qualities in minutes

Currently, many universities adhere to the concep...