浅谈 Android Binder 监控方案

在 Android 应用开发中,Binder 可以说是使用最为普遍的 IPC 机制了。我们考虑监控 Binder 这一 IPC 机制,一般是出于以下两个目的:

  • 卡顿优化:IPC 流程完整链路较长,且依赖于其他进程,耗时不可控,而 Binder 调用本身通常又是以 RPC 形式对外提供能力的,使得我们在使用时更容易忽略其 IPC 的本质。总的来说,主线程阻塞在同步 Binder 调用上是导致应用卡顿的一大典型原因。
  • 崩溃优化:Binder 调用过程中可能出现各类异常情况,典型的是 Binder 缓冲区耗尽的情况( 经典 TransactionTooLargeException )。Binder 的缓冲区大小大约为 1M( 注意到这里的缓冲区是最早 mmap 出来进程内全局共享的,而非单次 Binder 调用的限制 ),而异步调用( oneway )的情况更是被限制只能使用缓冲区的一半大小( 约 512K )。

考虑只监控某些个系统服务的情况,得益于 ServiceManager 和 AIDL 的设计,我们可以简单基于动态代理替换当前进程对应的 Proxy 对象来实现监控;而要实现进程内全局 Binder 监控的话,我们需要考虑怎么拦截到 Binder 调用通用的 transact 方法。

基于 Binder.ProxyTransactListener

注意到 Android 10 上系统引入了 Binder.ProxyTransactListener,在 Binder 调用前后( BinderProxy 的 transactNative 方法内部 )会触发回调。从提交记录来看,ProxyTransactListener 引入的目的之一就在于支持 SystemUI 监控主线程的 Binder 调用。

美中不足的地方在于,ProxyTransactListener 和相应的设置接口都是 hide 的,且 BinderProxy 的类属性 sTransactListener 在 hidden api 名单中。不过到目前为止,我们始终是有稳定的方案可以绕过 hidden api 的限制的,因此我们可以基于动态代理创建一个 ProxyTransactListener 实例设置给 BinderProxy,来实现对进程内 Java 的 Binder 调用的全局监控。

这里稍微提一下 hidden api 的绕过方案,在 Android 11 系统禁掉元反射之后,一个简单的方案是先创建一个 Native 线程再 Attach 拿到 JNIEnv,就可以在该线程内正常使用

VMRuntime.getRuntime().setHiddenApiExemptions(new String[]{"L"}); 

来实现全局 hidden api 加白。原理是系统对于基于 JNI 访问 Java API 的情况,在回溯 Java 堆栈找不到 caller 的情况,会信任该次调用不做 hidden api 的拦截,详细逻辑见 GetJniAccessContext。因此我们可以通过创建 Native 线程再 AttachCurrentThread 访问 JNI 接口的方式来构造没有 Java caller 的情况( 这也是 Native 线程 AttachCurrentThread 无法访问应用类的原因,没有 caller 就找不到可用的 ClassLoader )。比较有意思的地方是实际上官方早就意识到了这类 hidden api 后门的存在,但由于改动风险太大一类的原因一直没有合入限制逻辑,类似的讨论可以参考 Don’t trust unknown caller when accessing hidden API。

这一方案实现简单,兼容性好,主要的毛病在于:

只支持 Android 10 及以上版本,不过现状 Android 10 以下的设备占比已经不高了。
ProxyTransactListener 的接口设计上不带 data 参数( Binder 调用的传入数据 ),我们也就无法做传输数据大小的统计。此外,对于多进程应用来说,实践上可能会以统一的 AIDL 对象作为通信通道封装一层屏蔽了匿名 Binder 对象传递和 AIDL 模版代码的 IPC 框架,实际 IPC 调用的目标逻辑则以统一的调用约定封装在 data 参数中。这种情况下,只有拿到 data 参数( 将 IPC 调用统一放到线程池中执行的情况,堆栈没有意义 )我们才能实际确认 IPC 调用的真正目标逻辑。

JNI Hook BinderProxy.transactNative

实际上 Java 的 Binder 调用总是会走到 BinderProxy 的 JNI 方法 transactNative,我们可以基于 JNI Hook 来 hook transactNative,实现全版本的进程内 Java 的 Binder 调用的全局监控,也可以拿到 Binder 调用的完整参数和返回结果。

这里稍微提一下 JNI Hook,JNI Hook 基于 JNI 边界 hook Java JNI 方法对应的 Native 函数实现,具体实现上 hack 点少,稳定性较好,算得上是线上比较常用的一类通用 Native Hook 方案。大体上讲,JNI Hook 实现上分为两步,找到原 Native 函数和替换该 Native 函数。

  • Native 函数的替换比较简单,我们通过调用 JNI 的 RegisterNatives 接口就可以实现 Native 函数的覆盖。( 注意到如果原始 JNI 方法也是通过 RegisterNatives 注册的,我们需要保证 JNI Hook 的 RegisterNatives 执行在后 )
  • 找到原 Native 函数则稍微复杂些,而且我们总是需要依赖原 Native 函数已经先行注册才能找到该 Native 函数。
    • 一个可行的方案是手动实现一个 JNI 方法,用来计算实际 ArtMethod 对象( 即 Java 方法在 art 中的实际表示 )中存放 Native 函数的属性的偏移。得到这个偏移之后即可以基于被 Hook JNI 方法的 ArtMethod 对象拿到原 Native 函数。怎么拿到 ArtMethod 对象?实际上,在 Android 11 以前,jmethodID 就是 ArtMethod 指针,在 Android 11 之后,jmethodID 默认变成了间接引用,但我们仍然可以通过 Java Method 对象的 artMethod 属性拿到 ArtMethod 指针。详细介绍可参考一种通用超简单的 Android Java Native 方法 Hook,无需依赖 Hook 框架。
    • 另一个可行的方案是可以基于 art 的内部函数 GetNativeMethods 来直接查询得到原 Native 函数。GetNativeMethods 几个函数是 art 用于支持 NativeBridge 的,稳定性上也有所保证。NativeBridge 详细介绍可参考用于 Android ART 虚拟机 JNI 调用的 NativeBridge 介绍。

具体到 JNI Hook BinderProxy.transactNative,实际在跑到应用的第一行业务代码( Application 的 attachBaseContext )之前,就已经有 Java 的 Binder 调用发生,因此我们根本不需要手动触发 Binder 调用来保证 BinderProxy.transactNative 的 Native 函数注册。另外,注意到 BinderProxy 的 transactNative 也是 hidden api,这里也需要先行绕过 hidden api 的限制。

Hook BinderProxy.transactNative 的方案可以很好地满足监控进程内全局 Java Binder 调用的需要,但却监控不到 Native 的 Binder 调用。注意到这里的 Java/Native Binder 调用的差异在于 IPC 通信逻辑的实现位置,而非实际业务逻辑的实现位置。典型的如 MediaCodec 一类的音视频接口,实际的 Binder 调用封装都实现在 Native 层,我们使用 Java 调用这些接口,通过 BinderProxy.transactNative 也无法监控到实际的 Binder 调用。要实现包含 Native 的全局 Binder 调用监控,我们需要考虑 Hook 更下一层的 Native 的 transact 函数。

PLT Hook BpBinder::transact

和 Java 层的 Binder 接口设计类似,Native 层 Client 端发起的 Binder 调用,总是会走到 libbinder.so 中 BpBinder 的 transact 函数。注意到 BpBinder 的 transact 函数是一个导出的虚函数,而且使用上总是基于基类 IBinder 指针做动态绑定调用( 也就是说,其他 so 总是基于 BpBinder 的虚函数表来调用 BpBinder::transact,而不是直接依赖 BpBinder::transact 这一符号,而 BpBinder 的虚函数表在 libbinder.so 内部 ),因此我们直接 PLT Hook libbinder.so 对于 BpBinder::transact 的调用即可。

具体看下 BpBinder::transact 的函数声明:

    // NOLINTNEXTLINE(google-default-arguments)
    virtual status_t    transact(   uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags = 0) final;

其中,status_t 实际上只是 int32_t 的别名,但 Parcel 则不是 NDK 暴露的接口,我们没有途径拿到绝对稳定的 Parcel 对象的布局,好在 transact 函数对于 Parcel 的使用是基于引用和指针的( 引用在汇编层面的实现和指针类似 ),我们不需要依赖 Parcel 对象的布局也可以实现一个 transact 的替代函数。

在成功拦截到 BpBinder::transact 的调用之后,我们还需要考虑怎么基于 transact 的调用参数和返回值来获取到我们需要的信息。

对于 Binder 对象( 即 transact 的隐含调用参数 this 指针 )本身,我们通常会关注它的 descriptor( 再结合 code 参数可以定位到实际 IPC 调用的目标逻辑 ),这里我们直接调用导出接口 BpBinder::getInterfaceDescriptor 即可。

    virtual const String16&    getInterfaceDescriptor() const;

比较麻烦的是 String16 也不是 NDK 暴露的接口,而且它用来转成 char16_t* 字符创的函数实现是内联的,

    inline  const char16_t*     string() const;

我们只能重新 hardcode 声明一个类似的 String16 类来做强转。好在从系统源码看,String16 的对象布局比较简单且稳定,只有一个 const char16_t* 类型的私有属性 mString,而且不存在虚函数。类似这样:

class String16 {
public:
    [[nodiscard]] inline const char16_t *string() const;
private:
    const char16_t* mString;
};

inline const char16_t* String16::string() const {
    return mString;
}

拿到 String16 对应的 char16_t* 字符串之后,我们直接在回调 Java 时用 JNI 接口将其转为 jstring 即可。

另一个常用的信息是 data 的数据大小。我们可以直接调用导出接口 Parcel::dataSize 来获取。注意到 transact 函数 的 data 参数是 Parcel 的引用,我们直接声明一个空类来承接 data 参数,再对拿到的 data 取址,让编译器可以正常完成引用到指针的转换即可。类似这样:

class Parcel {};

// size_t dataSize() const;
typedef size_t(*ParcelDataSize)(const Parcel *);

// virtual status_t transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) = 0;
status_t HijackedTransact(void *thiz, uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags);

ParcelDataSize g_parcel_data_size = nullptr;
auto data_size = g_parcel_data_size(&data);

此外,注意到对于 Java 的 Binder 调用而言,会在 BinderProxy.transactNative 的内部再调用到 BpBinder::transact,我们可以结合 JNI Hook 和 PLT Hook 两个方案,对 Java 的 Binder 调用,基于 JNI Hook 拿到完整的 Java 参数,方便我们在 Java 回调中直接基于 Java 参数做进一步处理。

One More Thing

拦截到 Binder 调用只是监控的第一步,更为重要的是在这个基础上如何做数据处理来发现和定位问题。

前面提到的两类经典问题:IPC 耗时卡顿和传输数据过大崩溃,可以通过前后打点统计 transact 耗时以及调用前获取传输数据大小的方式来挖掘。 定位问题上,堆栈和当次 Binder 调用的 descriptor 和 code 是比较有价值的信息。

如果你还没有掌握Framework,现在想要在最短的时间里吃透它,可以参考一下《Android Framework核心知识点》,里面内容包含了:Init、Zygote、SystemServer、Binder、Handler、AMS、PMS、Launcher……等知识点记录。

《Framework 核心知识点汇总手册》:https://qr18.cn/AQpN4J

Handler 机制实现原理部分:
1.宏观理论分析与Message源码分析
2.MessageQueue的源码分析
3.Looper的源码分析
4.handler的源码分析
5.总结

Binder 原理:
1.学习Binder前必须要了解的知识点
2.ServiceManager中的Binder机制
3.系统服务的注册过程
4.ServiceManager的启动过程
5.系统服务的获取过程
6.Java Binder的初始化
7.Java Binder中系统服务的注册过程

Zygote :

  1. Android系统的启动过程及Zygote的启动过程
  2. 应用进程的启动过程

AMS源码分析 :

  1. Activity生命周期管理
  2. onActivityResult执行过程
  3. AMS中Activity栈管理详解

深入PMS源码:

1.PMS的启动过程和执行流程
2.APK的安装和卸载源码分析
3.PMS中intent-filter的匹配架构

WMS:
1.WMS的诞生
2.WMS的重要成员和Window的添加过程
3.Window的删除过程

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/95216.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

本地私有仓库、harbor私有仓库部署与管理

本地私有仓库、harbor私有仓库部署与管理 一、本地私有仓库1.本地私有仓库简介2.搭建本地私有仓库3.容器重启策略介绍 二、harbor私有仓库部署与管理1.什么是harbor2.Harbor的特性3.Harbor的构成4.harbor部署及配置5.客户端测试 三、Harbor维护1.创建2.普通用户操作私有仓库3.日…

PDFPrinting.Net Crack

PDFPrinting.Net Crack 它能够轻松灵活地预测完美的打印结果以及用户文件的示例性显示。在.NET的PDF打印中,可以快速浏览最关键的元素。如果用户需要获得更详细的概述,那么他可以查看快速入门手册,甚至现有文档的详细概述参考。 在这种情况下…

Java集合sort排序报错UnsupportedOperationException处理

文章目录 报错场景排查解决UnmodifiableList类介绍 报错场景 我们使用的是PostgreSQL数据库,存储业务数据,业务代码使用的是Spring JPA我们做的是智慧交通信控平台,有个功能是查询展示区域的交通态势,需要按照不同维度排序展示区…

SQL注入之布尔盲注

文章目录 布尔盲注是什么?布尔盲注获取sqli-labs名称 布尔盲注是什么? 当存在SQL注入时,攻击者无法通过页面或请求的返回信息,回显或获取到SQL注入语句的执行结果,这种情况就叫盲注。 布尔型盲注就是利用返回的True或F…

【校招VIP】前端算法考察之排序

考点介绍: 不同的场景中,不同的排序算法执行效率不同。 稳定:冒泡、插入、归并 不稳定:选择、快速、堆排序、希尔排序 『前端算法考察之排序』相关题目及解析内容可点击文章末尾链接查看! 一、考点题目 1、使用js实…

4.RabbitMQ高级特性 幂等 可靠消息 等等

一、如何保证生产者生产消息100%的投递成功 保障消息的成功发出保障MQ节点的成功接收发送端收到MQ节点(Broker)确认应答完善的消息进行补偿机制 1. 理解Confirm确认消息机制 消息的确认,是指生产者投递消息后,如果Broker收到消…

腾讯云学生服务器申请、学生认证入口及学生机价格表

腾讯云学生服务器申请、学生认证入口及学生机价格表,学生机申请流程,腾讯云学生服务器优惠活动:轻量应用服务器2核2G学生价30元3个月、58元6个月、112元一年,轻量应用服务器4核8G配置191.1元3个月、352.8元6个月、646.8元一年&…

极智嘉(Geek+)再获重磅荣誉,持续力领跑智慧物流行业发展

近日,全球仓储机器人引领者极智嘉(Geek)再度传来好消息,凭借着全球化的专业服务能力和稳健增长的亮眼海外成绩,一举荣登“2023出海品牌服务商”价值榜,成为唯一登榜的物流机器人企业。 作为率先出海的物流机器人企业,极…

Ubuntu 18.04上无法播放MP4格式视频解决办法

ubuntu18.04系统无法播放MP4格式视频,提示如下图所示: 解决办法: 1、首先,确保ubuntu系统已完全更新。可使用以下命令更新软件包列表:sudo apt update,然后使用以下命令升级所有已安装的软件包&#xff1a…

数据库导出工具

之前根据数据库升级需求,需要导出旧版本数据(sqlserver 6.5),利用c# winfrom写了一个小工具,导出数据。 →→→→→多了不说,少了不唠。进入正题→→→→ 连接数据库:输入数据库信息 连接成功…

Visual Studio2022史诗级更新,增加多个提高生产力的功能

Visual Studio 2022发布了17.7x版,这次更新中,增加多个提高生产力的功能以及性能进一步改进。 如果要体验新功能,需要将Visual Studio 2022的版本升级到17.7及以上 下面我们看看新增的功能以及改进的功能! 目录 文件比较自动修复代…

用心维护好电脑,提高学习工作效率

文章目录 一、我的电脑1.1 如何查看自己的电脑硬件信息呢? 二、电脑标准保养步骤和建议2.1 保持清洁2.2 定期升级系统和软件2.3 安全防护2.4 清理磁盘空间2.5 备份重要数据2.6 优化启动项2.7 散热管理2.8 硬件维护2.9 电源管理2.10 注意下载和安装2.11 定期维护 三、…

Yolov8-pose关键点检测:模型轻量化创新 | PConv结合c2f | CVPR2023 FasterNet

💡💡💡本文解决什么问题:新的partial convolution(PConv),通过同时减少冗余计算和内存访问可以更有效地提取空间特征。 PConv| GFLOPs从9.6降低至8.5,参数量从6482kb降低至6134kb, mAP50从0.921提升至0.925 Yolov8-Pose关键点检测专栏介绍:https://blog.csdn.n…

解决华为云ping不通的问题

进入华为云控制台。依次选择:云服务器->点击服务器id->安全组->更改安全组->添加入方向规则,添加一个安全组规则(ICMP),详见下图 再次ping公网ip就可以ping通了 产生这一问题的原因是ping的协议基于ICMP协…

编写Dockerfile制作Web应用系统nginx镜像

文章目录 题目要求:一、创建文档,编写Dockerfile文件可以将harbor仓库去启动先起来 二、运行Dockerfile,构建nginx镜像三、推送导私有仓库,也就是我们的harbor仓库 题目要求: 编写Dockerfile制作Web应用系统nginx镜像…

智慧校园用电安全解决方案

随着科技的不断发展,智慧校园建设逐渐成为了教育行业的一大趋势。在这个过程中,电力系统作为校园基础设施的重要组成部分,其安全、稳定、高效的运行显得尤为重要。下面小编来为大家介绍下智慧校园用电安全解决方案吧! 一、智慧校园电力系统现…

软件工程(十八) 行为型设计模式(四)

1、状态模式 简要说明 允许一个对象在其内部改变时改变它的行为 速记关键字 状态变成类 类图如下 状态模式主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。比如订单从待付款到待收货的咋黄台发生变化,执行的逻辑是不一样的。 所以我们将状态抽象为一…

word6 图文混排

目录 7-1 段落缩进排版7-2 搞定多级列表难题 7-1 段落缩进排版 段落对齐 缩进问题 悬挂缩进:缩进首行以外的段落 段落对齐: 7-2 搞定多级列表难题

抖音web频道爬虫

文章内容仅供参考学习,如有侵权请联系作者进行删除 抖音web频道爬虫运行结果演示,运行长期稳定【需要源码请先订购该栏目】。

移动端和PC端对比【组件库+调试vconsole +单位postcss-pxtorem+构建vite/webpack+可视化echarts/antv】

目录 组件库 移动端 vue vant PC端 react antd vue element 调试:vconsole vs dev tools中的控制台(Console) ​​​​​​​vconsole:在真机上调试 postcss-pxtorem:移动端不同的像素密度 构建工具 web…