許多人可能對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,那麼就可以通過這種手段攔截掉所有的網絡請求。