本文是 AndProxy——Syscall 及 Binder 运行时代理库 的升级版方案介绍。原方案通过 GotHook 拦截 Binder ioctl 实现服务代理;升级版完全抛弃 Hook,仅使用 seccomp-notify 机制,直接修改 Binder 事务中的目标 handle,利用 Binder 驱动自身的路由机制实现透明转发。关于 Binder 通信的基础概念,例如:可参考前置文章。在 Android 系统上,对系统服务调用进行拦截和代理,是虚拟机、应用分身、隐私保护等场景中的基础能力。传统代理方案大致分为两类。通过反射替换 ServiceManager 中的 IBinder 缓存。这种方式的问题是:通过 Hook libbinder.so 中的 ioctl 函数实现 Binder 调用拦截。原版 AndProxy 采用的就是这种方案,但它存在几个固有问题:既然所有 Binder 调用最终都会经过:那么问题就变成:能不能直接在系统调用层面拦截并修改 Binder 数据,而不修改任何代码?答案是:升级版 AndProxy 的核心思路是:整个过程不修改:只修改 Binder 事务中的一个字段:在展开方案之前,需要先理清几个关键概念。这是最常见的误区,也是方案设计的关键前提。Android 系统服务的注册名和类名并不相同:客户端通常通过服务名调用:获取对应的 Binder handle。只知道服务类名,无法直接推导出服务名。因此,在代理系统中必须同时区分:在 Binder 通信中,handle 是 Binder 驱动用来路由 IPC 数据的整数标识符。客户端发起 BC_TRANSACTION 时,会在:中指定目标服务的 handle。Binder 驱动根据这个值,将数据投递到对应的服务端进程。因此,只要能修改这个字段,就可以改变 Binder 事务的路由目标。也就是说:修改 target.handle,就等价于修改 Binder 请求的目的地。一次完整的 Binder 调用会被封装在:中。其中两个字段是本方案最关注的核心:方案的核心操作就是:在 BC_TRANSACTION 被提交到内核之前,将 target.handle 替换为代理服务的 handle。升级版 AndProxy 由三个子系统构成,均在用户态运行。整个方案通过注入 APK 的方式进入目标进程,在目标进程的用户态空间中运行。核心机制包括:整个过程不需要:升级版方案使用单个 @ProxyMethod 注解,直接声明代理一个目标方法所需的全部信息。示例:三个参数分别承担不同功能,缺一不可。Android 系统服务数量很多,而且服务类名与注册名之间没有统一规律。例如:手动维护一套完整映射表的成本很高,并且容易出错。更重要的是:serviceName 本身就是运行时获取 handle 的必要信息,并不是冗余字段。因此,让开发者在注解中同时声明 serviceClass 和 serviceName,是更务实的工程选择。这样既避免维护大型映射表,也让代理关系更加显式。KSP 处理器在编译期完成以下工作。遍历所有被 @ProxyMethod 标记的函数,提取三元组:例如:同一个 serviceName 下的代理方法会被归入同一个代理服务类。例如:每个代理服务对应一个 Binder 服务实例,运行时只注册一次。为每个被代理的方法生成一条路由记录。每条记录包含:最终生成的结构可以理解为:例如:这是整个方案的核心。它利用 Linux seccomp-notify 机制,在系统调用层面截获 Binder ioctl,并修改事务数据。seccomp-notify 对应的返回值是:它允许用户态 supervisor 接管被过滤器命中的系统调用。大致流程是:相比 Hook 方案,seccomp-notify 的优势是:SECCOMP_RET_USER_NOTIF 需要内核支持,典型环境要求:Seccomp-BPF 过滤器在内核态做快速判断。过滤逻辑如下:BPF 层只负责粗过滤。更精确的匹配逻辑,例如:都交给用户态 supervisor 处理。当 BPF 过滤器返回 SECCOMP_RET_USER_NOTIF 后,supervisor 接管处理。完整流程如下:Binder 事务的数据缓冲区中通常包含接口描述符,例如:这个字符串正好对应注解中的:由于注解中同时声明了:KSP 可以在编译期生成查找链:Supervisor 解析出 serviceClass 后,就可以直接定位到对应路由表。然后根据 binder_transaction_data.code 判断是否命中某个代理方法。整个替换过程完全发生在用户态内存中。ioctl 的第三个参数是一个指向用户空间结构体的指针:而 binder_transaction_data 又嵌套在 BWR 的写缓冲区中。当 seccomp-notify 暂停系统调用后:此时 Binder 驱动继续处理 ioctl 时,从用户态缓冲区读到的已经是修改后的数据:因此驱动会自然地将事务路由到代理服务。这个过程只修改一个字段:却把 Binder 路由交还给了 Binder 驱动本身完成。这也是该方案最简洁的地方:不重新实现 Binder 协议栈,只改变 Binder 驱动的路由目标。辅助系统维护各类映射关系,并为 supervisor 提供基础能力。辅助系统通过注解中声明的 serviceClass,在运行时反射对应 AIDL Stub 类中的 TRANSACTION_* 常量。例如:注解中的:会与:建立对应关系。最终生成:例如:辅助系统在代理服务启动时完成以下工作:整体映射可以理解为:例如:辅助系统提供 Binder 协议解析函数,用于 supervisor 中精确解析被拦截的 ioctl 数据。主要包括:这些函数是 supervisor 判断是否需要代理的基础。辅助系统还负责代理服务的启动和生命周期管理。具体包括:代理服务本身不需要知道请求是如何被路由过来的。从它的视角看,它只是一个普通 Binder 服务。下面以目标应用调用:为例,展示一次完整代理过程。开发者编写代理方法:KSP 处理器生成代理路由表:并生成对应代理服务类。初始化阶段完成:通过反射 IPackageManager.Stub 获取:将 code = 3 填入路由表。通过:获取原始服务 handle。创建代理服务实例。注册代理服务并获取代理服务 handle。安装 seccomp 过滤器。启动 supervisor。目标应用调用:此时流程如下:该方案不修改:也不需要:所有操作都发生在系统调用通知和用户态内存数据修改层面。方案只修改:之后的路由过程完全交给 Binder 驱动完成。因此不需要重新实现:本质上,这是在利用 Binder 已有能力做转发。通过 KSP 注解处理器,代理声明在编译期被收集和整理。运行时只需要填充:代理路由结构在编译期就已经确定,运行时逻辑更简单。Seccomp-BPF 过滤器在内核态执行粗过滤。只有命中的:才会进入 supervisor。Supervisor 只需要做:整体路径清晰,额外逻辑集中在 Binder 事务发起前。相比传
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-291098.htm
[原创]AndProxy 升级版:基于 Seccomp 的无 Hook Binder 服务代理
469 浏览
14 回复
谢谢分享
基于这个思路,实现了一个类似于CorePatch的内核模块,通过内核转发和注册系统服务,直接干掉签名校验
谢谢分享
看看
向大佬学习
厉害了
谢谢分享
tql
学习一下
看看老哥
学习学习
孤木落
基于这个思路,实现了一个类似于CorePatch的内核模块,通过内核转发和注册系统服务,直接干掉签名校验
可以在应用层实现吗
基于这个思路,实现了一个类似于CorePatch的内核模块,通过内核转发和注册系统服务,直接干掉签名校验
可以在应用层实现吗
谢谢分享
啊你好哇123
可以在应用层实现吗
我在文章中探讨的seccomp就是在用户态实现的方法
ebpf方案我暂时没有分享
可以在应用层实现吗
我在文章中探讨的seccomp就是在用户态实现的方法
ebpf方案我暂时没有分享