2020年3月22日星期日

使用LLVM Dump 安卓中的Binder接口

artical1-android-binder-dump

1. 概述

LLVM 给开发者提供了方便的接口,以便在编译过程中对程序进行优化。在安全领域,安全分析人员常可借助编写LLVM Pass,在 IR 层次作静态分析或是动态的插桩等操作,著名的 LLVM pass例如常出现在CTF中,让人头大的OLLVM。

Binder机制是广泛应用于安卓系统中的一种IPC机制,各个系统服务之间需要进行跨进程通信时,大多采用这种方式。因而binder在各大系统服务之间充当了桥梁,而根据Pratyusa K. Manadhata文章中关于攻击面的定义[1]:

attackSurface = methods + channel + dataItem

Binder 既作为methods, 又提供了channel,是安卓系统中的一类重要攻击面,当数据借助binder从低权限进程流入高权限进程时,有机会发起权限提升。

本文将介绍通过编写LLVM PASS,在编译过程中对AOSP的源代码进行分析,找到所有的 binder 接口及其函数,然后分析它们的调用关系,借此来梳理安卓中的本地提权攻击面。

本文源代码:https://github.com/xiangxiaobo/BinderInterfaceFinder

2. AOSP加载LLVM PASS 环境搭建

首先是环境的搭建,编写一个LLVM Pass让AOSP在编译过程中加载起来并不像其官方文档写的一样,先用clang对单个c/cpp文件编译,而后使用opt优化。AOSP的build子系统相当复杂,它采用soong以及blueprint对每个 Android.bp 文件进行解析,取出来各项参数,例如CFLAG,而后拼成单个的编译命令。

因而如果要对AOSP的单个project进行分析,则仅须修改其Android.bp 即可,但是如果要针对整个AOSP,那么要找到能影响全局的地方,这个地方在 global.go 文件中,这里我作出的修改:

如上,在 commonGlobalCflags 选项中添加这几项之后,即可在AOSP编译每一个 cc 文件的时候均加载起来我的 so。安卓10版本的 build 系统作了一些修复,如果是在旧版本的安卓里面,global cflag 是不允许接收不以"-"开头的参数的,因而还需要将一些 check patch掉才行。

 

此外,安卓所使用的 llvm 也是经过一定程度订制的,直接使用master分支或者任意一个llvm版本是行不通的,因为llvm在频繁更新,很多api可能都会发生变化,因而我的so应该与aosp所使用的llvm版本一致。这个版本号在哪里找?仍然是在这些复杂的build目录下,定义于 global.go 中:

如上,我当前的 aosp 分支使用的是 "clang-r353983c",预先放好的 clang 编译工具链位于 prebuilts 目录下,不过仅有已经编译好二进制编译工具链。

若要基于这个版本编写自己的插件,还需要有 r353983c 的源代码,在 android-llvm 的 google group中我找到了解决方案[2]:

Here're the steps for building any recent version of llvm prebuilts:

$ repo init -u https://android.googlesource.com/platform/manifest -b llvm-toolchain // copy manifest-.xml from clang-build to .repo/manifests $ repo init -m manifest-.xml // Do not use absolute path here

This should initialize the sources to the version used for that particular prebuilts. You can build that version of Clang by:

$ python toolchain/llvm_android/build.py

上面提到的 manifest-<build>.xml 对应于 clang-r353983c 目录下的 manifest 文件,如下:

至此,即可在AOSP中无痛使用自己编译的编译工具链了。

为AOSP编写LLVM PASS的方式并没有什么特殊的地方,在 llvm/lib/Transforms 中建文件夹写代码,而后在CMakeList.txt 中添加项即可。

以下来看 BinderInterfaceFinder 这个 pass 的具体实现。

3. BinderInterfaceFinder

3.1 Binder 接口特征分析

由于一个 binder 接口的代码分为server端与client端,即Bn端与Bp端,Bn类仅实现一个onTransact函数,而Bp类会实现接口中定义的所有函数,因而分析过程中从Bp端入手会比较方便,以下看一个实例。

以 IMediaDrmService为例,IMediaDrmService 接口继承于 IInterface,这是Binder接口的重要特征。

BnMediaDrmService是 IMediaDrmService 的server端,它实现了onTransact函数,在其中根据 transaction code 的编码调用具体的实现代码,Bn所调用的makeDrm以及makeCrypto并不由Bn自己实现,而是由Bn的子类去完成,它只管 onTransact。

Bp类是这个binder接口的客户端,如下,它将 makeCrypto 以及 makeDrm 都实现了一遍,它们的功能是将需要的参数写入到Parcel中,然后通过 transact 传出去,并取回transact之后 Bn 端的返回值。

 

从静态分析的角度来讲,从Bp端入手来找到所有的接口是可行的一种方案。

3.2 Bp 类的识别

LLVM Pass在解析过程中,提供的第一个入口函数为 runOnModule,即在当前源文件之上进行处理,在runOnModule中可以拿到一个迭代器,通过该迭代器可以对当前 module 的所有函数进行处理,因而在dump binder接口的过程中,我是直接从 function 入手,而后推出这个function所在的类,并确认它就是Binder的Bp类。安卓系统中的Binder接口代码实现特别规范,据我所审计的binder接口中,都遵从BnBp的命名方式,可以比较放心地以 Bp 作为一个特征,为了减少误报的情况,在拿到类名之后,需要确认它继承自 IInterface。

因此,Bp类的识别包括两个问题,一是如何从函数名中取出类名,二是如何确认这个类就是 IInterface的子类。

先看第一点,c++函数的第一个参数是 this指针,this指针的类型信息就是类名。不过也有特殊的情况,如果返回值是一个struct或者是一个class,那么在LLVM生成的IR中,第一个类名就不是this指针了,而是返回值。

例如,BpMediaDrmService::makeCrypto 函数的 IR 形式如下:

所对应源代码中的声明:

它的第二个参数才是class.android::BpMediaDrmService类型的this指针,第一个参数 agg.result 则是一个 class.android::sp.6 类型的指针,sp.6 类型对应的是 ICrypto 类型,即返回值信息。

仔细看 agg.result,它有一个 sret 属性,在 LLVM 的文档中,对于 sret 的描述如下[3]:

sret:

This indicates that the pointer parameter specifies the address of a structure that is the return value of the function in the source program. This pointer must be guaranteed by the caller to be valid: loads and stores to the structure may be assumed by the callee not to trap and to be properly aligned. This is not a valid attribute for return values.

LLVM的Argument类中,提供了一个十分方便的API hasStructRetAttr,用于判断该参数是否是返回值,若它是返回值,则意味着第二个参数才是this。因而这里已经可以定位到this指针的类型信息,并且对于返回值为复杂结构的函数,也可一并将之dump出来。

 

再看第二点,仍然以 MediaDrmService 为例,在LLVM编译出来的 IR中,BpMediaDrmService 的 type 类型信息如下:

可看到它继承自 BpInterface与 RefBase,而BpInterface实际上是一个模板类,真正封装的是IMediaDrmService,这个类又是 IInterface 的子类。

在LLVM中,确定某一个类型是否是 IInterface 的子类仅须通过递归地确认它的subType的类名是否为IInterface即可,代码片段如下:

经过这两个操作,加上一些必要的过滤,即可找到并dump下来所有的Bp类及其成员函数。最终MediaDrmService的所有接口信息如下:

经过c++filt的name demangle 之后,可读性就非常强了。

 

4. 误报漏报分析

对于以 aidl 形式定义的 binder接口,在编译过程中同样也会在 out 目录下生成对应的cpp文件,llvm也会处理到这些文件,因而即使Bp类没有显式地声明也没有问题,如下,IPackageManagerNative 接口就是一个仅有aidl源文件的接口:

此外,BinderFinder 也可处理 Hybrid Interface的情况,例如 IProducerListener中的 hybrid 接口同样可以 dump出来。

在处理结果中得到的数据是这些:

综上,该工具暂未发现误报的情况。不过 BinderInterfaceFinder仅用于静态分析c++实现的binder接口,对于c++实现的binder应该是没有漏报的,虽然我无法证明这一点。由于 LLVM 不对 Java源代码作处理,因而暂无分析 Java实现的 binder接口的能力。

 

5. Binder关系网络恢复

Binder 的架构并不是扁平化的,Binder之间还存在着相互调用的关系,例如 IMediaDrmService 能够返回 IDrm 以及 ICrypto 两种子接口,子接口则意味着更深层次的攻击面,在客户端可以进一步进行调用与测试。

同时,有些函数中,还会将binder 作为参数传进去,例如 IMediaExtractorService 的 makeExtractor函数接收一个 IDataSource,这意味着我们可以自己实现一个 IDataSource 的Bn端,等待 media extractor service来请求。

不过并非所有作为参数传入的Binder都是 Bn 可控的,也有一些参数是out类型的参数,例如CamerService的 connect函数,最后一个参数 device 实则是一个作为返回值的out,用于在客户端接收一个子接口。

 

在Dump下来了所有这些函数之后,将它们的参数类型、返回值类型再链起来,即可恢复AOSP native Binder 的关系网络了,这部分通过一些基本的字符串处理即可完成。以下放出我恢复出来的局部图:

 

6. 参考链接

  1. https://ieeexplore.ieee.org/abstract/document/5482589
  2. https://groups.google.com/forum/#!searchin/android-llvm/repo$20init|sort:date/android-llvm/ngFN7_7nzsk/oUxcT2DqBwAJ
  3. https://llvm.org/docs/LangRef.html
  4. https://cs.android.com

 

没有评论:

发表评论