Binder数据传输是有大小限制的。
oneway方式。手写MMAP初始化Binder服务(4M/2),ProcessState初始化BInder服务(1M-8K)/2。
非oneway方式。手写MMAP初始化Binder服务(4M),ProcessState初始化BInder服务(1M-8K)。
而且BInder的线程池默认是15个,15个线程共享这1MB-8KB的内存空间,所以实际传输大小会更加小。当数据传输达到限制的时候,就会抛出TransactionTooLargeException异常。
为了更加好的预防排查定位问题,我们可以hook transact方法监控整个APP的IPC流量。
这里hook的是系统的Service,我们自己生成的Service也可以用这种思想实现。不过注意的是。API28开始asInterface方法,被定义为黑名单接口,所以这个方案只能在TargetSdk28以下使用,omg。
private fun hookService(context: Context, serviceName: String, interfaceName: String): Any {
val serviceManager = Class.forName("android.os.ServiceManager")
val getServiceMethod = serviceManager.getDeclaredMethod("getService", String::class.java)
getServiceMethod.isAccessible = true
val serviceBinder = getServiceMethod.invoke(null, serviceName)
val interfaceStubClass = Class.forName("$interfaceName\$Stub")
val asInterfaceMethod = interfaceStubClass.getDeclaredMethod("asInterface", IBinder::class.java)
val serviceBinderImplProxy = Proxy.newProxyInstance(context.classLoader,
arrayOf(IBinder::class.java)) { _, method, args ->
Log.e("demoKillerTag", "method=$method")
Log.e("demoKillerTag", Thread.currentThread().stackTrace.contentDeepToString())
if (method.name == "transact") {
Log.e("demoKillerTag", "transact data.size=" + (args[1] as Parcel).dataSize())
}
method.invoke(serviceBinder, *(args ?: emptyArray()))
}
val sCache = serviceManager.getDeclaredField("sCache")
sCache.isAccessible = true
val cacheMap = sCache.get(null) as MutableMap
cacheMap[serviceName] = serviceBinderImplProxy as IBinder
return asInterfaceMethod.invoke(null, serviceBinderImplProxy)
}
由于Android做了Binder缓存,需要替换。然后APP内调用PackageManager的方法都会得到监控。
fun hookPackageManager(context: Context) {
val packageManager = context.packageManager
val field = packageManager::class.java.getDeclaredField("mPM")
field.isAccessible = true
field.set(packageManager,
hookService(context, "package", "android.content.pm.IPackageManager"))
}