深入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
通信协议中增加了新的标识字段。
要解决的问题
如何拦截
C/C++
层的函数拦截,并不像Java
层一样系统提供了较为稳定的代{过}{滤}理工具,在这里不是我们本期讨论的重点,可以直接采用网上开源的Hook
框架:
- https://github.com/bytedance/android-inline-hook
- https://github.com/bytedance/bhook
- https://github.com/asLody/whale
如何过滤
ioctl
函数为系统底层设备访问函数,调用及其频繁,而Binder
通信调用只是其中调用者之一,因此需要快速识别非Binder
通信调用,不影响程序性能。
函数定义:
复制代码 隐藏代码
#include <sys/ioctl.h>
int ioctl(int fildes, unsigned long request, ...);
request
的参数定义:
复制代码 隐藏代码
// source code: http://aospxref.com/android-14.0.0_r2/xref/bionic/libc/kernel/uapi/linux/android/binder.h
#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)
#define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, __s64)
#define BINDER_SET_MAX_THREADS _IOW('b', 5, __u32)
#define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, __s32)
#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, __s32)
#define BINDER_THREAD_EXIT _IOW('b', 8, __s32)
#define BINDER_VERSION _IOWR('b', 9, struct binder_version)
#define BINDER_GET_NODE_DEBUG_INFO _IOWR('b', 11, struct binder_node_debug_info)
#define BINDER_GET_NODE_INFO_FOR_REF _IOWR('b', 12, struct binder_node_info_for_ref)
#define BINDER_SET_CONTEXT_MGR_EXT _IOW('b', 13, struct flat_binder_object)
#define BINDER_FREEZE _IOW('b', 14, struct binder_freeze_info)
#define BINDER_GET_FROZEN_INFO _IOWR('b', 15, struct binder_frozen_status_info)
#define BINDER_ENABLE_ONEWAY_SPAM_DETECTION _IOW('b', 16, __u32)
#define BINDER_GET_EXTENDED_ERROR _IOWR('b', 17, struct binder_extended_error)
对应的源码:
复制代码 隐藏代码
// source code: http://aospxref.com/android-14.0.0_r2/xref/frameworks/native/libs/binder/IPCThreadState.cpp
void IPCThreadState::threadDestructor(void *st) {
ioctl(self->mProcess->mDriverFD, BINDER_THREAD_EXIT, 0);
}
status_t IPCThreadState::getProcessFreezeInfo(pid_t pid, uint32_t *sync_received, uint32_t *async_received) {
return ioctl(self()->mProcess->mDriverFD, BINDER_GET_FROZEN_INFO, &info);
}
status_t IPCThreadState::freeze(pid_t pid, bool enable, uint32_t timeout_ms) {
return ioctl(self()->mProcess->mDriverFD, BINDER_FREEZE, &info) < 0);
}
void IPCThreadState::logExtendedError() {
ioctl(self()->mProcess->mDriverFD, BINDER_GET_EXTENDED_ERROR, &ee) < 0);
}
status_t IPCThreadState::talkWithDriver(bool doReceive) {
// 实际Binder调用通信
return ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr);
}
快速过滤:
复制代码 隐藏代码
static int ioctl_hook(int fd, int cmd, void* arg) {
if (cmd != BINDER_WRITE_READ || !arg || g_ioctl_disabled) {
return g_ioctl_func(fd, cmd, arg);
}
}
如何解析
目标源码:http://aospxref.com/android-14.0.0_r2/xref/frameworks/native/libs/binder
重点解析发送(即BC_TRANSACTION
和BC_REPLY
)和接收(即BR_TRANSACTION
和BR_REPLY
)的类型数据。
如何修改数据
修改数据分为以下几种:
1)修复调用时参数数据。
2)修复调用后返回的结果数据。
如果数据修复不改变当前数据的长度,只是内容的变化,则可以直接通过地址进行修改。否则需要创建新的内存进行修改后将新的数据地址设置到BINDER_WRITE_READ
结构的buffer
成员。此时处理好内存的释放问题。
3)直接拦截本次调用。
为了保障稳定性,不打断Binder
的调用流程(通常这也是拦截和逆向方案保障稳定的最重要原则之一)。我们可以将目标函数code
修改成父类处理的通用方法,然后通过修复调用的返回值即可完成拦截。
方案实现
数据解析
Binder调用数据结构如下:
解析bwr
bwr
即binder_write_read
,从源码中了解到ioctl
的BINDER_WRITE_READ
类型的arg
数据结构为:
一旦您浏览本站,即表示您已接受以下条约:
1.使用辅助可能会违反游戏协议,甚至违法,用户有权决定使用,并自行承担风险;
2.本站辅助严禁用于任何形式的商业用途,若被恶意贩卖,利益与本站无关;
3.本站为非营利性网站,但为了分担服务器等运营费用,收费均为赞助,没有任何利益收益。
死神科技 » 深入Binder底层拦截