Android 记录一次因隐私合规引发的权限hook

背景

一天,本该快乐编码flutter的我,突然被集团法务钉了,说在合规扫描排查中发现某xxxApp存在在App静默状态下调用某敏感权限获取用户信息,不合规。通过调用栈排查发现是某第三方推送sdk在静默状态下心跳调用的,本着能动口不动脑的准则,我联系了上了第三方的技术,询问是否有静默方面的api,结果一番舌战后,对方告诉我他们隐私政策里有添加说明,之后也没有想要改动的打算,但是集团那边说在隐私里说明也不行。

综上,那只能自己动手。

解决的方法:是通过hook系统权限,添加某个业务逻辑点拦截并处理。

涉及到的知识点:java反射、动态代理、一点点耐心。

本文涉及到的敏感权限:

scss 复制代码
//wifi
android.net.wifi.WifiManager.getScanResults()
android.net.wifi.WifiManager.getConnectionInfo()
//蓝牙
android.bluetooth.le.BluetoothLeScanner.startScan()
//定位
android.location.LocationManager.getLastKnownLocation()

开始

wifi篇

1.首先寻找切入点,以方法WifiManager.getScanResults()为例查看源码

csharp 复制代码
public List<ScanResult> getScanResults() {
    try {
        return mService.getScanResults(mContext.getOpPackageName(),
                mContext.getAttributionTag());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

发现目标方法是由mService对象调用,它的定义

less 复制代码
@UnsupportedAppUsage
IWifiManager mService;

查看IWifiManager

arduino 复制代码
interface IWifiManager{
 ...

List<ScanResult> getScanResults(String callingPackage, String callingFeatureId);

WifiInfo getConnectionInfo(String callingPackage, String callingFeatureId);

 ...
}

可以看到IWifiManager是一个接口类,包含所需方法,可以当成一个切入点。

若以IWifiManager为切入点,进行hook

方法一
ini 复制代码
private static void hookWifi(Context context) {
    try {
        //反射获取相关类、字段对象
        Class<?> iWifiManagerClass = HookUtil.getClass("android.net.wifi.IWifiManager");
        Field serviceField = HookUtil.getField("android.net.wifi.WifiManager", "mService");
        
        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        //获取原始mService对象
        Object realIwm = serviceField.get(wifiManager);

        //创建IWifiManager代理
        Object proxy = Proxy.newProxyInstance(iWifiManagerClass.getClassLoader(),
                new Class[]{iWifiManagerClass}, new WifiManagerProxy(realIwm));

        //设置新代理
        serviceField.set(wifiManager, proxy);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

其中新代理类实现InvocationHandler

typescript 复制代码
public class WifiManagerProxy implements InvocationHandler {

    private final Object mOriginalTarget;

    public WifiManagerProxy(Object mOriginalTarget) {
        this.mOriginalTarget = mOriginalTarget;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (("getScanResults".equals(methodName) || "getConnectionInfo".equals(methodName))){
            //todo something
            return null;
        }
        return method.invoke(mOriginalTarget,args);
    }
}

2.考虑context问题:

获取原始wifiManager需要用到context上下文,不同context获取到的wifiManager不同。若统一使用application上下文可以基本覆盖所需,但是可能会出现遗漏(比如某处使用的是activity#context)。为了保证hook开关唯一,尝试再往上查找新的切入点。

查看获取wifiManager方法,由context调用.getSystemService()

ini 复制代码
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

继续查看context的实现contextImpl

typescript 复制代码
@Override
public Object getSystemService(String name) {
    ...
    return SystemServiceRegistry.getSystemService(this, name);
}

查看SystemServiceRegistry.getSystemService静态方法

kotlin 复制代码
public static Object getSystemService(ContextImpl ctx, String name) {
    if (name == null) {
        return null;
    }
    final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    if (fetcher == null) {
        ...
        return null;
    }

    final Object ret = fetcher.getService(ctx);
    if (sEnableServiceNotFoundWtf && ret == null) {
        ...
        return null;
    }
    return ret;
}

服务由SYSTEM_SERVICE_FETCHERS获取,它是一个静态的HashMap,它的put方法在registerService

less 复制代码
private static <T> void registerService(@NonNull String serviceName,
        @NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
   ...
   SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
   ...
}

static{
...
//Android 11及以上
WifiFrameworkInitializer.registerServiceWrappers()
...
}

...

@SystemApi
public static <TServiceClass> void registerContextAwareService(
        @NonNull String serviceName, @NonNull Class<TServiceClass> serviceWrapperClass,
        @NonNull ContextAwareServiceProducerWithoutBinder<TServiceClass> serviceProducer) {
    ...
    registerService(serviceName, serviceWrapperClass,
        new CachedServiceFetcher<TServiceClass>() {
            @Override
            public TServiceClass createService(ContextImpl ctx)
                    throws ServiceNotFoundException {
                return serviceProducer.createService(
                        ctx.getOuterContext(),
                        ServiceManager.getServiceOrThrow(serviceName));
            }});

}
csharp 复制代码
public static void registerServiceWrappers() {
...
SystemServiceRegistry.registerContextAwareService(
                  Context.WIFI_SERVICE,
                  WifiManager.class,
                  (context, serviceBinder) -> {
                      IWifiManager service = IWifiManager.Stub.asInterface(serviceBinder);
                      return new WifiManager(context, service, getInstanceLooper());
                  }
          );
}

SYSTEM_SERVICE_FETCHERS静态代码块中通过.registerServiceWrappers()注册WIFI_SERVICE服务。

registerService中new了一个CachedServiceFetcher,它返回一个serviceProducer.createService(...)

less 复制代码
TServiceClass createService(@NonNull Context context, @NonNull IBinder serviceBinder);

其中第二个参数是一个IBinder对象,它的创建

scss 复制代码
ServiceManager.getServiceOrThrow(serviceName)

继续

java 复制代码
public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
          final IBinder binder = getService(name);
          if (binder != null) {
              return binder;
          } else {
              throw new ServiceNotFoundException(name);
          }
      }
...
@UnsupportedAppUsage
public static IBinder getService(String name) {
          try {
              IBinder service = sCache.get(name);
              if (service != null) {
                  return service;
              } else {
                  return Binder.allowBlocking(rawGetService(name));
              }
          } catch (RemoteException e) {
              Log.e(TAG, "error in getService", e);
          }
          return null;
      }

最终在getServiceIBinder缓存在sCache中,它是一个静态变量

typescript 复制代码
@UnsupportedAppUsage
private static Map<String, IBinder> sCache = new ArrayMap<String, IBinder>();

综上,如果可以创建新的IBinder,再替换掉sCache中的原始值就可以实现所需。

若以sCache为一个切入点

方法二
ini 复制代码
private static void hookWifi2() {
    try {
        Method getServiceMethod = HookUtil.getMethod("android.os.ServiceManager", "getService", String.class);
        Object iBinderObject = getServiceMethod.invoke(null, Context.WIFI_SERVICE);

        Field sCacheFiled = HookUtil.getField("android.os.ServiceManager", "sCache");
        Object sCacheValue = sCacheFiled.get(null);
        
        //生成代理IBinder,并替换原始值
        if (iBinderObject != null && sCacheValue != null) {
            IBinder iBinder = (IBinder) iBinderObject;
            Map<String, IBinder> sCacheMap = (Map<String, IBinder>) sCacheValue;
            Object proxy = Proxy.newProxyInstance(iBinder.getClass().getClassLoader(), new Class[]{IBinder.class}, new WifiBinderProxy(iBinder));
            sCacheMap.put(Context.WIFI_SERVICE, (IBinder) proxy);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
typescript 复制代码
public class WifiBinderProxy implements InvocationHandler {

    private final IBinder originalTarget;

    public WifiBinderProxy(IBinder originalTarget) {
        this.originalTarget = originalTarget;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("queryLocalInterface".equals(method.getName())) {
            Object hook = hookQueryLocalInterface();
            if (hook != null){
                return hook;
            }
        }
        return method.invoke(originalTarget, args);
    }

    private Object hookQueryLocalInterface(){
        try {
            //获取原始IWifiManager对象
            Method asInterfaceMethod = HookUtil.getMethod("android.net.wifi.IWifiManager$Stub", "asInterface", IBinder.class);
            Object iLocationManagerObject = asInterfaceMethod.invoke(null, originalTarget);

            //生成新IWifiManager代理
            Class<?> iLocationManagerClass = HookUtil.getClass("android.net.wifi.IWifiManager");
            return Proxy.newProxyInstance(originalTarget.getClass().getClassLoader(),
                    new Class[]{IBinder.class, IInterface.class, iLocationManagerClass},
                    new WifiManagerProxy(iLocationManagerObject));
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

至此完成无需上下文的全局拦截。

蓝牙篇

BluetoothLeScanner.startScan()为例查找切入点,以下省略非必需源码粘贴

ini 复制代码
private int startScan(List<ScanFilter> filters, ScanSettings settings,
        final WorkSource workSource, final ScanCallback callback,
        final PendingIntent callbackIntent,
        List<List<ResultStorageDescriptor>> resultStorages) {
        ...
        IBluetoothGatt gatt;
        try {
            gatt = mBluetoothManager.getBluetoothGatt();
        } catch (RemoteException e) {
            gatt = null;
        }
...
        
private final IBluetoothManager mBluetoothManager;

...
public BluetoothLeScanner(BluetoothAdapter bluetoothAdapter) {
    mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
    mBluetoothManager = mBluetoothAdapter.getBluetoothManager();
    ...
}

向上查找IBluetoothManager,它在BluetoothAdapter中;向下代理getBluetoothGatt方法处理IBluetoothGatt

查看BluetoothAdapter的创建

java 复制代码
public static BluetoothAdapter createAdapter(AttributionSource attributionSource) {
    IBinder binder = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE);
    if (binder != null) {
        return new BluetoothAdapter(IBluetoothManager.Stub.asInterface(binder),
                attributionSource);
    } else {
        Log.e(TAG, "Bluetooth binder is null");
        return null;
    }
}

ok,他也包含由ServiceManager中获取得到IBinder,然后进行后续操作。

若以IBluetoothManager为切入点

ini 复制代码
private static void hookBluetooth() {
    try {
        //反射ServiceManager中的getService(BLUETOOTH_MANAGER_SERVICE = 'bluetooth_manager')方法,获取原始IBinder
        Method getServiceMethod = HookUtil.getMethod("android.os.ServiceManager", "getService", String.class);
        Object iBinderObject = getServiceMethod.invoke(null, "bluetooth_manager");

        //获取ServiceManager对象sCache
        Field sCacheFiled = HookUtil.getField("android.os.ServiceManager", "sCache");
        Object sCacheValue = sCacheFiled.get(null);

        //动态代理生成代理iBinder插入sCache
        if (iBinderObject != null && sCacheValue != null) {
            IBinder iBinder = (IBinder) iBinderObject;
            Map<String, IBinder> sCacheMap = (Map<String, IBinder>) sCacheValue;
            Object proxy = Proxy.newProxyInstance(iBinder.getClass().getClassLoader(), new Class[]{IBinder.class}, new BluetoothBinderProxy(iBinder));
            sCacheMap.put("bluetooth_manager", (IBinder) proxy);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

代理IBluetoothManager

typescript 复制代码
public class BluetoothBinderProxy implements InvocationHandler {

    private final IBinder mOriginalTarget;

    public BluetoothBinderProxy(IBinder originalTarget) {
        this.mOriginalTarget = originalTarget;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("queryLocalInterface".equals(method.getName())) {
            //拦截
            Object hook = hookQueryLocalInterface();
            if (hook != null){
                return hook;
            }
        }
        //不拦截
        return method.invoke(mOriginalTarget, args);
    }

    private Object hookQueryLocalInterface(){
        try {
            //获取原始IBluetoothManager对象
            Method asInterfaceMethod = HookUtil.getMethod("android.bluetooth.IBluetoothManager$Stub", "asInterface", IBinder.class);
            Object iBluetoothManagerObject = asInterfaceMethod.invoke(null, mOriginalTarget);

            //生成代理IBluetoothManager
            Class<?> iBluetoothManagerClass = HookUtil.getClass("android.bluetooth.IBluetoothManager");
            return Proxy.newProxyInstance(mOriginalTarget.getClass().getClassLoader(),
                    new Class[]{IBinder.class, IInterface.class, iBluetoothManagerClass},
                    new BluetoothManagerProxy(iBluetoothManagerObject));
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

代理IBluetoothGatt

typescript 复制代码
public class BluetoothManagerProxy implements InvocationHandler {

    private final Object mOriginalTarget;

    public BluetoothManagerProxy(Object mOriginalTarget) {
        this.mOriginalTarget = mOriginalTarget;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("getBluetoothGatt".equals(method.getName())) {
            Object object = method.invoke(mOriginalTarget,args);
            Object hook = hookGetBluetoothGatt(object);
            if (hook != null){
                return hook;
            }
        }
        return method.invoke(mOriginalTarget, args);
    }

    private Object hookGetBluetoothGatt(Object object) {
        try {
            Class<?> iBluetoothGattClass = HookUtil.getClass("android.bluetooth.IBluetoothGatt");
            return Proxy.newProxyInstance(mOriginalTarget.getClass().getClassLoader(),
                    new Class[]{IBinder.class, IInterface.class, iBluetoothGattClass},
                    new BluetoothGattProxy(object));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

处理业务逻辑

typescript 复制代码
public class BluetoothGattProxy implements InvocationHandler {

    private final Object mOriginalTarget;

    public BluetoothGattProxy(Object mOriginalTarget) {
        this.mOriginalTarget = mOriginalTarget;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("startScan".equals(method.getName())){
            //todo something
            return null;
        }
        return method.invoke(mOriginalTarget,args);
    }
}

定位篇

LocationManager.getLastKnownLocation()为例查找切入点,此处不粘贴源码,直接展示

ini 复制代码
private static void hookLocation() {
    try {
        Method getServiceMethod = HookUtil.getMethod("android.os.ServiceManager", "getService", String.class);
        Object iBinderObject = getServiceMethod.invoke(null, Context.LOCATION_SERVICE);

        Field sCacheFiled = HookUtil.getField("android.os.ServiceManager", "sCache");
        Object sCacheValue = sCacheFiled.get(null);

        //动态代理生成代理iBinder插入sCache
        if (iBinderObject != null && sCacheValue != null) {
            IBinder iBinder = (IBinder) iBinderObject;
            Map<String, IBinder> sCacheMap = (Map<String, IBinder>) sCacheValue;
            Object proxy = Proxy.newProxyInstance(iBinder.getClass().getClassLoader(), new Class[]{IBinder.class}, new LocationBinderProxy(iBinder));
            sCacheMap.put(Context.LOCATION_SERVICE, (IBinder) proxy);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
typescript 复制代码
public class LocationBinderProxy implements InvocationHandler {

    private final IBinder originalTarget;

    public LocationBinderProxy(IBinder originalTarget) {
        this.originalTarget = originalTarget;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("queryLocalInterface".equals(method.getName())) {
            Object hook = hookQueryLocalInterface();
            if (hook != null){
                return hook;
            }
        }
        return method.invoke(originalTarget, args);
    }

    private Object hookQueryLocalInterface(){
        try {
            //获取原始ILocationManager对象
            Method asInterfaceMethod = HookUtil.getMethod("android.location.ILocationManager$Stub", "asInterface", IBinder.class);
            Object iLocationManagerObject = asInterfaceMethod.invoke(null, originalTarget);

            //生成代理ILocationManager
            Class<?> iLocationManagerClass = HookUtil.getClass("android.location.ILocationManager");
            return Proxy.newProxyInstance(originalTarget.getClass().getClassLoader(),
                    new Class[]{IBinder.class, IInterface.class, iLocationManagerClass},
                    new LocationManagerProxy(iLocationManagerObject));
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}

总结

作为Android进程间通信机制Binder的守护进程,本次所hook的权限都可追溯到ServiceManagerServiceManager中的sCache缓存了权限相关的IBinder,以此为切入点可以进行统一处理,不需要引入context。

在此记录一下因隐私合规引发的hook处理流程,同时也想吐槽一下国内应用市场App上架审核是真滴难,每个市场的合规扫描标准都不一样。

附录

源码查看网站 aospxref.com/

路径:/frameworks/base/core/java/android/os/ServiceManager.java

相关推荐
服装学院的IT男4 小时前
【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-2
android
Arms2064 小时前
android 全面屏最底部栏沉浸式
android
服装学院的IT男4 小时前
【Android 源码分析】Activity生命周期之onStop-1
android
ChinaDragonDreamer6 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
网络研究院8 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下9 小时前
android navigation 用法详细使用
android
小比卡丘11 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭12 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
落落落sss14 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.14 小时前
数据库语句优化
android·数据库·adb