本站总访问量
文章目录
  1. 1. 0.为什么要研究Binder fuzz
  2. 2. 1.什么是Binder(有基础的可以略过这一部分)
  3. 3. 2.Binder fuzz怎么作
    1. 3.1. 1)如何取得服务的IBinder对象?
    2. 3.2. 2)code如何生成?
    3. 3.3. 3)data如何构造?
    4. 3.4. 4)fuzz系统和逻辑怎么设计?
    5. 3.5. 5)如何判断fuzz结果和识别安全漏洞?

扣吧力作,欢迎转载,转载请注明来自colbert337.github.io

最近扣吧忙成狗了,好久没更新博客,对不住大家了,今天趁天气暖和点,来一篇干货。

由于好久没搞Android了,写得不专业的地方,请见谅哈。

0.为什么要研究Binder fuzz

以目前最热门的指纹方案为例。

soter

TZ:Trustzone(请自行百度)
CA:Trustzone框架中的Clinet App
TA:Trustzone框架中的Trustzone APP

上层APP通过Binder机制调用keystore和FingerprintService两个底层系统服务,来获取密钥存储和指纹的能力。底层服务再通过CA跟TZ驱动通信,调用TZ中TA提供的服务,如指纹识别等安全性要求较高的服务。

我们今天只看Android侧的Binder体系。

Binder其实是提供了一种进程间通信(IPC)的功能。这些系统服务,通过binder协议抽象出一个个的“接口”,供其他进程调用,是一个重要的潜在的攻击面。如果没有做好权限控制,会让低权限的第三方应用/病毒/木马利用,后果不堪设想。

其次,做Android的同学都知道,Binder是android一个非常重要的机制,夸张一点可以说是“Android的灵魂”,非常有必要进行细致的分析和漏洞挖掘。

插播一个扣吧总结的知识点,系统服务的分类

1.Binder体系的java服务(有Stub接口,也就是AIDL封装)
2.Binder体系的Native服务
3.socket体系的init服务(通常见于init.rc)
4.其他服务

OK,再谈谈为什么使用fuzz技术呢?

总的来说,是因为fuzz在协议和接口安全测试中比较简单粗暴,试错成本低。所以,“不管什么接口,先fuzz一把看看”。

Fuzzing是一种基于缺陷注入的自动软件测试技术。通过编写fuzzer工具向目标程序提供某种形式的输入并观察其响应来发现问题,这种输入可以是完全随机的或精心构造的。Fuzzing测试通常以大小相关的部分、字符串、标志字符串开始或结束的二进制块等为重点,使用边界值附近的值对目标进行测试。

主要有两种类型的fuzzing技术 :

1)dumb fuzzing 这种测试无需了解协议或文件本身格式,通过提供完全随机的输入或简单改变某些字节去发现问题。这种方法实现起来较简单,容易快速触发错误,但它的完全随机性会导致产生大量无效的输入或格式。

2)Intelligent fuzzing 研究目标应用程序的协议或文件格式、功能配置,了解各类漏洞的成因,有目的地编写fuzzer。编写有效的fuzzer需要花费时间,但能够对某些感兴趣的部分集中测试,因此更有效。

1.什么是Binder(有基础的可以略过这一部分)

Android系统采用Binder机制作为进程间通信机制,类似于COM和CORBA分布式组件架构,通俗来讲其实就是提供远程过程调用(RPC)功能。

在Binder机制中,由Client、Server、ServiceManger、Binder驱动这四个部分组成,其中Client、Server、ServiceManager运行在用户空间,Binder驱动运行在内核空间。Binder就是把这四个组件粘合在一起的粘合剂,核心组件是Binder驱动,ServiceManager提供了辅助管理的功能。Client和Server正是在Binder驱动和ServiceManager提供的基础设施上,进行CS通信。

binderipc

下面这个流程图可以简单说明Client通过binder调用Server的一个过程,Client会通过Proxy(这里的Proxy不是单一实体,实际上是一系列的BpInterface、BpBinder等代理组件)去跟binder驱动通信,Proxy把数据打包成parcel类型数据再进行传输。

binderproxy

那么数据具体是怎么传输的呢?

我们继续深究一下,笔者总结了一个比较全的图。Java层服务其实也是在Native层服务BpBinder和BBinder的一个封装。如果屏蔽底层驱动来看,整个Binder代理的核心就是BpBinder和BBinder。

binderall

其中,BpBinder最重要的职责就是实现跨进程传输的传输机制,至于具体传输的是什么语义,它并不关心。我们观察它的transact()函数的参数,可以看到所有的语义都被打包成Parcel类型数据。(Parcel是轻量级的高效的对象序列化和反序列化机制,Android在Java空间和C++都实现了Parcel,由于它在C/C++中,直接使用了内存来读取数据,因此,它更有效率)

请记住这个伟大的函数——transact()

transact

举一个例子:上层APP调用MediaRecorder对外提供的API,名字叫setCamera,实际上是执行了BpMediaRecorder中的setCamera方法中,remote()返回的就是BpBinder对象,这里会组装好parcel数据包,会传给BpBinder的transact函数。transact函数就会把数据发给对端,也就是另一个BBinder对象。

setCamera

我们看一下具体是如何发送数据?

BpBinder的transact函数,通过层层调用,最终通过ioctl和binder驱动通信

binderioctl

嗯,上述的就是发送请求的过程。

下面来看接收方,Binder远程通信的目标端实体必须继承于BBinder类,该类和BpBinder相对,主要关心的只是传输方面的东西,不太关心所传输的语义。当收到回复后,会执行IPCTHreadState::waitForRespaonse函数的逻辑,并执行executeCommand(cmd)

executeCommand

executeCommand中,会取得一个合法的BBinder对象,并执行BBinder的transact函数。
(是不是有点奇怪,BBinder也有一个transact函数,请继续往下看吧)

BBinder

BBinder::transact中会调用onTransact,这个onTransact才是真正处理业务的。需要注意的是,因为我们的binder实体在本质上都是继承于BBinder的,而且我们一般都会重载onTransact()函数,所以上面的onTransact()实际上调用的是具体binder实体的onTransact()成员函数。也就是说,onTransact的具体实现一般在上层的binder实体,而不在BBinder。

onTransact

上面说了,BBinder没有实现一个默认的onTransact()成员函数,所以在远程通信时,BBinder::transact()调用的onTransact()其实是Bnxxx或者BnInterface的某个子类的onTransact()成员函数,举个例子,BnMediaRecorder中实现了一个onTransact函数,通过switch-case,根据不同code进行分发处理。

BnMediaRecorder

switch(code)中的code,其实就是前面说的BpBinder中transact函数传过来的int型的方法号。

2.Binder fuzz怎么作

经过上面的分析,我们已经对Binder有个全局的了解。fuzz的关键是选择好fuzz的目标和fuzz切入点(接口),那么应该如何选择呢?

思路就是农民斗地主!

前面也说了,系统服务(地主)具有高权限,是我们需要重点关注的对象,而低权限进程(农民)可以利用binder call去调用系统服务,从低权限到高权限,存在一个跨安全域的数据流,这里就是一个典型的攻击界面。所以,我们选择系统服务作为fuzz的目标。

binderfuzz1

那么Fuzz接口呢?选择fuzz接口需要满足这几个要求:

1)这个接口是开放的,是可以被低权限进程调用的
2)这个接口距离fuzz目标(系统服务)比较接近,中间路径最好透传,这样比较容易分析异常
3)从简原则

根据上面的分析,BpBinder中的transact函数就是一个很好的fuzz接口,但这货在底层无法直接调用。

怎么办呢?

我们从BpBinder往上层找,很容易发现,Java层IBinder的transact函数最终调用到BpBinder,且参数是原封不动的“透传”到底层,考虑到java层的可视化和扩展性,我决定选择IBinder的公有方法transact作为fuzz接口。

下图就是这个接口的定义:

fuzzapi

请大家认真看看上图注释的说明:

code是int类型,指定了服务方法号
data是parcel类型,是发送的数据,满足binder协议规则,下面会有详述
reply也是parcel类型,是通信结束后返回的数据
flag是标记位,0为普通RPC,需要等待,调用发起后处于阻塞状态直到接收到返回,1为one-way RPC,表示“不需要等待回复的”事务,一般为无返回值的单向调用。

下面开始讲重点了,额。

接口不是你想fuzz就能fuzz。我们来解决几个关键问题:

1)如何取得服务的IBinder对象?

我们要取到对端的IBinder对象,才可以调用这个服务。系统其实有一些隐藏API可以利用。先通过反射出ServiceManager(hide属性)中的listServices获取所有运行的服务名称:

listService

获取到String类型的服务名称后,再反射getService获取对应的服务IBinder对象:

getService

是不是很犀利,其实是借用了上文说的ServiceManager的强大力量。

2)code如何生成?

code也称为TransactionID,标定了服务端方法号。

每个服务对外定义的方法都会分配方法号,而且是有规律的,第一个服务方法code使用1,第二个是2,,第三个使用3,依次类推,如果有N个方法,就分别分配1-N个连续的服务号。

有个小技巧,对于Java服务,必定有Stub类,可以通过反射出mInterfaceToken+”$Stub”类中所有成员属性,其中以”TRANSACTION_”开头的int型就是该方法对应的。

如下图的例子,服务端greet方法对应的code就是TRANSACTION_greet:

aidlexample

如果是Native服务,就比较悲剧了,目前还没有好的自动化方法直接获取code。一般服务方法数不会太多,所以确定一个上限如50,从1到50循环生成code就可以把所有方法遍历。当然可以通过人工逆向分析出code,但这样成本比较高。

3)data如何构造?

通过大量的源码review和分析得知,data由“RPC header+参数1+参数2+….”来构成的。

举个例子,如下图,setDataSource这个API,首先调用data.writeInterfaceToken会写入一个RPC header,然后会依次写入调用方法的参数,比如setdataSource有3个参数,这里就会依次写入三个数据:

setDataSource

是不是很有规律!!

通过review writeInterfaceToken的实现,我们可以发现这个RPC header是由一个int型数据加上String类型的interface name来构成。

writeInterfaceToken

但我们不需要自己去构造RPC header,直接调用writeInterfaceToken函数,传入interface name就可以了。最后抽象出来的parcel类型的data应该是这样的:

RPCheader

那大家可能会问interface name是什么东西,如何获取?很简单,interface name是接口名称,只要取得IBinder对象,就可以直接getInterfaceDescriptor来获取interface name,也就是接口方法的描述符。

getInterfaceDescriptor

再看如何获取一个方法的参数和类型呢?

对于Java层服务的方法,可以通过反射获取method对象,然后用getParameterTypes获取所有的类型:

methodtpye

对于native层服务,无法直接获取方法参数类型,可以用过review调用者实现和反编译分析等方法来作。

4)fuzz系统和逻辑怎么设计?

直接上图吧。如下图,整个fuzz系统分为4个模块,分别是数据产生器,fuzz引擎,监视器和日志模块。

1)数据产生器就是用上述方法产生transact需要用到的数据

2)fuzz引擎用于执行具体的transact过程

3)监视器用于监控fuzz结果和异常

4)日志模块用于记录fuzz结果

fuzz

这里笔者采用了3种fuzz方法

1)dumb fuzz:构造好RPC header后,直接塞入大量随机数据,code范围为1-100,比较暴力。

2)intelligent fuzz:构造好RPC header后,精准识别出code,并根据不同的code构造出类型正确的随机参数

3)simple fuzz:构造好RPC header后,精准识别出code,但每次请求只写入int类型“0”,通过返回值,快速识别fuzz目标的接口是否有权限校验

5)如何判断fuzz结果和识别安全漏洞?

一般来说,要做到“权限判断+数据有效性判断”两层防护才是安全的。

通过监控transact的返回值和系统log和系统状态,可以看到的fuzz现象主要有以下几种:

1)有SecurityException,则说明该接口有进行权限判断,做了一层防护

2)无Exception,说明该接口没有进行权限校验,默认对外暴露,是不安全的,可以深挖

3)异常现象,如系统重启、指纹服务挂死、屏幕无响应等,说明该接口不仅没有进行权限判断,而且fuzz数据导致了缓存区溢出/进程crash等异常,这类现象要再去进行人工分析,很有可能会严重的提权漏洞(比如root)

举个例子,看看到底是哪里出现安全漏洞。下图,ontransact函数中switch-case结构里,其中一个case中没有对数据进行判断就读到*device_address,而这个指针直接当成参数直接使用,当指针地址异常就会引起系统服务进程crash,从而导致系统重启,是一个典型的拒绝服务漏洞。也就是说,任意一个低权限的进程可以随时进行攻击,导致系统重启。

cve

再举个例子,假设某手机厂家的系统指纹服务有个接口叫DeleteFingerPrint(),用于删除用户指纹,该服务的实现没有进行权限判断和参数校验,恶意攻击者就有可能构造参数,非法调用该服务的方法,把用户的指纹信息删除。

重要的事情要说三遍!

参数要做检查

参数要做检查

参数要做检查

今天先写到这里,写得有点乱,后续再更新一下。如果你喜欢扣吧的文章,请多多留言支持~

文章目录
  1. 1. 0.为什么要研究Binder fuzz
  2. 2. 1.什么是Binder(有基础的可以略过这一部分)
  3. 3. 2.Binder fuzz怎么作
    1. 3.1. 1)如何取得服务的IBinder对象?
    2. 3.2. 2)code如何生成?
    3. 3.3. 3)data如何构造?
    4. 3.4. 4)fuzz系统和逻辑怎么设计?
    5. 3.5. 5)如何判断fuzz结果和识别安全漏洞?