基于底层动态拦截技术,实现对Android平台下应用进程Binder通信协议的动态分析和拦截。
说明
Binder
作为Android
系统跨进程通信的核心机制。网上也有很多深度讲解该机制的文章,如:
这些文章和系统源码可以很好帮助我们理解Binder的实现原理和设计理念,为拦截做准备。借助Binder拦截可以我们可以扩展出那些能力呢:
- 虚拟化的能力,多年前就出现的应用免安装运行类产品如:
VirtualApp
/DroidPlugin
/平行空间/双开大师/应用分身等。 - 测试验证的能力,通常为
Framework
层功能开发。 - 检测第三方
SDK
或模块系统服务调用访问情况(特别是敏感API
调用)。 - 逆向分析应用底层服务接口调用实现。
- 第三方
ROM
扩展Framework
服务。
现有方案
一直以来实时分析和拦截进程的Binder
通信是通过Java
层的AIDL
接口代理来实现的。借助于Android
系统Binder
服务接口设计的规范,上层的接口均继承 于IBinder
。
如一下为代理目标对象的所有的接口API
的方法:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
private static void getInterface(Class<?> cls, final HashSet<Class<?>> ss) {
Class<?>[] ii;
do {
ii = cls.getInterfaces();
for (final Class<?> i : ii) {
if (ss.add(i)) {
getInterface(i, ss);
}
}
cls = cls.getSuperclass();
} while (cls != null);
}
private static Class<?>[] getInterfaces(Class<?> cls) {
final HashSet<Class<?>> ss = new LinkedHashSet<Class<?>>();
getInterface(cls, ss);
if (0 < ss.size()) {
return ss.toArray(new Class<?>[ss.size()]);
}
return null;
}
public static Object createProxy(Object org, InvocationHandler cb) {
try {
Class<?> cls = org.getClass();
Class<?>[] cc = getInterfaces(cls);
return Proxy.newProxyInstance(cls.getClassLoader(), cc, cb);
} catch (Throwable e) {
Logger.e(e);
} finally {
// TODO release fix proxy name
}
return null;
}
1、对于已经生成的Binder
服务对象,在应用进程可参与实现逻辑之前就已经缓存了,我们需要找到并且进行替换(AMS、PMS、WMS等
),如AMS
在Android 8.0之后的缓存如下:
// source code: http://aospxref.com/android-9.0.0_r61/xref/frameworks/base/core/java/android/app/ActivityManager.java
package android.app;
public class ActivityManager {
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
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;
}
};
}
因此我们需要找到并且替换它,如:
Object obj;
if (Build.VERSION.SDK_INT < 26) {// <= 7.0
obj = ReflectUtils.getStaticFieldValue("android.app.ActivityManagerNative", "gDefault");
} else {// 8.0 <=
obj = ReflectUtils.getStaticFieldValue("android.app.ActivityManager", "IActivityManagerSingleton");
}
Object inst = ReflectUtils.getFieldValue(obj, "mInstance");
ReflectUtils.setFieldValue(obj, "mInstance", createProxy(inst));
2、对于后续运行过程中才获取的Binder
服务,则需要代理ServiceManager
,源码如下:
// source code: http://aospxref.com/android-9.0.0_r61/xref/frameworks/base/core/java/android/os/ServiceManager.java
package android.os;
public final class ServiceManager {
private static final String TAG = "ServiceManager";
private static IServiceManager sServiceManager;
}
因此我们的代理如下:
Class<?> cls = ReflectUtils.findClass("android.os.ServiceManager");
Object org = ReflectUtils.getStaticFieldValue(cls, "sServiceManager");
Object pxy = new createProxy(org);
if (null != pxy) {
ReflectUtils.setStaticFieldValue(getGlobalClass(), "sServiceManager", pxy);
}
这样每次在第一次访问该服务时,就会调用IServiceManager
中的getService
的方法,而该方法已经被我们代理拦截,我们可以通过参数可以识别当前获取的是哪个服务,然后将获取的服务对象代理后在继续返回即可。
但是:
这样的方案并不能拦截进程中所有的Binder
服务。我们面临几大问题:
-
首先,Android源码越来越庞大,了解所有的服务工作量很大,因此有哪些服务已经被缓存排查非常困难。
-
其次,厂商越来越钟情于扩展自定义服务,这些服务不开源,识别和适配更加耗时。
-
再次,有一部分服务只有
native
实现,并不能通过Java
层的接口代理进行拦截(如:Sensor/Audio/Video/Camera服务等
)。// source code: http://aospxref.com/android-13.0.0_r3/xref/frameworks/av/camera/ICamera.cpp
class BpCamera: public BpInterface<ICamera>
{
public:
explicit BpCamera(const sp<IBinder>& impl)
: BpInterface<ICamera>(impl)
{
}
// start recording mode, must call setPreviewTarget first
status_t startRecording()
{
ALOGV("startRecording");
Parcel data, reply;
data.writeInterfaceToken(ICamera::getInterfaceDescriptor());
remote()->transact(START_RECORDING, data, &reply);
return reply.readInt32();
}
}
新方案:基于底层拦截
原理
我们都知道Binder
在应用进程运行原理如下图:
不管是Java
层还是native
层的接口调用,最后都会通过ioctl
函数访问共享内存空间,达到跨进程访问数据交换的目的。因此我们只要拦截ioctl
函数,即可完成对所有Binder
通信数据的拦截。底层拦截有以下优势:
1)可以拦截所有的Binder通信。
2)底层拦截稳定,高兼容性。从Android 4.x
至Android 14
,近10年的系统版本演进,涉及到Binder
底层通信适配仅两次;一次是支持64位进程(当时需要同时兼容32位和64位进程访问Binder
服务)。另一次是华为鸿蒙系统的诞生,华为ROM
在Binder
通信协议中增加了新的标识字段。