1. 简述
Binder Hook 可以 Hook 掉当前App用到的系统 Service 服务。
以 LocationManager 为例,在获取一个 LocationManager 时分为两步。第一:获取 IBinder 对象;第二:通过 IBinder 的 asInterface()方法转化为 LocationMangerService 对象。最后初始化 LocationManager,application 层用到的都是LocationManager 对象。
Hook的大致原理是:ServiceManager 在获取某个 Binder 时,如果本地有缓存的 Binder,就不再跨进程请求 Binder 了。我们可以在缓存中注入自己的 Binder 对象,使得 ServiceManager 在查询本地缓存时得到一个自定义的 CustomBinder 对象,不再跨进程向系统请求。并且 ILocationManager.Stub.asInterface(CustomBinder) 方法返回我们自定义的Service对象。
这里面有两个地方需要用到自定义的对象。由于我们只 Hook 其中一部分的功能,其他功能还需要保留,所以用动态代理的方式创建自定义的 Binder 和自定义的 Service。
在理解后面的内容前你需要了解这些知识点:
- 一点点Binder的知识,知道 IBinder 转 IInterface 的大致流程;
- Java的动态代理。
2. Context 获取系统 Service 的流程
Activity 等类在获取系统 Service 时,都是调用 getSystemService(serviceName) 方法获取的。
Context 的 getSystemService() 方法调用了 SystemServiceRegistry 的 getSystemService() 方法。
SystemServiceRegistry 中有一个常量 SYSTEM_SERVICE_FETCHERS,这是一个Map。保存了ServiceName 和对应的 ServiceFetcher。ServiceFetcher 是用于创建具体 Service 对象的类。ServiceFetcher 的关键方法是 createService() 方法。
在 ServiceFetcher 的 createService() 方法中,调用了 ServiceManager.getService(name)
方法。以 LocationManager 对应的 ServiceFetcher 为例,它的 createService() 方法源码如下:
// Android 8.0 android.app.SystemServiceRegistry.java
@Override
public LocationManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(Context.LOCATION_SERVICE);
return new LocationManager(ctx, ILocationManager.Stub.asInterface(b));
}
假如我们要 Hook 掉 LocationManager 的 getLastKnownLocation() 方法(下文都是)。我们要做的就是让ServiceManager.getService(Context.LOCATION_SERVICE)
返回我们自定义的 Binder 对象。
先看一下这个方法的源码:
// Android 8.0 android.os.ServiceManager.java
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return Binder.allowBlocking(getIServiceManager().getService(name));
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
sCache 是一个 Map,缓存了已经向系统请求过的 Binder。如果需要让这个方法返回我们自己的 binder 对象,只需要事先往 sCache 中 put 一个自定义的 Binder 对象就行了。
在 put 之前,需要先创建出一个自定义的 Binder。这个 Binder 在被 ILocationManager.Stub.asInterface 处理后,可以返回一个自定义的 LocationManagerService 对象。
先看一下 Binder 的 asInterface() 的实现:
// Android 8.0 android.location.ILocationManager.java
public static android.location.ILocationManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof android.location.ILocationManager))) {
return ((android.location.ILocationManager) iin);
}
return new android.location.ILocationManager.Stub.Proxy(obj);
}
如果把 queryLocalInterface()方法返回一个自定义的Service,使得走 if 语句内部,不走 else,那就算是Hook 成功了。
误区:
asInterface(binder) 就是把 binder 做了一次类型转换
实际上XXX service = XXX.Stub.asInterface(binder)
的返回值根据 binder 的来源有两种情况:
- 跨进程时,service 的类型是 XXX.Stub.Proxy
- 相同进程时,service 的类型是 XXX.Stub
XXX.Stub.asInterface(binder);得到的返回值并不一定是binder自己,并且调用系统服务时肯定不是binder自己。
3 创建自定义的 Service 和 Binder 对象
3.1 自定义的 Service 对象
假设我们想让系统的 LocationManager 返回的位置信息全是在天安门(116.23, 39.54)。那我们需要使得 LocatitionManagerService 的 getLastLocation() 方法 返回的全是 (116.23, 39.54)。
由于我们不能直接拿到系统的这个Service对象,可以先用反射的方式拿到系统的LocationManagerService。然后拦截 getLastLocation() 方法。
import android.location.Location;
import android.location.LocationManager;
import android.os.IBinder;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 动态代理时用到的 Handler
* @author ZHP
* @since 16/12/25 17:36
*/
public class ServiceHookHandler implements InvocationHandler {
private Object mOriginService;
/**
* @param binder 系统原始的Binder对象
*/
@SuppressWarnings("unchecked")
public ServiceHookHandler(IBinder binder) {
try {
// 由于可以拿到 Binder 对象,但无法拿到 Service 的对象, 所以我们要手动获取
// 即: this.mOriginService = ILocationManager.Stub.asInterface(binder);
Class ILocationManager$Stub = Class.forName("android.location.ILocationManager$Stub");
Method asInterface = ILocationManager$Stub.getDeclaredMethod("asInterface", IBinder.class);
this.mOriginService = asInterface.invoke(null, binder);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch(method.getName()) {
case "getLastLocation":
// 一直返回天安门的坐标
Location location = new Location(LocationManager.GPS_PROVIDER);
location.setLongitude(116.23);
location.setLatitude(39.54);
return location;
default:
return method.invoke(this.mOriginService, args);
}
}
}
3.2 自定义的 Binder 对象
原生的Binder对象在调用 queryLocalInterface() 方法时会返回原生的Service对象。我们希望返回3.1中的自定义Service。所以这里拦截 queryLocalInterface() 方法。
import android.os.IBinder;
import android.os.IInterface;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 动态代理时用到的Handler,用于创建自定义Binder
* @author ZHP
* @since 16/12/25 17:36
*/
public class BinderHookHandler implements InvocationHandler {
private IBinder mOriginBinder;
private Class ILocationManager;
@SuppressWarnings("unchecked")
public BinderHookHandler(IBinder binder) {
this.mOriginBinder = binder;
try {
this.ILocationManager = Class.forName("android.location.ILocationManager");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
switch (method.getName()) {
// 使得返回自定义的Service
case "queryLocalInterface":
ClassLoader classLoader = mOriginBinder.getClass().getClassLoader();
Class[] interfaces = new Class[] {IInterface.class, IBinder.class, ILocationManager};
ServiceHookHandler handler = new ServiceHookHandler(this.mOriginBinder);
return Proxy.newProxyInstance(classLoader, interfaces, handler);
default:
return method.invoke(mOriginBinder, args);
}
}
}
4. 将自定义的 Binder 注入到 ServiceManager 中
有了自定义的 Binder 后,将它注入到 ServiceManger 的 sCache 变量中就完成 Hook 了~
import android.content.Context;
import android.os.IBinder;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
/**
* @author ZHP
* @since 16/12/25 17:56
*/
public class HookManager {
@SuppressWarnings("unchecked")
public static boolean hookLocationManager() {
try {
// 1. 获取系统自己的Binder
Class ServiceManager = Class.forName("android.os.ServiceManager");
Method getService = ServiceManager.getDeclaredMethod("getService", String.class);
IBinder binder = (IBinder) getService.invoke(null, Context.LOCATION_SERVICE);
// 2. 创建我们自己的Binder,动态代理了 queryLocalInterface 方法。
ClassLoader classLoader = binder.getClass().getClassLoader();
Class[] interfaces = {IBinder.class};
BinderHookHandler handler = new BinderHookHandler(binder);
IBinder customBinder = (IBinder) Proxy.newProxyInstance(classLoader, interfaces, handler);
// 3. 获取 ServiceManager 中的 sCache
Field sCache = ServiceManager.getDeclaredField("sCache");
sCache.setAccessible(true);
Map cache = (Map) sCache.get(null);
// 4. 将自定义的 Binder 对象替换掉旧的系统 Binder
cache.put(Context.LOCATION_SERVICE, customBinder);
sCache.setAccessible(false);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
5. 测试
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Hook
HookManager.hookLocationManager();
}
/**
* 请求定位信息
*/
private void requestLocation() {
// 定位权限检测
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.i("郑海鹏", "没有定位权限");
Toast.makeText(this, "没有定位权限", Toast.LENGTH_SHORT).show();
return;
}
// 获取位置并显示
LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
String message = "(" + location.getLongitude() + ", " + location.getLatitude() + ")";
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
Log.i("郑海鹏", message);
}
public void onClick(View view) {
this.requestLocation();
}
}
当onClick被调用的时候,Toast和Log都会显示天安门的坐标(116.23, 39.54)。证明Hook成功!
你甚至可以用Binder Hook的方式Hook掉 ActivityManager。
团队博客同名文章:http://www.jianshu.com/p/fcb832a2b411
转载必须注明出处:http://www.jianshu.com/p/5c2c3fc4286b