Android 记录一次因隐私合规引发的权限hook
背景
一天,本该快乐编码flutter的我,突然被集团法务钉了,说在合规扫描排查中发现某xxxApp存在在App静默状态下调用某敏感权限获取用户信息,不合规
。通过调用栈排查发现是某第三方推送sdk在静默状态下心跳调用的,本着能动口不动脑的准则,我联系了上了第三方的技术,询问是否有静默方面的api,结果一番舌战后,对方告诉我他们隐私政策里有添加说明,之后也没有想要改动的打算,但是集团那边说在隐私里说明也不行。
综上,那只能自己动手。
解决的方法:是通过hook系统权限,添加某个业务逻辑点拦截并处理。
涉及到的知识点:java反射、动态代理、一点点耐心。
本文涉及到的敏感权限:
//wifi
android.net.wifi.WifiManager.getScanResults()
android.net.wifi.WifiManager.getConnectionInfo()
//蓝牙
android.bluetooth.le.BluetoothLeScanner.startScan()
//定位
android.location.LocationManager.getLastKnownLocation()
开始
wifi篇
1.首先寻找切入点,以方法WifiManager.getScanResults()
为例查看源码
public List<ScanResult> getScanResults() {
try {
return mService.getScanResults(mContext.getOpPackageName(),
mContext.getAttributionTag());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
发现目标方法是由mService
对象调用,它的定义
@UnsupportedAppUsage
IWifiManager mService;
查看IWifiManager
interface IWifiManager{
...
List<ScanResult> getScanResults(String callingPackage, String callingFeatureId);
WifiInfo getConnectionInfo(String callingPackage, String callingFeatureId);
...
}
可以看到IWifiManager
是一个接口类,包含所需方法,可以当成一个切入点。
若以IWifiManager
为切入点,进行hook
方法一
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
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()
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
继续查看context的实现contextImpl
中
@Override
public Object getSystemService(String name) {
...
return SystemServiceRegistry.getSystemService(this, name);
}
查看SystemServiceRegistry.getSystemService
静态方法
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
中
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));
}});
}
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(...)
TServiceClass createService(@NonNull Context context, @NonNull IBinder serviceBinder);
其中第二个参数是一个IBinder对象,它的创建
ServiceManager.getServiceOrThrow(serviceName)
继续
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;
}
最终在getService
中IBinder
缓存在sCache
中,它是一个静态变量
@UnsupportedAppUsage
private static Map<String, IBinder> sCache = new ArrayMap<String, IBinder>();
综上,如果可以创建新的IBinder
,再替换掉sCache
中的原始值就可以实现所需。
若以sCache
为一个切入点
方法二
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();
}
}
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 iwifiManagerObject = asInterfaceMethod.invoke(null, originalTarget);
//生成新IWifiManager代理
Class<?> iwifiManagerClass = HookUtil.getClass("android.net.wifi.IWifiManager");
return Proxy.newProxyInstance(originalTarget.getClass().getClassLoader(),
new Class[]{IBinder.class, IInterface.class, iwifiManagerClass},
new WifiManagerProxy(iLocationManagerObject));
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
至此完成无需上下文的全局拦截。
蓝牙篇
以BluetoothLeScanner.startScan()
为例查找切入点,以下省略非必需源码粘贴
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
的创建
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
为切入点
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
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
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;
}
}
处理业务逻辑
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()
为例查找切入点,此处不粘贴源码,直接展示
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();
}
}
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的权限都可追溯到ServiceManager
,ServiceManager
中的sCache
缓存了权限相关的IBinder
,以此为切入点可以进行统一处理,不需要引入context。
在此记录一下因隐私合规引发的hook处理流程,同时也想吐槽一下国内应用市场App上架审核是真滴难,每个市场的合规扫描标准都不一样。
附录
源码查看网站 aospxref.com/
路径:/frameworks/base/core/java/android/os/ServiceManager.j
作者:秋至
来源:juejin.cn/post/7262243685898960955
ava