Service插件化解決方案

  • 2019 年 10 月 7 日
  • 筆記

1.ActivityThread最終是通過Instrumentation啟動一個Activity的。而ActivityThread啟動Service並不藉助於Instrumentation,而是直接把Service反射出來就啟動了。Instrumentation只給Activity提供服務

2.一般預先在宿主app中創建10個StubService佔位就夠了

***startService的解決方案***

首先把插件和宿主的dex合併

/**   * 由於應用程式使用的ClassLoader為PathClassLoader   * 最終繼承自 BaseDexClassLoader   * 查看源碼得知,這個BaseDexClassLoader載入程式碼根據一個叫做   * dexElements的數組進行, 因此我們把包含程式碼的dex文件插入這個數組   * 系統的classLoader就能幫助我們找到這個類   *   * 這個類用來進行對於BaseDexClassLoader的Hook   * 類名太長, 不要吐槽.   * @author weishu   * @date 16/3/28   */  public final class BaseDexClassLoaderHookHelper {        public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)              throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {          // 獲取 BaseDexClassLoader : pathList          Object pathListObj = RefInvoke.getFieldObject(DexClassLoader.class.getSuperclass(), cl, "pathList");            // 獲取 PathList: Element[] dexElements          Object[] dexElements = (Object[]) RefInvoke.getFieldObject(pathListObj, "dexElements");            // Element 類型          Class<?> elementClass = dexElements.getClass().getComponentType();            // 創建一個數組, 用來替換原始的數組          Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);            // 構造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 這個構造函數          Class[] p1 = {File.class, boolean.class, File.class, DexFile.class};          Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};          Object o = RefInvoke.createObject(elementClass, p1, v1);            Object[] toAddElementArray = new Object[] { o };          // 把原始的elements複製進去          System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);          // 插件的那個element複製進去          System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);            // 替換          RefInvoke.setFieldObject(pathListObj, "dexElements", newElements);      }  }

其次採用「欺上瞞下」的方法

public class AMSHookHelper {        public static final String EXTRA_TARGET_INTENT = "extra_target_intent";        public static void hookAMN() throws ClassNotFoundException,              NoSuchMethodException, InvocationTargetException,              IllegalAccessException, NoSuchFieldException {            //獲取AMN的gDefault單例gDefault,gDefault是final靜態的          Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault");            // gDefault是一個 android.util.Singleton<T>對象; 我們取出這個單例裡面的mInstance欄位          Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", gDefault, "mInstance");            // 創建一個這個對象的代理對象MockClass1, 然後替換這個欄位, 讓我們的代理對象幫忙幹活          Class<?> classB2Interface = Class.forName("android.app.IActivityManager");          Object proxy = Proxy.newProxyInstance(                  Thread.currentThread().getContextClassLoader(),                  new Class<?>[] { classB2Interface },                  new MockClass1(mInstance));            //把gDefault的mInstance欄位,修改為proxy          Class class1 = gDefault.getClass();          RefInvoke.setFieldObject("android.util.Singleton", gDefault, "mInstance", proxy);      }        public static void hookActivityThread() throws Exception {            // 先獲取到當前的ActivityThread對象          Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");            // 由於ActivityThread一個進程只有一個,我們獲取這個對象的mH          Handler mH = (Handler) RefInvoke.getFieldObject(currentActivityThread, "mH");            //把Handler的mCallback欄位,替換為new MockClass2(mH)          RefInvoke.setFieldObject(Handler.class, mH, "mCallback", new MockClass2(mH));      }  }

其中,HookService,讓AMS啟動StubService的實現在類MockClass1上

class MockClass1 implements InvocationHandler {        private static final String TAG = "MockClass1";        // 替身StubService的包名      private static final String stubPackage = "jianqiang.com.activityhook1";        Object mBase;        public MockClass1(Object base) {          mBase = base;      }        @Override      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {            Log.e("bao", method.getName());            if ("startService".equals(method.getName())) {              // 只攔截這個方法              // 替換參數, 任你所為;甚至替換原始StubService啟動別的Service偷梁換柱                // 找到參數裡面的第一個Intent 對象              int index = 0;              for (int i = 0; i < args.length; i++) {                  if (args[i] instanceof Intent) {                      index = i;                      break;                  }              }                //get StubService form UPFApplication.pluginServices              Intent rawIntent = (Intent) args[index];              String rawServiceName = rawIntent.getComponent().getClassName();                String stubServiceName = UPFApplication.pluginServices.get(rawServiceName);                // replace Plugin Service of StubService              ComponentName componentName = new ComponentName(stubPackage, stubServiceName);              Intent newIntent = new Intent();              newIntent.setComponent(componentName);                // Replace Intent, cheat AMS              args[index] = newIntent;                Log.d(TAG, "hook success");              return method.invoke(mBase, args);          } else if ("stopService".equals(method.getName())) {              // 只攔截這個方法              // 替換參數, 任你所為;甚至替換原始StubService啟動別的Service偷梁換柱                // 找到參數裡面的第一個Intent 對象              int index = 0;              for (int i = 0; i < args.length; i++) {                  if (args[i] instanceof Intent) {                      index = i;                      break;                  }              }                //get StubService form UPFApplication.pluginServices              Intent rawIntent = (Intent) args[index];              String rawServiceName = rawIntent.getComponent().getClassName();              String stubServiceName = UPFApplication.pluginServices.get(rawServiceName);                // replace Plugin Service of StubService              ComponentName componentName = new ComponentName(stubPackage, stubServiceName);              Intent newIntent = new Intent();              newIntent.setComponent(componentName);                // Replace Intent, cheat AMS              args[index] = newIntent;                Log.d(TAG, "hook success");              return method.invoke(mBase, args);          }            return method.invoke(mBase, args);      }  }

第2,AMS被欺騙後,它原本會通知APP啟動StubService,而我們要Hook掉ActivityThread的mH對象的mCallback對象,仍然截獲它的handleMessage方法(handleCreateService方法),具體實現在MockClass2中

class MockClass2 implements Handler.Callback {        Handler mBase;        public MockClass2(Handler base) {          mBase = base;      }        @Override      public boolean handleMessage(Message msg) {            Log.d("baobao4321", String.valueOf(msg.what));          switch (msg.what) {                // ActivityThread裡面 "CREATE_SERVICE" 這個欄位的值是114              // 本來使用反射的方式獲取最好, 這裡為了簡便直接使用硬編碼              case 114:                  handleCreateService(msg);                  break;          }            mBase.handleMessage(msg);          return true;      }        private void handleCreateService(Message msg) {          // 這裡簡單起見,直接取出插件Servie            Object obj = msg.obj;          ServiceInfo serviceInfo = (ServiceInfo) RefInvoke.getFieldObject(obj, "info");            String realServiceName = null;            for (String key : UPFApplication.pluginServices.keySet()) {              String value = UPFApplication.pluginServices.get(key);              if(value.equals(serviceInfo.name)) {                  realServiceName = key;                  break;              }          }            serviceInfo.name = realServiceName;      }  }

在宿主中調用

Intent intent = new Intent();  intent.setComponent(          new ComponentName("jianqiang.com.testservice1",                  "jianqiang.com.testservice1.MyService1"));  startService(intent);    Intent intent = new Intent();  intent.setComponent(          new ComponentName("jianqiang.com.testservice1",                  "jianqiang.com.testservice1.MyService1"));  stopService(intent);

***bindService的解決方案***

只要在實現類MockClass1中增加

else if ("bindService".equals(method.getName())) {        // 找到參數裡面的第一個Intent 對象      int index = 0;      for (int i = 0; i < args.length; i++) {          if (args[i] instanceof Intent) {              index = i;              break;          }      }        Intent rawIntent = (Intent) args[index];      String rawServiceName = rawIntent.getComponent().getClassName();      String stubServiceName = UPFApplication.pluginServices.get(rawServiceName);          // replace Plugin Service of StubService      ComponentName componentName = new ComponentName(stubPackage, stubServiceName);      Intent newIntent = new Intent();      newIntent.setComponent(componentName);        // Replace Intent, cheat AMS      args[index] = newIntent;        Log.d(TAG, "hook success");      return method.invoke(mBase, args);  }

宿主中調用

Intent intent = new Intent();  intent.setComponent(          new ComponentName("jianqiang.com.testservice1",                  "jianqiang.com.testservice1.MyService2"));  bindService(intent, conn, Service.BIND_AUTO_CREATE);    unbindService(conn);

問1:為什麼不在unbind的時候欺騙AMS?

因為unbind語法是unbindService(conn),AMS會根據conn來找到對應的Service,所以我們不需要把MyService2替換為StubService2

問2:為什麼在MockClass2中不需要吧StubService2切換回MyService2?

因為bindService是先走handleCreateService再走handleBindService方法。在handleCreateService方法中已經將StubService2切換回MyService2了,所以後面不需要切換了。

Tags: