Android本地服务漏洞挖掘技术 - 中篇

admin 2023年12月23日01:34:24评论22 views字数 3076阅读10分15秒阅读模式
引言
在上文《Android本地服务漏洞挖掘技术 - 上篇》中,我们介绍了Android本地服务中Socket服务的漏洞挖掘相关内容。在本篇中,我们会对Binder服务的基础知识、攻击面、挖掘方法进行详细阐述。
基础知识
按照作用域和所处的架构层次,我们将Binder服务分为Origin Binder服务,AIDL Binder服务,HIDL Binder服务和Vendor Binder服务。其详细定义如下:
Origin Binder服务:这并不是一个官方的命名,这类指的是命名空间为/dev/binder,服务入口函数仍为人工编码的onTransact接口的服务;

AIDL Binder服务:AIDL全称为Android Interface Definition Language,是一种Android接口定义语言;

HIDL Binder服务,HIDL全称为HAL Interface Definition Language,同样是一种Android接口定义语言,不同之处它是用于HAL与其他进程的通信;

Vendor Binder服务:命名空间为/dev/vndbinder,主要用于vendor进程与vendor进程的通信。

Origin Binder服务

在Android 8之前,绝大多数Binder服务都属于此类。此时的Android安全还是一片蓝海,且AOSP的代码质量也并不高,因此众多白帽子发现了大量的安全漏洞。

收集信息
通常可以通过两种方式定位到服务和入口函数:
其一,使用系统自带的service list命令,不过由于这个列表中大量的服务都是纯Java服务,如果是想挖掘内存破坏相关漏洞,需要更多关注C/C++服务。

Android本地服务漏洞挖掘技术 - 中篇
Android本地服务漏洞挖掘技术 - 中篇

其二,在Android源码中查找onTransact函数,比如可以在cs.android.com中得到如下结果:

Android本地服务漏洞挖掘技术 - 中篇
Android本地服务漏洞挖掘技术 - 中篇

漏洞挖掘
除了权限问题以外,大部分漏洞主要是由于服务端未正确校验客户端传入的Parcel数据,导致的各类内存破坏漏洞,如未初始化内存,越界读写,类型混淆等。挖掘方法同样分为人工审计和模糊测试两种,不过根据个人经验来看,模糊测试并不适用,这是因为:
1.Parcel数据除了常规的uint32,uint8等整型以外,还会包含一些特殊类型,如文件句柄,native handle,parcelable对象等,这使得如果采用afl或libfuzzer的变异策略,那么就很难能够触发到深层次的代码路径;
2. 服务的各个接口直接通常存在上下文关系,即要触发B接口,首先需要调用A接口,这也使得传统模糊测试很难做到;
3.某些深层次服务需要通过公开服务的接口获得;
4.服务代码的复杂度较低,人工审计从onTranscat函数跟踪数据流转并不会显得低效;

未初始化内存
我们以CVE-2020-0134为例,代码在栈上声明了state变量,但并没有初始化值,接着调用getOfflineLicenseState函数以为其赋值。

Android本地服务漏洞挖掘技术 - 中篇
Android本地服务漏洞挖掘技术 - 中篇

可以看到在某些条件下,getOfflineLicenseState函数会提前返回,并不会对state赋值。

Android本地服务漏洞挖掘技术 - 中篇
Android本地服务漏洞挖掘技术 - 中篇

接着代码调用reply->writeInt32(static_cast<DrmPlugin::OfflineLicenseState>(state));,将未初始化的state值返回给客户端,从而导致内存信息泄露。

除了这种直观的漏洞模式以外,在Origin Binder服务中,未初始化内存还有以下几种模式:
1.Parcel::read(void* outData, size_t len)用于从Parcel数据中读取len大小的数据到outData缓冲区中,如果Parcel数据长度小于len,那么函数会返回错误且不会清理outData缓冲区,那么如果上层代码在调用Parcel::read之前未初始化变量,且又未校验Parcel::read返回值时,即很大概率会触发漏洞;
2.某个IBinder对象的成员变量未在构造函数中初始化;

从Android 12开始,默认启用零初始化内存特性,即编译器会自动对原生代码中的堆栈变量进行赋0操作,几乎是从根本上消灭了此类型漏洞,不过根据实测,对于某些大型堆分配,由于性能的考虑,编译器似乎不一定会进行零初始化。

越界读写

我们以CVE-2020-0346为例,在ATTACH_BUFFER这个分支里,代码尝试从parcel数据中反序列化出GraphicBuffer对象。

Android本地服务漏洞挖掘技术 - 中篇
Android本地服务漏洞挖掘技术 - 中篇

data.read(*buffer.get())实际上会调用到GraphicBuffer::unflatten函数,当buf[0] == 'BHBB'时候,代码会又调用unflattenBufferHubBuffer函数。

Android本地服务漏洞挖掘技术 - 中篇
Android本地服务漏洞挖掘技术 - 中篇

numIntsInToken的最大值可为0xffffffff,由于该服务进程是32位进程,因此const size_t sizeNeeded = static_cast<size_t>(3 + numIntsInToken) * sizeof(int)会产生整数溢出,使得sizeNeeded小于size,从而绕过后面的合法性校验,最终在memcpy处发生越界写漏洞。

Android本地服务漏洞挖掘技术 - 中篇
Android本地服务漏洞挖掘技术 - 中篇

随着代码质量的改善和白帽子持之以恒的挖掘,从Android 11开始,此类漏洞也几乎绝迹了。

类型混淆
我们以CVE-2021-1027为例,在SET_TRANSACTION_STATE分支中,代码从parcel数据中反序列化构造了类型为ComposerState的数组state,接着作为参数传递到setTransactionState函数。

Android本地服务漏洞挖掘技术 - 中篇
Android本地服务漏洞挖掘技术 - 中篇

在进入setTransactionState函数之前,我们先看看ComposerState的定义。可以看到,ComposerState为一个结构体,其中包含一个类型为layer_state_t的state成员变量,而layer_state_t中又有一个IBinder类型的成员变量surface。

Android本地服务漏洞挖掘技术 - 中篇
Android本地服务漏洞挖掘技术 - 中篇

再经过多轮函数调用后,代码会执行到addSurfaceChangesLocked函数,其中关键代码如下:

const sp<const Layer> layer(getLayer(state.surface));

在getLayer函数中,代码通过static_cast关键字强制将IBinder类型的weakHandle转换为Layer::Handle,根据前文的分析,这个surface成员变量的值是由客户端传入的,由于这里的强制转换并没有校验类型,因此可能导致类型混淆。

Android本地服务漏洞挖掘技术 - 中篇
Android本地服务漏洞挖掘技术 - 中篇

更多关于此漏洞的信息,大家可以参考谷歌提供的补丁:https://android.googlesource.com/platform/frameworks/native/+/a8c7c54eed57e5a4b56905a4fb00e27e25b1b908。

类型混淆漏洞通常更为隐蔽,也会造成更严重的安全风险。

Android本地服务漏洞挖掘技术 - 中篇

总结

近几年来,从Pixel手机上的统计来看,Origin Binder服务(C/C++)的数量愈来愈少,而且原有服务的代码变动也很小。这些因素使得近两年来,已经很少有白帽子发现相关漏洞。如果你仍对此领域有兴趣,那么更推荐你去关注业务逻辑安全(如权限校验不当、用户交互绕过、敏感信息泄漏等),或者也可以看看关于智能指针生命周期、强弱类型转换的问题,也许会有新的发现。

参考:
1.https://source.android.com/docs/security/bulletin/

END

原文始发于微信公众号(OPPO安珀实验室):Android本地服务漏洞挖掘技术 - 中篇

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月23日01:34:24
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Android本地服务漏洞挖掘技术 - 中篇http://cn-sec.com/archives/2329092.html

发表评论

匿名网友 填写信息