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