1. 概述2. AOSP加载LLVM PASS 环境搭建3. BinderInterfaceFinder3.1 Binder 接口特征分析3.2 Bp 类的识别4. 误报漏报分析5. Binder关系网络恢复6. 参考链接
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 文件中,这里我作出的修改:
x
root@ed19064f513f:/aosp/build/soong# git diff
diff --git a/cc/config/global.go b/cc/config/global.go
index 815c31d..f87ff96 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
"-g",
"-fno-strict-aliasing",
+ "-Xclang",
+ "-load",
+ "-Xclang",
+ "/share/llvm-android-10.0.0_r17/out/stage2/lib64/LLVMBinderInterfaceFinder.so",
}
如上,在 commonGlobalCflags 选项中添加这几项之后,即可在AOSP编译每一个 cc 文件的时候均加载起来我的 so。安卓10版本的 build 系统作了一些修复,如果是在旧版本的安卓里面,global cflag 是不允许接收不以"-"开头的参数的,因而还需要将一些 check patch掉才行。
此外,安卓所使用的 llvm 也是经过一定程度订制的,直接使用master分支或者任意一个llvm版本是行不通的,因为llvm在频繁更新,很多api可能都会发生变化,因而我的so应该与aosp所使用的llvm版本一致。这个版本号在哪里找?仍然是在这些复杂的build目录下,定义于 global.go 中:
x
root@ed19064f513f:/share/mount/aosp/build# grep -i clangdefaultversion -r .
./soong/cc/config/global.go: ClangDefaultVersion = "clang-r353983c"
./soong/cc/config/global.go: return ClangDefaultVersion
如上,我当前的 aosp 分支使用的是 "clang-r353983c",预先放好的 clang 编译工具链位于 prebuilts 目录下,不过仅有已经编译好二进制编译工具链。
xroot@ed19064f513f:/share/mount/aosp# ls prebuilts/clang/host/linux-x86/
Android.bp GCC_4_9_DEPRECATION.md clang-3289846 clang-r344140b clang-r349610 clang-r353983c clang-stable
若要基于这个版本编写自己的插件,还需要有 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 文件,如下:
xxxxxxxxxx
root@ed19064f513f:/share/mount/aosp/prebuilts/clang/host/linux-x86/clang-r353983c# ll
total 188
drwxrwxrwx 10 bobb bobb 4096 Jan 14 09:53 ./
drwxrwxrwx 18 bobb bobb 4096 Jan 14 09:53 ../
-rwxrwxrwx 1 bobb bobb 24 Jan 14 09:53 AndroidVersion.txt*
-rwxrwxrwx 1 bobb bobb 0 Jan 14 09:53 MODULE_LICENSE_BSD_LIKE*
-rwxrwxrwx 1 bobb bobb 0 Jan 14 09:53 MODULE_LICENSE_MIT*
-rwxrwxrwx 1 bobb bobb 130424 Jan 14 09:53 NOTICE*
drwxrwxrwx 2 bobb bobb 4096 Jan 14 09:53 bin/
drwxrwxrwx 8 bobb bobb 4096 Jan 14 09:53 include/
drwxrwxrwx 4 bobb bobb 4096 Jan 14 09:53 lib64/
drwxrwxrwx 2 bobb bobb 4096 Jan 14 09:53 libexec/
-rwxrwxrwx 1 bobb bobb 5244 Jan 14 09:53 manifest_5484270.xml* /// <=== 这一个
drwxrwxrwx 3 bobb bobb 4096 Jan 14 09:53 prebuilt_include/
drwxrwxrwx 6 bobb bobb 12288 Jan 14 09:53 runtimes_ndk_cxx/
drwxrwxrwx 7 bobb bobb 4096 Jan 14 09:53 share/
drwxrwxrwx 5 bobb bobb 4096 Jan 14 09:53 test/
至此,即可在AOSP中无痛使用自己编译的编译工具链了。
为AOSP编写LLVM PASS的方式并没有什么特殊的地方,在 llvm/lib/Transforms 中建文件夹写代码,而后在CMakeList.txt 中添加项即可。
xxxxxxxxxx
path: llvm-android-10.0.0_r17/toolchain/llvm/lib/Transforms
以下来看 BinderInterfaceFinder 这个 pass 的具体实现。
3. BinderInterfaceFinder
3.1 Binder 接口特征分析
由于一个 binder 接口的代码分为server端与client端,即Bn端与Bp端,Bn类仅实现一个onTransact函数,而Bp类会实现接口中定义的所有函数,因而分析过程中从Bp端入手会比较方便,以下看一个实例。
以 IMediaDrmService为例,IMediaDrmService 接口继承于 IInterface,这是Binder接口的重要特征。
x
> path: frameworks/av/drm/libmediadrm/include/mediadrm/IMediaDrmService.h
class IMediaDrmService: public IInterface
{
public:
DECLARE_META_INTERFACE(MediaDrmService);
virtual sp<ICrypto> makeCrypto() = 0;
virtual sp<IDrm> makeDrm() = 0;
};
BnMediaDrmService是 IMediaDrmService 的server端,它实现了onTransact函数,在其中根据 transaction code 的编码调用具体的实现代码,Bn所调用的makeDrm以及makeCrypto并不由Bn自己实现,而是由Bn的子类去完成,它只管 onTransact。
xxxxxxxxxx
status_t BnMediaDrmService::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch (code) {
case MAKE_CRYPTO: {
CHECK_INTERFACE(IMediaDrmService, data, reply);
sp<ICrypto> crypto = makeCrypto();
reply->writeStrongBinder(IInterface::asBinder(crypto));
return NO_ERROR;
} break;
case MAKE_DRM: {
CHECK_INTERFACE(IMediaDrmService, data, reply);
sp<IDrm> drm = makeDrm();
reply->writeStrongBinder(IInterface::asBinder(drm));
return NO_ERROR;
} break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
Bp类是这个binder接口的客户端,如下,它将 makeCrypto 以及 makeDrm 都实现了一遍,它们的功能是将需要的参数写入到Parcel中,然后通过 transact 传出去,并取回transact之后 Bn 端的返回值。
x
> path: frameworks/av/drm/libmediadrm/IMediaDrmService.cpp
class BpMediaDrmService: public BpInterface<IMediaDrmService>
{
public:
explicit BpMediaDrmService(const sp<IBinder>& impl)
: BpInterface<IMediaDrmService>(impl)
{
}
virtual sp<ICrypto> makeCrypto() {
Parcel data, reply;
data.writeInterfaceToken(IMediaDrmService::getInterfaceDescriptor());
remote()->transact(MAKE_CRYPTO, data, &reply);
return interface_cast<ICrypto>(reply.readStrongBinder());
}
virtual sp<IDrm> makeDrm() {
Parcel data, reply;
data.writeInterfaceToken(IMediaDrmService::getInterfaceDescriptor());
remote()->transact(MAKE_DRM, data, &reply);
return interface_cast<IDrm>(reply.readStrongBinder());
}
};
从静态分析的角度来讲,从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 形式如下:
x@_ZN7android17BpMediaDrmService10makeCryptoEv(%"class.android::sp.6"* noalias sret %agg.result, %"class.android::BpMediaDrmService"* %this) unnamed_addr #4 comdat align 2
所对应源代码中的声明:
xvirtual sp<ICrypto> makeCrypto() ;
它的第二个参数才是class.android::BpMediaDrmService
类型的this指针,第一个参数 agg.result 则是一个 class.android::sp.6
类型的指针,sp.6 类型对应的是 ICrypto 类型,即返回值信息。
xxxxxxxxxx
%"class.android::sp.6" = type { %"struct.android::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出来。
xxxxxxxxxx
47 Argument* pthis = arg_begin;
48 *has_sret = 0;
49 if ( pthis->hasStructRetAttr() ){
50 *has_sret = 1;
51 pthis++;
52 }
53 Type* pthis_type = pthis->getType();
再看第二点,仍然以 MediaDrmService 为例,在LLVM编译出来的 IR中,BpMediaDrmService 的 type 类型信息如下:
xxxxxxxxxx
%"class.android::BpMediaDrmService" = type
{
%"class.android::BpInterface.base",
%"class.android::RefBase"
}
%"class.android::BpInterface.base" = type
{
%"class.android::IMediaDrmService.base",
%"class.android::BpRefBase.base"
}
%"class.android::IMediaDrmService.base" = type
{
%"class.android::IInterface.base"
}
可看到它继承自 BpInterface与 RefBase,而BpInterface实际上是一个模板类,真正封装的是IMediaDrmService,这个类又是 IInterface 的子类。
在LLVM中,确定某一个类型是否是 IInterface 的子类仅须通过递归地确认它的subType的类名是否为IInterface即可,代码片段如下:
xxxxxxxxxx
18 bool BinderInterfaceFinder::isInterface(Type* type){
19 if( type->getTypeID() != Type::TypeID::StructTyID){
20 return false;
21 }
22 if( type->getStructName().equals(StringRef("class.android::IInterface.base"))){
23 return true;
24 }
25
26 for(Type::subtype_iterator sub_iter = type->subtype_begin(), sub_t_end = type->subtype_end(); sub_iter!=sub_t_end; sub_iter++){
27 Type* subType = *sub_iter;
28 if( subType->getTypeID() == Type::TypeID::StructTyID){
29 if (isInterface(subType)){
30 return true;
31 }
32 }
33 }
34 return false;
35 }
经过这两个操作,加上一些必要的过滤,即可找到并dump下来所有的Bp类及其成员函数。最终MediaDrmService的所有接口信息如下:
xframeworks/av/drm/libmediadrm/IMediaDrmService.cpp
_ZN7android17BpMediaDrmServiceC1ERKNS_2spINS_7IBinderEEE
_ZN7android17BpMediaDrmService10makeCryptoEv
retType:struct.android::ICrypto
_ZN7android17BpMediaDrmService7makeDrmEv
retType:struct.android::IDrm
经过c++filt的name demangle 之后,可读性就非常强了。
xframeworks/av/drm/libmediadrm/IMediaDrmService.cpp
android::BpMediaDrmService::BpMediaDrmService(android::sp<android::IBinder> const&)
android::BpMediaDrmService::makeCrypto()
retType:struct.android::ICrypto
android::BpMediaDrmService::makeDrm()
retType:struct.android::IDrm
4. 误报漏报分析
对于以 aidl 形式定义的 binder接口,在编译过程中同样也会在 out 目录下生成对应的cpp文件,llvm也会处理到这些文件,因而即使Bp类没有显式地声明也没有问题,如下,IPackageManagerNative 接口就是一个仅有aidl源文件的接口:
xxxxxxxxxx
out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_x86_core_shared_com.android.media/gen/aidl/frameworks/native/libs/binder/aidl
/android/content/pm/IPackageManagerNative.cpp
android::content::pm::BpPackageManagerNative::BpPackageManagerNative(android::sp<android::IBinder> const&)
android::content::pm::BpPackageManagerNative::BpPackageManagerNative(android::sp<android::IBinder> const&)
android::content::pm::BpPackageManagerNative::getNamesForUids(std::__1::vector<int, std::__1::allocator<int> > const&, std::__1::vector<std::__1::basi
c_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>,
std::__1::allocator<char> > > >*)
此外,BinderFinder 也可处理 Hybrid Interface的情况,例如 IProducerListener中的 hybrid 接口同样可以 dump出来。
class ProducerListener : public virtual RefBase
{
public:
ProducerListener() {}
virtual ~ProducerListener();
// onBufferReleased is called from IGraphicBufferConsumer::releaseBuffer to
// notify the producer that a new buffer is free and ready to be dequeued.
//
// This is called without any lock held and can be called concurrently by
// multiple threads.
virtual void onBufferReleased() = 0; // Asynchronous
virtual bool needsReleaseNotify() = 0;
// onBuffersFreed is called from IGraphicBufferConsumer::discardFreeBuffers
// to notify the producer that certain free buffers are discarded by the consumer.
virtual void onBuffersDiscarded(const std::vector<int32_t>& slots) = 0; // Asynchronous
};
class IProducerListener : public ProducerListener, public IInterface
{
public:
using HProducerListener1 =
::android::hardware::graphics::bufferqueue::V1_0::IProducerListener;
using HProducerListener2 =
::android::hardware::graphics::bufferqueue::V2_0::IProducerListener;
DECLARE_HYBRID_META_INTERFACE(
ProducerListener,
HProducerListener1,
HProducerListener2)
};
在处理结果中得到的数据是这些:
xframeworks/native/libs/gui/IProducerListener.cpp
android::BpProducerListener::onBufferReleased()
android::BpProducerListener::needsReleaseNotify()
android::HpInterface<android::BpProducerListener, android::hardware::graphics::bufferqueue::V1_0::utils::H2BProducerListener, android::hardware::graph
ics::bufferqueue::V2_0::utils::H2BProducerListener>::HpInterface(android::sp<android::IBinder> const&)
android::HpInterface<android::BpProducerListener, android::hardware::graphics::bufferqueue::V1_0::utils::H2BProducerListener, android::hardware::graph
ics::bufferqueue::V2_0::utils::H2BProducerListener>::getHalVariant() const
android::HpInterface<android::BpProducerListener, android::hardware::graphics::bufferqueue::V1_0::utils::H2BProducerListener, android::hardware::graph
ics::bufferqueue::V2_0::utils::H2BProducerListener>::onAsBinder()
android::BpProducerListener::BpProducerListener(android::sp<android::IBinder> const&)
android::HpInterface<android::BpProducerListener, android::hardware::graphics::bufferqueue::V1_0::utils::H2BProducerListener, android::hardware::graph
ics::bufferqueue::V2_0::utils::H2BProducerListener>::castFromHalBaseAndConvert(unsigned int, android::sp<android::hidl::base::V1_0::IBase> const&)
bool android::HpInterface<android::BpProducerListener, android::hardware::graphics::bufferqueue::V1_0::utils::H2BProducerListener, android::hardware::
graphics::bufferqueue::V2_0::utils::H2BProducerListener>::_castFromHalBaseAndConvert<2u>(unsigned int, android::sp<android::hidl::base::V1_0::IBase> c
onst&)
bool android::HpInterface<android::BpProducerListener, android::hardware::graphics::bufferqueue::V1_0::utils::H2BProducerListener, android::hardware::
graphics::bufferqueue::V2_0::utils::H2BProducerListener>::_castFromHalBaseAndConvert<1u>(unsigned int, android::sp<android::hidl::base::V1_0::IBase> c
onst&)
bool android::HpInterface<android::BpProducerListener, android::hardware::graphics::bufferqueue::V1_0::utils::H2BProducerListener, android::hardware::
graphics::bufferqueue::V2_0::utils::H2BProducerListener>::_castFromHalBaseAndConvert<0u>(unsigned int, android::sp<android::hidl::base::V1_0::IBase> c
onst&)
综上,该工具暂未发现误报的情况。不过 BinderInterfaceFinder仅用于静态分析c++实现的binder接口,对于c++实现的binder应该是没有漏报的,虽然我无法证明这一点。由于 LLVM 不对 Java源代码作处理,因而暂无分析 Java实现的 binder接口的能力。
5. Binder关系网络恢复
Binder 的架构并不是扁平化的,Binder之间还存在着相互调用的关系,例如 IMediaDrmService 能够返回 IDrm 以及 ICrypto 两种子接口,子接口则意味着更深层次的攻击面,在客户端可以进一步进行调用与测试。
class IMediaDrmService: public IInterface
{
public:
DECLARE_META_INTERFACE(MediaDrmService);
virtual sp<ICrypto> makeCrypto() = 0;
virtual sp<IDrm> makeDrm() = 0;
};
同时,有些函数中,还会将binder 作为参数传进去,例如 IMediaExtractorService 的 makeExtractor函数接收一个 IDataSource,这意味着我们可以自己实现一个 IDataSource 的Bn端,等待 media extractor service来请求。
xxxxxxxxxx
class IMediaExtractorService: public IInterface
{
public:
DECLARE_META_INTERFACE(MediaExtractorService);
virtual sp<IMediaExtractor> makeExtractor(const sp<IDataSource> &source, const char *mime) = 0;
virtual sp<IDataSource> makeIDataSource(int fd, int64_t offset, int64_t length) = 0;
virtual std::unordered_set<std::string> getSupportedTypes() = 0;
};
不过并非所有作为参数传入的Binder都是 Bn 可控的,也有一些参数是out类型的参数,例如CamerService的 connect函数,最后一个参数 device 实则是一个作为返回值的out,用于在客户端接收一个子接口。
virtual binder::Status connect(const sp<hardware::ICameraClient>& cameraClient,
int32_t cameraId, const String16& clientPackageName,
int32_t clientUid, int clientPid,
/*out*/
sp<hardware::ICamera>* device);
在Dump下来了所有这些函数之后,将它们的参数类型、返回值类型再链起来,即可恢复AOSP native Binder 的关系网络了,这部分通过一些基本的字符串处理即可完成。以下放出我恢复出来的局部图:
6. 参考链接
- https://ieeexplore.ieee.org/abstract/document/5482589
- https://groups.google.com/forum/#!searchin/android-llvm/repo$20init|sort:date/android-llvm/ngFN7_7nzsk/oUxcT2DqBwAJ
- https://llvm.org/docs/LangRef.html
- https://cs.android.com