插件化原理解析Binder Hook

BinderAndroid系统的通讯桥梁。因此Hook Binder就能够改变通讯行为.分析Hook过程有利于理解Binder机制

系统服务获取

1
ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)

通过以上代码能够获取系统服务。我们来看看内部是如何获取的

1
2
3
4
5
6
7
8
9
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}

@Override
public String getSystemServiceName(Class<?> serviceClass) {
return SystemServiceRegistry.getSystemServiceName(serviceClass);
}

ContextImpl中通过SystemServiceRegistry获取服务

1
2
3
4
5
6
7
8
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}

public static String getSystemServiceName(Class<?> serviceClass) {
return SYSTEM_SERVICE_NAMES.get(serviceClass);
}

通过Class或者类限定名都可以获取对应的服务

1
2
3
4
private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
new HashMap<Class<?>, String>();
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();

这里有两个HashMap,一个用于缓存服务名称,一个用于缓存服务获取器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static {
registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
new CachedServiceFetcher<ActivityManager>() {
@Override
public ActivityManager createService(ContextImpl ctx) {
return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
}});
}

private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

服务在首次使用的时候已经被初始化了。并且初始化时候会被缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
private final int mCacheIndex;

public CachedServiceFetcher() {
mCacheIndex = sServiceCacheSize++;
}

@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
synchronized (cache) {
// Fetch or create the service.
Object service = cache[mCacheIndex];
if (service == null) {
try {
service = createService(ctx);
cache[mCacheIndex] = service;
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
}
}
return (T)service;
}
}

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

每次CacheServiceFetcher创建时,全局的服务缓存空间会增大,并将新的服务放到数组末端。每次需要获取服务的时候,先寻找缓存中的服务,如果不存在创建并缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};

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

ActivityManager中可以看到ActivityManagerService服务的获取方式,从ServiceManager中获取。在ServiceManager中,会先从缓冲中获取。

Hook IBinder

从上述流程中可以了解系统服务的获取过程,那么我们需要怎么去Hook这些服务呢?我们可以将asInterface返回的结果修改成我们Hook过的对象。下面我们以ClipboardService为例来实践Hook过程

1
2
3
4
5
public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException {
mContext = context;
mService = IClipboard.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));
}

ClipboardManager在初始化的时候,从ServiceManager获取ClipboardService。从这里可以看出asInterface需要一个IBinder参数,这个参数从ServiceManager获取。而在ServiceManager中会首先从缓存中查找。因此我们可以提前往缓存冲插入我们Hook的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SuppressLint("PrivateApi")
class BinderHookHelperKt {
companion object {

@Throws(Exception::class)
fun hookClipboardService() {
val CLIPBOARD_SERVICE = "clipboard"
val serviceManager = Class.forName("android.os.ServiceManager")
val getService = serviceManager.getDeclaredMethod("getService", String::class.java)
val rawBinder = getService.invoke(null, CLIPBOARD_SERVICE) as IBinder
val hookedBinder = Proxy.newProxyInstance(serviceManager.classLoader, arrayOf(IBinder::class.java), BinderProxyHookHandlerKt(rawBinder))
val cacheField = serviceManager.getDeclaredField("sCache")
cacheField.isAccessible = true
val cache = cacheField.get(null) as HashMap<String, IBinder>
cache.put(CLIPBOARD_SERVICE, hookedBinder as IBinder)
}
}
}

获取ServiceManagergetService方法,返回一个IBinder对象。然后通过动态代理,代理此IBinder对象。然后将插入ServiceManager的缓存中。这样下次使用时就能够直接拿到这个代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class BinderProxyHookHandlerKt(val base: IBinder) : InvocationHandler {

companion object {
val TAG = BinderProxyHookHandlerKt::class.java.canonicalName
}

var stub = Any()
var iinterface = Any()

init {
try {
stub = Class.forName("android.content.IClipboard\$Stub")
iinterface = Class.forName("android.content.IClipboard")
} catch (e: Exception) {
e.printStackTrace()
}
}

override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any {
if ("queryLocalInterface" == method!!.name) {
Log.d(TAG, "hook queryLocalInterface")
val classArray = arrayOf(iinterface as Class<Any>)
return Proxy.newProxyInstance((proxy as Any).javaClass.classLoader, classArray, BinderHookHandlerKt(base, stub as Class<Any>))
}

Log.d(TAG, "method: " + method.name)
return method.invoke(base, args)
}
}

如果IBinderqueryLocalInterface触发,那么通过动态代理,代理IClipboard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class BinderHookHandlerKt(base: IBinder, stubClass: Class<Any>) : InvocationHandler {
companion object {
val TAG = BinderHookHandlerKt::class.java.canonicalName
}

var obj = Any()

init {
try {
val asInterfaceMethod: Method = stubClass.getDeclaredMethod("asInterface", IBinder::class.java)
obj = asInterfaceMethod.invoke(null, base)
} catch (e: Exception) {
throw RuntimeException("hooked failed")
}
}

override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any {
if ("getPrimaryClip" == method!!.name) {
Log.d(TAG, "hook getPrimaryClip")
return ClipData.newPlainText(null, "you have been hooked")
}

if ("hasPrimaryClip" == method.name) {
return true
}

return method.invoke(obj, args)
}
}

获取IClipboard$StubasInterface方法,返回BinderProxy。当getPrimaryClip方法触发时,返回固定值。当hasPrimaryClip触发时,总是返回true表示粘贴板有内容。这样就成功的hook ClipboardService

运行程序之后,使用粘贴功能,会发现永远只能粘贴我们自己返回的内容

整个过程的思路是:
ServiceManager内部一张表管理着很多的Binder对象。我们需要Hook某个Binder对象的queryLocalInterface,并将其缓存。由于ServiceManager的缓存表里的IBinder大部分都是BinderProxy对象。当使用时会调用asInterface转换成需要的接口。