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 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,那么就可以通过这种手段拦截掉所有的网络请求。

你可能感兴趣的:(Android,Android蓝牙)