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

相关推荐
Meteors.43 分钟前
Android约束布局(ConstraintLayout)常用属性
android
alexhilton1 小时前
玩转Shader之学会如何变形画布
android·kotlin·android jetpack
whysqwhw5 小时前
安卓图片性能优化技巧
android
风往哪边走6 小时前
自定义底部筛选弹框
android
Yyyy4826 小时前
MyCAT基础概念
android
Android轮子哥7 小时前
尝试解决 Android 适配最后一公里
android
雨白8 小时前
OkHttp 源码解析:enqueue 非同步流程与 Dispatcher 调度
android
风往哪边走9 小时前
自定义仿日历组件弹框
android
没有了遇见9 小时前
Android 外接 U 盘开发实战:从权限到文件复制
android
木西10 小时前
React Native DApp 开发全栈实战·从 0 到 1 系列(eas构建自定义客户端)
react native·web3·app