1. 程式人生 > >Service外掛化解決方案

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了,所以後面不需要切換了。