农民斗地主——Binder fuzz安全研究

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(),用于删除用户指纹,该服务的实现没有进行权限判断和参数校验,恶意攻击者就有可能构造参数,非法调用该服务的方法,把用户的指纹信息删除。

重要的事情要说三遍!

参数要做检查

参数要做检查

参数要做检查

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

 

转载自:http://colbert337.github.io/2016/01/25/binder-fuzz/    原文作者:colbertli