Service外掛化解決方案
--摘自《android外掛化開發指南》
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了,所以後面不需要切換了。