Android 违规获取用户隐私(获取MAC地址)整改

前几天,收到公司App违规收取用户隐私的邮件,说是存在收集设备MAC地址的行为。
这就让我很方了,上次已经整改过一次违规获取用户隐私的问题了,这次又来。。
因为上次整改的时候,已将所有的第三方库移到用户同意了隐私协议后,才去初始化的,自己的代码又不会去获取这些数据,理应不会再出现获取,所以就很奇怪,不知道哪里出了问题。

后来想到,既然是去获取了MAC地址,必定要调用系统的API,那么我只要去HOOK系统的方法,就可以知道在什么时候,去获取了MAC地址了。

由于各个系统版本获取MAC地址的方式不同,所以特意拿了个Android5.1的手机进行测试。

	/**
     * Android 6.0 之前(不包括6.0)获取mac地址
     * 必须的权限 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
     *
     * @param context * @return
     */
    private String getMacDefault(Context context) {
    
    
        String mac = "0";
        if (context == null) {
    
    
            return mac;
        }
        WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        WifiInfo info = null;
        try {
    
    
            info = wifi.getConnectionInfo();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

        if (info == null) {
    
    
            return null;
        }
        mac = info.getMacAddress();

        return mac;
    }

可以看到,我们在Android6.0以前是通过WifiManager.getConnectionInfo()的方式来获取相关数据的。
我们来看下这个方法。

	IWifiManager mService;
	public WifiInfo getConnectionInfo() {
    
    
	       try {
    
    
	           return mService.getConnectionInfo(mContext.getOpPackageName());
	       } catch (RemoteException e) {
    
    
	           throw e.rethrowFromSystemServer();
	       }
	   }

可以看到,这里调用的是IWifiManager.getConnectionInfo(),IWifiManager是一个接口

	interface IWifiManager{
    
    
		WifiInfo getConnectionInfo(String callingPackage);
		...
	}

那么这个IWifiManager是什么时候被赋值的呢?我们回到context.getSystemService(Context.WIFI_SERVICE);
我们知道,context的实现类其实是ContextImpl.java,我们直接来看ContextImpl.getSystemService()

	@Override
    public Object getSystemService(String name) {
    
    
        return SystemServiceRegistry.getSystemService(this, name);
    }

我们可以看到,这里调用了SystemServiceRegistry.getSystemService(this, name);
我们再来看这个类

	public static Object getSystemService(ContextImpl ctx, String name) {
    
    
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

这里的SYSTEM_SERVICE_FETCHERS是一个Map<String, ServiceFetcher<?>>,从这我们可以知道,通过SYSTEM_SERVICE_FETCHERS获取到一个ServiceFetcher,然后再调用ServiceFetcher.getSErvice(ctx),从而得到Service。
那么SYSTEM_SERVICE_FETCHERS是从哪里开始赋值的呢?
我们可以在SystemServiceRegistry找到一端静态代码块

	static{
    
    
		...
		registerService(Context.WIFI_SERVICE, WifiManager.class,
                new CachedServiceFetcher<WifiManager>() {
    
    
            @Override
            public WifiManager createService(ContextImpl ctx) throws ServiceNotFoundException {
    
    
                IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_SERVICE);
                IWifiManager service = IWifiManager.Stub.asInterface(b);
                return new WifiManager(ctx.getOuterContext(), service,
                        ConnectivityThread.getInstanceLooper());
            }});
        ...
	}

其中,就有注册我们需要的WIFI_SERVICE,并实现了createService方法,方法内使用了AIDL,进行了跨进程的访问。
我们来看下CachedServiceFetcher,这是个抽象类

	static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
    
    
        private final int mCacheIndex;

        CachedServiceFetcher() {
    
    
            // Note this class must be instantiated only by the static initializer of the
            // outer class (SystemServiceRegistry), which already does the synchronization,
            // so bare access to sServiceCacheSize is okay here.
            mCacheIndex = sServiceCacheSize++;
        }

        @Override
        @SuppressWarnings("unchecked")
        public final T getService(ContextImpl ctx) {
    
    
            final Object[] cache = ctx.mServiceCache;
            final int[] gates = ctx.mServiceInitializationStateArray;

            for (;;) {
    
    
                boolean doInitialize = false;
                synchronized (cache) {
    
    
                    // Return it if we already have a cached instance.
                    T service = (T) cache[mCacheIndex];
                    if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
    
    
                    	//如果缓存不为null,直接返回service
                        return service;
                    }

                    // If we get here, there's no cached instance.

                    // Grr... if gate is STATE_READY, then this means we initialized the service
                    // once but someone cleared it.
                    // We start over from STATE_UNINITIALIZED.
                    if (gates[mCacheIndex] == ContextImpl.STATE_READY) {
    
    
                        gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED;
                    }

                    // It's possible for multiple threads to get here at the same time, so
                    // use the "gate" to make sure only the first thread will call createService().

                    // At this point, the gate must be either UNINITIALIZED or INITIALIZING.
                    if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) {
    
    
                        doInitialize = true;
                        gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING;
                    }
                }

                if (doInitialize) {
    
    
                    // Only the first thread gets here.

                    T service = null;
                    @ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND;
                    try {
    
    
                        // This thread is the first one to get here. Instantiate the service
                        // *without* the cache lock held.
                        //第一次调用,回去调用createService,也就是registerService时候去实现的那个方法
                        service = createService(ctx);
                        newState = ContextImpl.STATE_READY;

                    } catch (ServiceNotFoundException e) {
    
    
                        onServiceNotFound(e);

                    } finally {
    
    
                        synchronized (cache) {
    
    
                            cache[mCacheIndex] = service;
                            gates[mCacheIndex] = newState;
                            cache.notifyAll();
                        }
                    }
                    return service;
                }
                // The other threads will wait for the first thread to call notifyAll(),
                // and go back to the top and retry.
                synchronized (cache) {
    
    
                    while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
    
    
                        try {
    
    
                            cache.wait();
                        } catch (InterruptedException e) {
    
    
                            Log.w(TAG, "getService() interrupted");
                            Thread.currentThread().interrupt();
                            return null;
                        }
                    }
                }
            }
        }

        public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
    }

我们可以看下ContextImpl中的mServiceCache

    final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();

这个mServiceCache的也是从SystemServiceRegistry中获取的,而且只返回了一个简单的数组。

    public static Object[] createServiceCache() {
    
    
        return new Object[sServiceCacheSize];
    }

可以看到,getService的时候,优先会去拿缓存,而缓存的规则是根据ContextImpl来的,由于ApplicationActivityServiceContextImpl不是同一个,所以获取到的WifiManager不是同一个。
看到这里,我们就知道WifiManager的创建过程了。所以我们分别对ApplicationActivityService获取的WifiManager,分别替换其IWifiManager,就可以达到Hook的目的了。

附上代码

public class HookUtils {
    
    
    private static WifiInfo cacheWifiInfo = null;

    public static void hookMacAddress(String tag, Context context) {
    
    
        try {
    
    
            Class<?> iWifiManager = Class.forName("android.net.wifi.IWifiManager");
            Field serviceField = WifiManager.class.getDeclaredField("mService");
            serviceField.setAccessible(true);

            WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
            // real mService
            Object realIwm = serviceField.get(wifi);
            // replace mService  with Proxy.newProxyInstance
            serviceField.set(wifi, Proxy.newProxyInstance(iWifiManager.getClassLoader(),
                    new Class[]{
    
    iWifiManager},
                    new InvocationHandler(tag, "getConnectionInfo", realIwm)));
            Log.i(tag, "wifiManager hook success");
        } catch (Exception e) {
    
    
            Log.e(tag, "printStackTrace:" + e.getMessage());
            e.printStackTrace();
        }
    }

    public static class InvocationHandler implements java.lang.reflect.InvocationHandler {
    
    

        private final String tag;
        private final String methodName;
        private Object real;

        public InvocationHandler(String tag, String methodName, Object real) {
    
    
            this.real = real;
            this.methodName = methodName;
            this.tag = tag;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
            Log.d(tag, "method invoke " + method.getName());
            if (methodName.equals(method.getName())) {
    
    
                if (cacheWifiInfo != null) {
    
    
                    Log.d(tag, "cacheWifiInfo:" + cacheWifiInfo);
                    return cacheWifiInfo;
                }
                WifiInfo wifiInfo = null;
                try {
    
    
                    Class cls = WifiInfo.class;
                    wifiInfo = (WifiInfo) cls.newInstance();
                    Field mMacAddressField = cls.getDeclaredField("mMacAddress");
                    mMacAddressField.setAccessible(true);
                    mMacAddressField.set(wifiInfo, "");
                    cacheWifiInfo = wifiInfo;
                    Log.d(tag, "wifiInfo:" + wifiInfo);
                } catch (Exception e) {
    
    
                    Log.e(tag, "WifiInfo error:" + e.getMessage());
                }
                return wifiInfo;
            } else {
    
    
                return method.invoke(real, args);
            }
        }
    }
}

然后进行Hook

     HookUtils.hookMacAddress("Z-Application",getApplicationContext());
     HookUtils.hookMacAddress("Z-Activity",MainActivity.this);
     HookUtils.hookMacAddress("Z-Service",MyService.this);

最后,运行程序,可以看到,当获取Mac地址的时候,会打印相关日志。

method invoke getConnectionInfo

至此,我们就Hook成功了。
然后,我们就可以在App出现这个日志的时候,定位到是哪个功能调用了Mac地址。
我们这最终定位到是点击了隐私协议,通过腾讯X5 WebView显示H5页面的时候,调用了Mac地址。
最终,通过Hook MAC地址方法,当获取MAC地址的时候,进行全局的拦截,返回一个空的MAC地址,使其无法获取到真正的MAC地址。

还有另一个违规问题看这边 Android 违规获取用户隐私(获取软件安装列表信息)整改

参考 Hook之WifiManager

猜你喜欢

转载自blog.csdn.net/EthanCo/article/details/111544333