安卓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
中。
我们看下ContextImpl
的getSystemService
方法:
@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();
}
其实到这里,一切都已经变得很明朗了,我们再看下CachedServiceFetcher
的getService
方法。
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
来说的,也就是说在Application
和Activity
中获取的WifiManager
并不是同一个对象,但是同一个Application
或者Activity
获取的WifiManager
是同一个对象。因此,只要我们在Hook之前,先获取到这个系统服务对象即可。
实施
经过上面的分析,我们的目标已经很明确了,就是通过动态代理替换掉WifiManager
的mService
成员变量。
第一步:我们先进行准备工作,利用反射获取相关类的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已经结束了,我们可以在Activity
的onCreate
方法中进行hook操作,此时ContextImpl
对象已经被初始化了。
关于ContextImpl
我们知道Activtiy
和Application
都是Context
的子类,而Context
的真正实现是在frameWork
层的ContextImpl
类。安卓应用程序启动的入口是ActivtiyThread
类的main
方法,需要说明的是:ActivtiyThread
并不是一个Thread
类型对象。在ActivtiyThread
类的main
方法中,会创建并启动主线程Looper
、创建Activtiy
、ContextImpl
,将``Activtiy、
ContextImpl、
Application进行绑定,然后执行
Activtiy```的生命周期方法。
也就是说每一个Activtiy
、Application
、还有 Service
都会持有一个单独的ContextImpl
实例,这种引用是通过ContextWrapper
的成员变量mBase
来支持的,这个mBase
就是一个ContextImpl
对象。
然后我们来测试一下前面提到的一个结论:
Application
和Activity
获取到的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
缓存。Application
跟Activity
的一个不同点就是生命周期不同。我们在获取系统服务时,如果直接通过Application
来获取,获取到的对象是不会出错的,而且会有很多好处:
- 仅仅触发一次跨进程调用
- 不用担心内存泄漏问题
- 获取到的服务对象每次都是同一个
基于上面几点,我们在获取系统服务时,尽量使用Application
的Context
。
总结
以上对WifiManager
的Hook操作想说明的是一种思想,通过这种思想和方式,基本上可以Hook系统的大部分服务。在这个过程中,可能会遇到隐藏API的问题,怎么去解决隐藏API的问题,思想也很简单,就是通过系统API去反射调用隐藏API。