Hook之WifiManager

安卓WifiManager是系统服务之一,应用程序是通过上下文对象Context进行跨进程通信来获取的。 这里我们来看一下,如何修改应用程序内的WifiManager的功能,也就是替换WifiManager

切入点

在开发过程中,一般是通过上下文对象Context来获取WifiManager,代码如下:

WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

我们顺藤摸瓜,来研究下WifiManager这个类,看看它针对wifi模块的功能到底是由谁实现的。我们选择一个方法getWifiState来看一下源码:

   /**
     * Gets the Wi-Fi enabled state.
     * @return One of {@link #WIFI_STATE_DISABLED},
     *         {@link #WIFI_STATE_DISABLING}, {@link #WIFI_STATE_ENABLED},
     *         {@link #WIFI_STATE_ENABLING}, {@link #WIFI_STATE_UNKNOWN}
     * @see #isWifiEnabled()
     */
    public int getWifiState() {
        try {
            return mService.getWifiEnabledState();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

可以看到最终是调用的mService.getWifiEnabledState()方法,也就是说具体实现是由mService来完成的。

    @UnsupportedAppUsage
    IWifiManager mService;

是一个系统层面的IWifiManager类型变量。这里可能是一个突破口,因为在安卓世界中,类名以I开头的类大概率是一个接口,只要IWifiManager是一个接口,我们就可以使用动态代理来替换掉这个成员变量。看下源码:

frameworks/base/wifi/java/android/net/wifi/IWifiManager.aidl,aidl文件,编译完后也就是一个接口文件。看下文件内容:

/**
 * Interface that allows controlling and querying Wi-Fi connectivity.
 *
 * {@hide}
 */
interface IWifiManager
{

    ......

    boolean setWifiEnabled(String packageName, boolean enable);

    int getWifiEnabledState();

    ......
 
   }

可以看到,是定义了一些接口方法,其中就有getWifiEnabledState,到此,我们已经可以确定IWifiManager就是一个很好的hook切入点。剩下的,我们需要确认的是:WifiManager是何时被系统初始化的?是否是单例的?是否跟系统组件绑定的?

这时候,我们需要研究下ContextImpl类,为什么呢?因为Context最终实现基本都在ContextImpl中。

我们看下ContextImplgetSystemService方法:

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

最终调用的是SystemServiceRegistry的一个静态方法getSystemService

    /**
     * Gets a system service from a given context.
     */
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

 private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();

SYSTEM_SERVICE_FETCHERS是一个静态的HashMap,然后我们会发现,在SystemServiceRegistry的静态代码块中,会向SYSTEM_SERVICE_FETCHERS中添加各种服务对象,看如下代码:

    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());
            }});

        ......

    }

在这里,我们可以看到熟悉的跨进程通信重要角色IBinder了,CachedServiceFetcher通过createService创建了WifiManager对象,其中IWifiManager是构造器的一个参数,从WifiManager源码中也可以看出。

/**
     * Create a new WifiManager instance.
     * Applications will almost always want to use
     * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
     * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}.
     * @param context the application context
     * @param service the Binder interface
     * @hide - hide this because it takes in a parameter of type IWifiManager, which
     * is a system private class.
     */
    public WifiManager(Context context, IWifiManager service, Looper looper) {
        mContext = context;
        mService = service;
        mLooper = looper;
        mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
        updateVerboseLoggingEnabledFromService();
    }

其实到这里,一切都已经变得很明朗了,我们再看下CachedServiceFetchergetService方法。

public final T getService(ContextImpl ctx) {
            final Object[] cache = ctx.mServiceCache;

            .....

            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) {
                        return service;
                    }
                }

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

                    T service = null;
                    try {
                        // This thread is the first one to get here. Instantiate the service
                        // *without* the cache lock held.
                        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;
                }
                    ......
                }
            }
        }

这部分源码的注释很清晰,就是先从Context的缓存中获取服务对象,如果没有,则调用createService方法,来创建服务对象,并存储到Context的缓存中,我们回头看下ContextImpl类,

// The system service cache for the system services that are cached per-ContextImpl.
    final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();

是在对象初始化时从SystemServiceRegistry中获取的,并且只是简单的返回了一个指定大小的对象数组。

    /**
     * Creates an array which is used to cache per-Context service instances.
     */
    public static Object[] createServiceCache() {
        return new Object[sServiceCacheSize];
    }

也就是说在首次获取该系统服务时会进行缓存,并且缓存是针对每个ContextImpl来说的,也就是说在ApplicationActivity中获取的WifiManager并不是同一个对象,但是同一个Application或者Activity获取的WifiManager是同一个对象。因此,只要我们在Hook之前,先获取到这个系统服务对象即可。

实施

经过上面的分析,我们的目标已经很明确了,就是通过动态代理替换掉WifiManagermService成员变量。

第一步:我们先进行准备工作,利用反射获取相关类的Class对象以及成员变量的Field对象。

    try {
      iWifiManager = Class.forName("android.net.wifi.IWifiManager");
      serviceField = WifiManager.class.getDeclaredField("mService");
      serviceField.setAccessible(true);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (NoSuchFieldException e) {
      e.printStackTrace();
    }
   

第二步:获取系统真实的WifiManager对象,并替换mService成员。

     try {
      // real wifi
      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 IWMInvocationHandler(realIwm)));
      Log.e("wh", "wifiManager hook success");
    } catch (Exception e) {
      e.printStackTrace();
    }

第三步:监听调用,通过InvocationHandler来实现,还不能影响正常服务的使用。

private static class IWMInvocationHandler implements InvocationHandler {

    private Object real;

    public IWMInvocationHandler(Object real) {
      this.real = real;
    }

    @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Log.e("wh", "method invoke " + method.getName());
      // TODO: 2019-12-23 real logic
      return method.invoke(real, args);
    }
  }

到此,hook已经结束了,我们可以在ActivityonCreate方法中进行hook操作,此时ContextImpl对象已经被初始化了。

关于ContextImpl

我们知道ActivtiyApplication都是Context的子类,而Context的真正实现是在frameWork层的ContextImpl类。安卓应用程序启动的入口是ActivtiyThread类的main方法,需要说明的是:ActivtiyThread并不是一个Thread类型对象。在ActivtiyThread类的main方法中,会创建并启动主线程Looper、创建ActivtiyContextImpl,将``ActivtiyContextImplApplication进行绑定,然后执行Activtiy```的生命周期方法。

也就是说每一个ActivtiyApplication、还有 Service都会持有一个单独的ContextImpl实例,这种引用是通过ContextWrapper的成员变量mBase来支持的,这个mBase就是一个ContextImpl对象。

然后我们来测试一下前面提到的一个结论:
ApplicationActivity获取到的WifiManager不是同一个对象。

Actvity

    WifiManager wifi = (WifiManager) MainActivity.this.getSystemService(Context.WIFI_SERVICE);
    Log.e("wh", "MainActivity 中 WifiManager hash " + wifi.hashCode());

Application

    WifiManager wifi = (WifiManager) this.getSystemService(Context.WIFI_SERVICE);
    Log.e("wh", "Application 中 WifiManager hash" + wifi.hashCode());

输出如下,属于不同的两个对象

 	E/wh: Application 中 WifiManager hash 72191661
 	E/wh: MainActivity 中 WifiManager hash 118326778

接下来我们在MainActivity中通过ApplicationContext来获取下服务,证明是否存在缓存

Actvity如下,Application代码不变

    WifiManager wifi = (WifiManager) MainActivity.this.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
    Log.e("wh", "MainActivity 中通过 getApplicationContext() 获取 WifiManager hash" + wifi.hashCode());

输出如下,属于相同的一个对象

	E/wh: Application 中 WifiManager hash223127010
	E/wh: MainActivity 中通过 getApplicationContext() 获取 WifiManager hash223127010

可以看出,验证结果是符合预期的。在这个过程中,不知道有没有发现一个问题?不管是从Application还是Activity获取的WifiManager都具备相同的功能,而且对应的服务还会被ContextImpl缓存。ApplicationActivity的一个不同点就是生命周期不同。我们在获取系统服务时,如果直接通过Application来获取,获取到的对象是不会出错的,而且会有很多好处:

  1. 仅仅触发一次跨进程调用
  2. 不用担心内存泄漏问题
  3. 获取到的服务对象每次都是同一个

基于上面几点,我们在获取系统服务时,尽量使用ApplicationContext

总结

以上对WifiManager的Hook操作想说明的是一种思想,通过这种思想和方式,基本上可以Hook系统的大部分服务。在这个过程中,可能会遇到隐藏API的问题,怎么去解决隐藏API的问题,思想也很简单,就是通过系统API去反射调用隐藏API。

github地址

发布了36 篇原创文章 · 获赞 9 · 访问量 4861

猜你喜欢

转载自blog.csdn.net/lotty_wh/article/details/104842144