Android藍牙之Gatt Hook

許多人可能對Hook技術有些陌生,其實從字面意思上理解這就類似一個鉤子,掛在了系統中的某些地方,然後當執行流程經過該處時會被勾住,可以選擇放行或截獲,或做點手腳偷偷改改參數,或記錄日誌,或檢查權限,或post到別的上下文去執行,應用場景還挺多。

本文會重點討論藍牙相關的Hook,要全局監測所有BLE藍牙設備的操作,對於不那麼活躍的設備我們會斷開連接並釋放資源,畢竟藍牙通信信道是有限的。那麼如何全局監測所有藍牙設備的操作呢?可以在所有操作設備的地方打點,不過這樣太麻煩,給代碼搞亂了不說,即便漏了也不知道。好一點的辦法是封裝一套接口供所有人調用,打點在接口內做好,外部不用關心。不過如果有人不守規矩不走這套接口則依然會漏了。接下來本文會提出一種新的解決思路,不用到處打點,也不用封裝這麼一套公共接口。其核心原理就是在系統api內部掛一個鉤子,用戶操作設備總要調系統api的,這樣就能被我們攔截到並且萬無一失。現在的問題是如何在系統內部打入這麼一個鉤子而不被察覺呢?

第一步,我們需要調研這個鉤子下在哪裏。我們注意到BLE設備操作都需要有gatt句柄,我們就以gatt爲入口,先看這個gatt是在哪裏獲取的,如下:

public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback) {
    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    IBluetoothManager managerService = adapter.getBluetoothManager();
    try {
        IBluetoothGatt iGatt = managerService.getBluetoothGatt();
        if (iGatt == null) {
            // BLE is not supported
            return null;
        }
        BluetoothGatt gatt = new BluetoothGatt(context, iGatt, this);
        gatt.connect(autoConnect, callback);
        return gatt;
    } catch (RemoteException e) {Log.e(TAG, "", e);}
    return null;
}

這個gatt核心其實是IBluetoothGatt,是IBluetoothManager調用getBluetoothGatt返回的。而這個IBluetoothManager是BluetoothAdapter中的,我們將目光轉移到BluetoothAdapter中,如下:

public static synchronized BluetoothAdapter getDefaultAdapter() {
    if (sAdapter == null) {
        IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE);
        if (b != null) {
            IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b);
            sAdapter = new BluetoothAdapter(managerService);
        } else {
            Log.e(TAG, "Bluetooth binder is null");
        }
    }
    return sAdapter;
}

可見BluetoothAdapter是個單例,且IBluetoothManager是從ServiceManager中獲取的。我們再將目光轉移到ServiceManager,如下:

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;
}

這裏我們發現ServiceManager中所有的IBinder都緩存在sCache中,這是個靜態全局變量,是個下鉤子的好入口。

到這裏開始講核心思路了,首先通過反射拿到ServiceManager中的IBluetoothManager的IBinder,動態生成一個代理對象塞回ServiceManager的sCache中。這樣BluetoothAdapter初始化時拿到的就是個代理的IBinder。

public static void hook() {
    Method getService = ServiceManagerCompat.getService();
    IBinder iBinder = HookUtils.invoke(getService, null, BLUETOOTH_MANAGER);

    IBinder proxy = (IBinder) Proxy.newProxyInstance(iBinder.getClass().getClassLoader(),
            new Class<?>[]{IBinder.class},
            new BluetoothManagerBinderProxyHandler(iBinder));

    HashMap<String, IBinder> cache = ServiceManagerCompat.getCacheValue();
    cache.put(BLUETOOTH_MANAGER, proxy);
}

到這裏事情還遠沒完,我們最終的目的是攔截所有關於IBluetoothGatt的調用。接下來看BluetoothAdapter中拿到這個IBluetoothManager的IBinder之後調用了IBluetoothManager.Stub.asInterface來將這個IBinder轉爲IBluetoothManager,我們看asInterface的實現:

public static android.bluetooth.IBluetoothManager asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof android.bluetooth.IBluetoothManager))) {
        return ((android.bluetooth.IBluetoothManager) iin);
    }
    return new android.bluetooth.IBluetoothManager.Stub.Proxy(obj);
}

這裏傳入的參數IBinder可能是Binder,也可能是BinderProxy。如果是Binder則queryLocalInterface返回的是Binder自身,如果是BinderProxy則queryLocalInterface返回的是null。所以我們看到如果是Binder實體,則這裏直接返回自身,如果是Binder代理,則這裏會封裝一個業務代理對象返回。所以我們可以在這裏攔截queryLocalInterface,返回一個代理的IBluetoothManager對象。如下:

private static class BluetoothManagerBinderProxyHandler implements InvocationHandler {

    private  IBinder iBinder;

    private Class<?> iBluetoothManagerClaz;
    private Object iBluetoothManager;

    BluetoothManagerBinderProxyHandler(IBinder iBinder) {
        this.iBinder = iBinder;

        this.iBluetoothManagerClaz = HookUtils.getClass("android.bluetooth.IBluetoothManager");
        Class<?> stub = HookUtils.getClass("android.bluetooth.IBluetoothManager$Stub");
        Method asInterface = HookUtils.getMethod(stub, "asInterface", IBinder.class);
        this.iBluetoothManager = HookUtils.invoke(asInterface, null, iBinder);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("queryLocalInterface".equals(method.getName())) {
            return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
                    new Class<?>[] {IBinder.class, IInterface.class, iBluetoothManagerClaz},
                    new BluetoothManagerProxyHandler(iBluetoothManager));
        }
        return method.invoke(iBinder, args);
    }
}

當BluetoothAdapter拿到這個代理的IBluetoothManager對象後,調用getBluetoothGatt時,我們需要再次攔截返回一個gatt的代理對象,如下:

private static class BluetoothManagerProxyHandler implements InvocationHandler {

    private Object iBluetoothManager;

    private Class<?> bluetoothGattClaz;
    private Object bluetoothGatt;

    BluetoothManagerProxyHandler(Object iBluetoothManager) {
        this.iBluetoothManager = iBluetoothManager;

        this.bluetoothGattClaz = HookUtils.getClass("android.bluetooth.IBluetoothGatt");
        Class<?> stub = HookUtils.getClass("android.bluetooth.IBluetoothManager");
        Method method = HookUtils.getMethod(stub, "getBluetoothGatt");
        this.bluetoothGatt = HookUtils.invoke(method, iBluetoothManager);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("getBluetoothGatt".equals(method.getName())) {
            return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
                    new Class<?>[] {IBinder.class, IInterface.class, bluetoothGattClaz},
                    new BluetoothGattProxyHandler(bluetoothGatt));
        }
        return method.invoke(iBluetoothManager, args);
    }
}

拿到這個gatt的代理對象之後,我們就可以攔截其所有api,監測設備的一切活動,如下:

private static class BluetoothGattProxyHandler implements InvocationHandler {

    private Object bluetoothGatt;

    BluetoothGattProxyHandler(Object bluetoothGatt) {
        this.bluetoothGatt = bluetoothGatt;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        BluetoothLog.v(String.format("IBluetoothGatt method: %s", method.getName()));
        return method.invoke(bluetoothGatt, args);
    }
}

這裏只是打了一行日誌而已,我們可以添加更復雜的監控邏輯。到這裏所有設備的gatt操作都會被攔截下來了,而且做的神不知鬼不覺。

我們以上的這些下鉤子的過程都可以封裝到一個類中供別人調用,即便APP是多進程架構,只要一行代碼就可以監控該進程中所有Gatt操作,並立即報到主進程中。

類似的還可以給網絡請求下鉤子,比如插件中不允許有自己的網絡請求,必須全部走主APP的api,那麼就可以通過這種手段攔截掉所有的網絡請求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章