Android插件化與熱修復(四)---DroidPlugin hook 系統service

內容概要

本篇文章主要回答以下問題:

  1. 為什么要hook 系統service
  2. 應該在哪些環節來hook,為什么是這些環節

前提說明

  • 首先,本篇文章需要對Hook機制有比較清晰的了解,關于Hook機制,可以參考上篇文章《 Android插件化與熱修復(三)---DroidPlugin Hook機制》

  • 另外,最好參考著DroidPlugin的源碼跟著文章一步步來看,有助于理解文章當前講的內容。

Binder機制介紹

幾點說明

  • 了解Binder機制,有助于理解本文內容。
  • 以下Binder機制介紹的內容來自網上的一篇博客。
  • 大家有個粗略的認識即可,對于Binder機制的介紹并不是本文的重點,也不影響對本文的理解,想深入了解的,可以參考該博客的文章。

Binder機制

Binder 是Android系統采用的進程間通信(IPC)機制。 IPC機制有好多種, Binder 只是其中一種。
Binder機制中包含四個組件Client、Server、Service Manager和Binder驅動程序。

Binder機制
  1. Client、Server和Service Manager實現在用戶空間中,Binder驅動程序實現在內核空間中
  2. Binder驅動程序和Service Manager在Android平臺中已經實現,開發者只需要在用戶空間實現自己的Client和Server
  3. Binder驅動程序提供設備文件/dev/binder與用戶空間交互,Client、Server和Service Manager通過open和ioctl文件操作函數與Binder驅動程序進行通信
  4. Client和Server之間的進程間通信通過Binder驅動程序間接實現
  5. Service Manager是一個守護進程,用來管理Server,并向Client提供查詢Server接口的能力

文章鏈接:http://blog.csdn.net/luoshengyang/article/details/6618363

使用AIDL調用遠程Service的過程

為了搞清楚應該在哪些環節來hook,為什么是這些環節,我們需要對使用AIDL調用遠程Service的過程有個清楚的了解。

新建一個aidl文件

定義通訊的接口
IRemoteService.aidl

interface IRemoteService {

    String getValue();

}

IDE會為我們自動生成IRemoteService.java
將該文件格式化后,我們來看看該文件的結構

IRemoteService.java

將Stub類折疊后,我們發現這里面的結構其實很簡單

Stub類

看一下Stub類的聲明:
public static abstract class Stub extends android.os.Binder implements com.example.jst.androidtest.IRemoteService

  • extends android.os.Binder
    android.os.Binder 實現了 IBinder接口,看一下IBinder接口源碼的介紹

Base interface for a remotable object

所以Stub對象就是一個remotable object (可遠程化的對象),通俗點,就是這個對象可以跨進程來使用(當然,肯定不是直接使用另一個進程的對象,具體實現原理就牽涉到底層實現了)。

  • implements IRemoteService
    Stub并沒有真正的實現,而是定義為abstract,這樣子類就必須具體實現這個接口。

綜上,也就是說Stub對象是一個實現了IRemoteService接口的可以跨進程使用的對象。

下面在RemoteService里會看到它的使用。

實現遠程服務 RemoteService

public class RemoteService extends Service {

    public class RemoteServiceImpl extends IRemoteService.Stub {
        @Override
        public String getValue() throws RemoteException {
            return "從RemoteService獲得的值.";
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new RemoteServiceImpl();
    }
}

這里主要就用到了IRemoteService.Stub類,你定義一個IRemoteService.Stub類的子類來具體實現IRemoteService接口,另外因為Stub類實現了IBinder接口所以可以作為onBind方法的返回值,同時因為實現了IBinder接口,所以Stub對象是一個可以跨進程使用的對象。

Manifest文件里注冊RemoteService

        <service
            android:name="com.example.jst.androidtest.RemoteService"
            android:process=":remote"/>

android:process=":remote" 指定了該服務是在另一個進程中。

程序中bind RemoteService

public class MainActivity extends Activity {

    private IRemoteService remoteService;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //IRemoteService.Stub.asInterface 將 經過跨進程傳輸過來的IBinder對象 轉換成了我們自定義的接口對象IRemoteService。
            remoteService = IRemoteService.Stub.asInterface(service);

            //通過IRemoteService對象我們才能調用我們自定義的接口方法getValue。
            try {
                remoteService.getValue();
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
}

bind RemoteService時最主要的就是ServiceConnection,在onServiceConnected里會將我們在RemoteService的onBind方法里返回的IBinder對象作為參數傳遞過來(實際上IBinder對象是被跨進程傳輸的),這里有一個關鍵方法IRemoteService.Stub.asInterface,這個asInterface方法將IBinder對象轉換成了我們能使用的IRemoteService對象。

android 系統Service 使用過程

以TELEPHONY_SERVICE為例,我們在使用系統的service的時候一般都是這么用的:

TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
tm.getSimState();

系統提供的Service也是運行在其他進程的,我們在使用系統的Service時內部也是用的AIDL這種標準的方式,只不過是系統幫我們封裝好了一些便利的工具類而已。

在介紹應該在哪些環節來hook 之前,我們先來了解為什么要hook 系統service。

hook 系統service的目的

遇到的問題

上面講了我們在應用中使用系統Service的方式,我們在插件apk中肯定也想使用系統的Service,但是系統Service在被使用的過程中會對使用者的包名進行驗證,因為插件apk沒有執行真正的安裝,所以系統Service不認識它,就會通不過驗證,從而不能使用系統Service.

解決方法

我們把在使用系統Service的過程中的某些步驟hook掉,把插件apk的包名替換成宿主的包名,因為宿主是真實安裝的,所以可以通過系統Service的驗證。實際上就是偷梁換柱。

應該在哪個步驟來進行hook

參考前面 "程序中bind RemoteService" 那一節的介紹

    private IRemoteService remoteService;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
           
           remoteService = IRemoteService.Stub.asInterface(service);
           remoteService.getValue();
        }
    }

我們應用最終使用的是 IRemoteService.Stub.asInterface(service)轉換后的IRemoteService接口對象,是在這個接口對象上調用的接口方法。我們的調用方法傳的參數什么的也是在調用這個接口對象上的方法時傳的,所以如果我們能把這個asInterface方法轉換后生成的接口對象hook掉,就能把插件apk的包名替換成宿主的包名。

如何hook掉asInterface轉換后的接口對象

以ITelephonyBinderHook為例,這里hook的原理跟上節講的一樣,可以參考上節的文章。

ITelephonyBinderHook

public class ITelephonyBinderHook extends BinderHook {

    public ITelephonyBinderHook(Context hostContext) {
        super(hostContext);
    }


    private final static String SERVICE_NAME = Context.TELEPHONY_SERVICE;

    @Override
    Object getOldObj() throws Exception {
        //獲取該Service對應的IBinder對象
        IBinder iBinder = MyServiceManager.getOriginService(SERVICE_NAME);
        //調用asInterface方法將該iBinder對象轉換為 接口對象 該返回值是作為被hook的oldObj被存儲起來的
        return ITelephonyCompat.asInterface(iBinder);
    }

    @Override
    public String getServiceName() {
        return SERVICE_NAME;
    }

    @Override
    protected BaseHookHandle createHookHandle() {
        return new ITelephonyHookHandle(mHostContext);
    }
}

看看父類BinderHook

abstract class BinderHook extends Hook implements InvocationHandler {

    private Object mOldObj;

    public BinderHook(Context hostContext) {
        super(hostContext);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//與上節講的一樣,略
    }

    abstract Object getOldObj() throws Exception;

    void setOldObj(Object mOldObj) {
        this.mOldObj = mOldObj;
    }

    public abstract String getServiceName();

    @Override
    protected void onInstall(ClassLoader classLoader) throws Throwable {
        //暫時忽略 一會會介紹
        new ServiceManagerCacheBinderHook(mHostContext, getServiceName()).onInstall(classLoader);
        //獲取子類實現的getOldObj()產生的mOldObj
        mOldObj = getOldObj();
        //生成代理對象
        Class<?> clazz = mOldObj.getClass();
        List<Class<?>> interfaces = Utils.getAllInterfaces(clazz);
        Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
        Object proxiedObj = MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, this);
        //將代理對象存儲起來
        MyServiceManager.addProxiedObj(getServiceName(), proxiedObj);
    }
}

跟上節講的hook機制的原理是一樣的。

HookedMethodHandler跟上節有一點不太一樣。

來看看ITelephonyHookHandle

public class ITelephonyHookHandle extends BaseHookHandle {

    @Override
    protected void init() {

        sHookedMethodHandlers.put("dial", new MyBaseHandler(mHostContext));
        sHookedMethodHandlers.put("call", new MyBaseHandler(mHostContext));
        sHookedMethodHandlers.put("endCall", new MyBaseHandler(mHostContext));
        sHookedMethodHandlers.put("endCallForSubscriber", new MyBaseHandler(mHostContext));
        sHookedMethodHandlers.put("answerRingingCall", new MyBaseHandler(mHostContext));
        sHookedMethodHandlers.put("answerRingingCallForSubscriber", new MyBaseHandler(mHostContext));
  //……
    }

    private static class MyBaseHandler extends ReplaceCallingPackageHookedMethodHandler {
        public MyBaseHandler(Context context) {
            super(context);
        }
    }
}

你會發現所有的HookedMethodHandler對象都是同一個MyBaseHandler,說明hook這些方法的目的都是一個,具體目的是什么呢,來看一下ReplaceCallingPackageHookedMethodHandler

ReplaceCallingPackageHookedMethodHandler

class ReplaceCallingPackageHookedMethodHandler extends HookedMethodHandler {

    public ReplaceCallingPackageHookedMethodHandler(Context hostContext) {
        super(hostContext);
    }

    @Override
    protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
            if (args != null && args.length > 0) {
                for (int index = 0; index < args.length; index++) {
                    if (args[index] != null && (args[index] instanceof String)) {
                        String str = ((String) args[index]);
                        //如果是插件apk的包名
                        if (isPackagePlugin(str)) {
                            //替換成宿主的包名
                            args[index] = mHostContext.getPackageName();
                        }
                    }
                }
            }
        }
        return super.beforeInvoke(receiver, method, args);
    }

    private static boolean isPackagePlugin(String packageName) throws RemoteException {
        return PluginManager.getInstance().isPluginPackage(packageName);
    }
}

這里就做了一件事,就是將插件apk的包名替換成宿主的包名,這樣在插件apk里就能正常的使用系統的service了。

代理對象掛載的問題:

一個非常重要的問題
回看一下BinderHook類的onInstall方法

//將代理對象存儲起來
MyServiceManager.addProxiedObj(getServiceName(), proxiedObj);

hook是hook掉了,但是這里只是把我們產生的代理對象存儲了起來,并沒有把它掛載到系統上,從而讓應用在使用系統Service的時候使用到我們生成的代理對象。
為什么這里沒法將我們產生的代理對象掛載上去呢。
我們來回顧一下上節講到的代理對象是如何掛載上去的。

IPackageManagerHook的onInstall方法

public class IPackageManagerHook extends ProxyHook {

    @Override
    protected void onInstall(ClassLoader classLoader) throws Throwable {
        
        Object currentActivityThread = ActivityThreadCompat.currentActivityThread();
        //從主線程對象里通過反射拿到sPackageManager對象,作為原始對象賦值給mOldObj
        setOldObj(FieldUtils.readField(currentActivityThread, "sPackageManager"));
        Class<?> iPmClass = mOldObj.getClass();
        //生成代理對象
        List<Class<?>> interfaces = Utils.getAllInterfaces(iPmClass);
        Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
        Object newPm = MyProxy.newProxyInstance(iPmClass.getClassLoader(), ifs, this);
        //用代理對象替換原始對象
        FieldUtils.writeField(currentActivityThread, "sPackageManager", newPm);
        //調用宿主的context的getPackageManager獲取PackageManager對象
        PackageManager pm = mHostContext.getPackageManager();
        Object mPM = FieldUtils.readField(pm, "mPM");
        //如果該對象不是我們的代理對象,就把該對象也替換成我們的代理對象
        if (mPM != newPm) {
            FieldUtils.writeField(pm, "mPM", newPm);
        }
    }

}

這里,我們找到了被hook的原始對象在系統的哪些類中是作為成員變量的,這個例子中是ActivityThread里的sPackageManager和PackageManager里的mPM。
我們需要把這些成員變量都替換成我們的代理對象,因為這些類都有可能被使用到。
而對于asInterface方法轉換后的對象,我猜測有可能是并沒有作為某個系統類的成員變量來存儲,所以我們無法像IPackageManagerHook那樣去掛載。
那么,如何才能將我們產生的代理對象掛載上去呢?

解決方案

    private IRemoteService remoteService;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
           
           remoteService = IRemoteService.Stub.asInterface(service);
           remoteService.getValue();
        }
    }

作為onServiceConnected參數的IBinder對象是很容易被掛載的,所以可以通過IBinder對象來解決掛載的問題。為什么通過IBinder對象能解決掛載的問題,具體原理跟asInterface方法內部的實現原理有關,后面會介紹到。
為什么IBinder對象是很容易被掛載的,我們需要先看看ServiceManager

Service管理者--ServiceManager

package android.os;

/** @hide */
public final class ServiceManager {
    private static final String TAG = "ServiceManager";

    private static IServiceManager sServiceManager;
    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }

    /**
     * Returns a reference to a service with the given name.
     * 
     * @param name the name of the service to get
     * @return a reference to the service, or <code>null</code> if the service doesn't exist
     */
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

    /**
     * Place a new @a service called @a name into the service
     * manager.
     * 
     * @param name the name of the new service
     * @param service the service object
     */
    public static void addService(String name, IBinder service) {
        try {
            getIServiceManager().addService(name, service, false);
        } catch (RemoteException e) {
            Log.e(TAG, "error in addService", e);
        }
    }

    /**
     * Place a new @a service called @a name into the service
     * manager.
     * 
     * @param name the name of the new service
     * @param service the service object
     * @param allowIsolated set to true to allow isolated sandboxed processes
     * to access this service
     */
    public static void addService(String name, IBinder service, boolean allowIsolated) {
        try {
            getIServiceManager().addService(name, service, allowIsolated);
        } catch (RemoteException e) {
            Log.e(TAG, "error in addService", e);
        }
    }
    
}

ServiceManager主要作用是管理Service:

  • sCache:提供了Service的緩存 HashMap<String, IBinder> sCache, ActivityThread在bindApplication()的時候,會從ServiceManager那邊獲得service cache。有這個service cache之后可以減少和ServiceManager的IPC(具體參考下面getService執行過程的介紹)。
  • getService 方法:獲取Service。 方法說明:先讀緩存sCache ,如果沒有再通過IPC來獲取(具體實現看源碼)
  • addService方法: 添加Service 。

這里ServiceManager里存儲的IBinder對象就是遠程Service跨進程傳輸過來的IBinder對象,就是我們調用asInterface轉換成接口對象的參數對象。
這里的IBinder對象就是ServiceManager的一個成員變量sCache,所以很容易被掛載上去。

解釋如何通過IBinder對象和asInterface方法來實現我們生成的代理對象的掛載問題,需要先看看asInterface方法的內部實現。

asInterface方法源碼

還是以前面講的 AIDL調用遠程Service 的例子為例

    public static abstract class Stub extends android.os.Binder implements com.example.jst.androidtest.IRemoteService {
        private static final java.lang.String DESCRIPTOR = "com.example.jst.androidtest.IRemoteService";

        /**
         * Cast an IBinder object into an com.example.jst.androidtest.IRemoteService interface,
         * generating a proxy if needed.
         */
        public static com.example.jst.androidtest.IRemoteService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            //如果iin 不為空  并且是IRemoteService類型的 就返回iin
            if (((iin != null) && (iin instanceof com.example.jst.androidtest.IRemoteService))) {
                return ((com.example.jst.androidtest.IRemoteService) iin);
            }
            //否則返回一個實現了IRemoteService接口的Proxy對象
            return new com.example.jst.androidtest.IRemoteService.Stub.Proxy(obj);
        }

        private static class Proxy implements com.example.jst.androidtest.IRemoteService {
//實現細節略
        }

因為是跨進程使用的,所以我們在我們的應用進程中拿到的IBinder對象的類型并不是我們在RemoteService里的那個繼承了android.os.Binder 實現了 IRemoteService接口的 IRemoteService.Stub類的子類RemoteServiceImpl,因為是跨進程的,所以底層會進行一些處理,我們這里拿到的IBinder對象的類型實際上是BinderProxy類型。(如果RemoteService沒有跨進程,這里拿到的IBinder對象就不是BinderProxy類型的,而是RemoteServiceImpl對象本身)

BinderProxy


final class BinderProxy implements IBinder {
//……
    public IInterface queryLocalInterface(String descriptor) {
        return null;
    }
//……
}

BinderProxy定義在android.os.Binder.java文件里,但是并不是Binder類的內部類。

需要注意的是BinderProxy的queryLocalInterface方法返回的是null。(如果不是跨進程的,queryLocalInterface方法就是android.os.Binder里的queryLocalInterface方法,并不是返回null,具體參考android.os.Binder源碼)
通過看asInterface源碼發現,如果queryLocalInterface返回null,asInterface方法會返回一個實現了IRemoteService接口的Proxy對象,所以一般情況下在跨進程的時候我們在外面使用的也就是這個Proxy對象。但是,這里還有一個分支是queryLocalInterface不返回null,而是返回一個實現了IRemoteService接口的對象,此時asInterface方法返回的就是這個對象。關鍵就在這里,如果我們hook掉這個IBinder對象的queryLocalInterface方法,讓queryLocalInterface返回我們在前面生成的代理對象,asInterface方法就會返回這個代理對象,這樣外界拿到的就是這個代理對象,不就解決了我們前面生成的代理對象的掛載問題了嗎。

對IBinder對象的hook ServiceManagerCacheBinderHook

onInstall

    @Override
    protected void onInstall(ClassLoader classLoader) throws Throwable {
        //從ServiceManager對象里讀出來sCache
        Object sCacheObj = FieldUtils.readStaticField(ServiceManagerCompat.Class(), "sCache");
        if (sCacheObj instanceof Map) {
            Map sCache = (Map) sCacheObj;
            //從sCache中讀出來該mServiceName對應的service
            Object Obj = sCache.get(mServiceName);
            if (Obj != null && false) {
                //FIXME 已經有了怎么處理?這里我們只是把原來的給remove掉,再添加自己的。程序下次取用的時候就變成我們hook過的了。
                //但是這樣有缺陷。
                throw new RuntimeException("Can not install binder hook for " + mServiceName);
            } else {
                //將該service移除
                sCache.remove(mServiceName);
                //這里調用ServiceManager的getService方法來取原始的IBinder對象,參考getService源碼我們知道,因為sCache里的被移除了,
                //所以這里會執行一次IPC來獲取原始的IBinder對象,這里為什么不用sCache里的對象作為原始的IBinder對象呢,我想應該是擔心sCache
                //里有可能已經是替換過的代理對象了,比如你不知道在之前的哪個時機已經將代理對象替換了原始對象,所以執行一次IPC從系統全新獲取
                //的這個對象一定是原始的IBinder對象。
                IBinder mServiceIBinder = ServiceManagerCompat.getService(mServiceName);
                if (mServiceIBinder == null) {
                    //這里做了異常補救,如果調用getService方法沒拿到原始的IBinder對象 就看看從sCache里面的那個是不是原始對象,
                    // 如果不是代理對象,說明是原始對象
                    if (Obj != null && Obj instanceof IBinder && !Proxy.isProxyClass(Obj.getClass())) {
                        mServiceIBinder = ((IBinder) Obj);
                    }
                }
                if (mServiceIBinder != null) {
                    //MyServiceManager是工具類,用來存儲數據,這里將原始的IBinder對象存起來
                    MyServiceManager.addOriginService(mServiceName, mServiceIBinder);
                    //生成代理對象
                    Class clazz = mServiceIBinder.getClass();
                    List<Class<?>> interfaces = Utils.getAllInterfaces(clazz);
                    Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
                    IBinder mProxyServiceIBinder = (IBinder) MyProxy.newProxyInstance(clazz.getClassLoader(), ifs, this);
                    //將代理對象添加進sCache中
                    sCache.put(mServiceName, mProxyServiceIBinder);
                    //將代理對象也存儲起來
                    MyServiceManager.addProxiedServiceCache(mServiceName, mProxyServiceIBinder);
                }
            }
        }
    }

該onInstall方法是在BinderHook的onInstall方法里被調用,具體看文章前面對BinderHook的介紹。

ServiceManagerHookHandle

    private class ServiceManagerHookHandle extends BaseHookHandle {

        private ServiceManagerHookHandle(Context context) {
            super(context);
        }

        @Override
        protected void init() {
            sHookedMethodHandlers.put("queryLocalInterface", new queryLocalInterface(mHostContext));
        }


        class queryLocalInterface extends HookedMethodHandler {
            public queryLocalInterface(Context context) {
                super(context);
            }

            @Override
            protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable {
                Object localInterface = invokeResult;
                //這里是我們在 BinderHook.onInstall() 方法里生成的代理對象
                Object proxiedObj = MyServiceManager.getProxiedObj(mServiceName);
                if (localInterface == null && proxiedObj != null) {
                    setFakedResult(proxiedObj);
                }
            }
        }
    }

至此,hook 系統service的內容就講解完了,為了讓插件apk里能夠透明的使用系統的Service真是不容易,這里比較難的點是hook了好幾個地方,一定要知道hook各個地方的目的是什么,才能更好的理解其中的原理

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容