Android蓝牙之Gatt Hook

来源:互联网 发布:mac u盘装系统 sierra 编辑:程序博客网 时间:2024/05/16 18:10

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

1 0