2020年6月9日星期二
Why Memory Sanitizer support x86_64 only
[1] https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/43308.pdf
2020年4月26日星期日
在AOSP上跑AFL的解决方案
1. TL;DR
xxxxxxxxxx
1<= clone afl
2$ git clone https://github.com/google/AFL
3$ mmm AFL
4
5<= clone 测试程序
6$ git clone https://github.com/xiangxiaobo/afl-android-fuzzme
7$ SANITIZE_TARGET="address coverage" mmm afl-android-fuzzme
8
9<= push 到安卓设备上
10$ adb push $OUT/system/bin/afl-fuzz /data/local/tmp
11$ adb push $OUT/system/bin/afl-fuzzme /data/local/tmp
12$ adb shell
13$ cd /data/local/tmp
14
15<= 直接fuzz
16$ mkdir in
17$ echo SEED > in/SEED
18$ ./afl-fuzz -i in -o out -m none -- ./afl-fuzzme @@
19<=== wait for crash
2. 背景
大约在2019年11、12月的时候,开发者 JoeyJiao 先后给 AFL 提交了两份 commit [1][2]并被AFL merge到了master分支。从此AFL终于有了官方的Android平台支持。
原生的AFL无法直接在安卓平台运行的原因是共享内存机制的差异。AFL使用的是shmat, shmget 等调用来完成内存的映射等操作,这段共享内存会被直接用于存放收集到的边路径覆盖信息并与afl-fuzz主程序进行共享,而安卓系统使用的是bionic libc,并未提供shmat等调用,而是转而使用匿名共享内存ashmem[3]。
在此之前,也有人对AFL on Android进行过适配,并且都很好用,我所知最早的是2015年Adrian在afl-users论坛中贴出来的patch文件[4],而后2016年又有 ele7enxxh 在github中放出来的android-afl。不过Android 7.0之后,随着AOSP的编译方式已经由传统的make && Android.mk全面转向 soong编译系统 && Android.bp,如今直接用它们对AOSP中的代码进行Fuzz总觉得不那么顺手。
在JoeyJiao的两份提交里,首先是给AFL添加了一个 android-ashmem.h 文件,用于解决共享内存的问题,而后又提供了一个Android.bp文件,可直接使用mmm/mm/make afl-fuzz等方式将之编译成可在安卓设备上运行的可执行文件。不过关于如何具体在安卓上使用的文档并未给出,当然我们也可以在AFL的issue里看到sakura大佬的提问[6],JoeyJiao 为此也给出一个简便的guide。
在这份 guide里,对于 Android.mk 项目的支持很完善,因为mk格式的编译脚本本来就支持 LOCAL_CC, LOCAL_CXX,可以直接定义编译此工程所使用的clang/clang++的路径,不过对于An droid.bp项目,由于没法自定义clang/clang++(我又翻了一遍soong的文档以及源代码,仍未找到能自定义的地方[7][[8]),因此就需要去全局地改动 build/soong 目录下的go文件了,这种方式看起来可能不够优雅(似乎在此issue下ele7enxxh也这么觉得),Joey为此也慷慨地给出了他的patch[9]。至此,已经扫清了全部障碍。
由于某些原因,我也动过build/soong中的内容,每次动的时侯,再次编译aosp中的project就会全盘重来,所以改build中的go脚本我内心是隐隐抗拒的,毕竟,出不出得来洞是一时的事,追求极致优雅是一辈子的事情。
3. AFL & afl-clang-fast
3.1 afl-gcc
通常来讲,如要使用 AFL 对目标进行Fuzz,首先要使用 afl-gcc/afl-g++ 对其进行插桩。
也就是文档中给出的经典使用方法:
x1CC=afl-gcc ./configure
插桩的目的是为了对目标程序进行必要的改装,改装的方面有这么几个:
- 插入fork server
- 映射共享内存
- 覆盖统计插桩
其中fork server是AFL中一个十分精妙的设计,可用于减少每次都重启目标进程导致的开销,从而大大提高fuzz的效率,当然,如果显式地使用 NO_FORKSERVER 选项,那么第一项也可以去掉;映射的共享内存用于存储运行过程中收集到的路径信息,反馈给fuzz主进程以进行决策;而在每一个基本块都插入的覆盖统计函数(__afl_maybe_log)用于记录程序的运行状态。
afl-gcc 的源码中,做的事情有三个
- 找到编译所需的可执行文件的路径(find_as)
- 修改编译参数(edit_params)
- 重新执行真正的gcc (execvp)
在afl-gcc执行时,会使用一个自定义的汇编器 afl-as 对源代码进行汇编,afl-as 会向程序中插入一大段的汇编代码(位于 afl-as.h 文件中)以完成上述的插桩操作。也正是因为插入的是汇编代码,所以早期的AFL对arm平台的支持也不佳。
3.2 AFL LLVM mode -- with LLVM Pass
除了GCC外,AFL还支持LLVM模式,在此模式下,仍然要对目标程序进行 3.1 章节中提到的三个方面的改装。
AFL的llvm_mode 下有一个 afl-llvm-pass.so.cc 的文件,这部分代码用于在clang/clang++编译源代码的时候,加载一个定义好的 llvm pass,对每个基本块都插入覆盖统计的代码。
LLVM Pass的插桩模式是在它翻译成IR中间语言之后进行的,因此也就不用再对每个平台都进行汇编的手写工作了。
3.3 AFL LLVM mode -- without LLVM Pass
路径的统计不只是AFL在做,LLVM后来也有了自己的 sanitizer coverage[10],如果在编译的时候,加上了 -fsanitize-coverage=trace-pc-guard
选项,那么编译器会在模块加载时自动添加一个 __sanitizer_cov_trace_pc_guard_init
函数,并在每个基本块的起始处都插入一个 __sanitizer_cov_trace_pc_guard(&guard_variable)
函数,从而自动地统计程序走过的路径。
sanitizer coverage 本来是结合 __sanitizer_cov_reset
以及 __sanitizer_cov_dump
来使用的,在运行时,程序会链接到 LLVM compiler_rt 中的runtime库进行覆盖统计。
而AFL的方法是自己在 afl-llvm-rt.o.c
里面重写了__sanitizer_cov_trace_pc_guard_init
以及 __sanitizer_cov_trace_pc_guard
函数,相当于接管了LLVM的 runtime库,以此将它变成AFL自己的路径覆盖统计器。不过这种方法也会带来一定的效率上的损失,因为 3.2 中插入的指令是 load 等基础的指令,而 trace-pc-guard 是通过插入一个 call 来完成的覆盖统计,因此据官方统计,大概会有20%左右的效率损耗[11]。
在 afl-llvm-rt 里面,有一个带 constructor 属性的 __afl_auto_init
函数。这个函数会在程序的 main 函数执行之前就执行,在这里面能够完成包括 fork server 的实现与交互、共享内存的映射、以及上文提到的两个函数的重写(用于路径的统计)等操作,加上由 trace-pc-guard 选项所带来的插桩功能,已经基本完成了需要对目标程序实施的所有改造操作。于是我再看了一下 afl-clang-fast 中的代码,已经可以确定,这个clang wrapper它可有可无。
JoeyJiao 在给 afl-llvm-rt 编写 bp 规则的时候,是将它变成了一个静态链接库,所以,要对AOSP中的project进行Fuzz,直接链这个静态库就可以了,不需要对编译过程中所使用的 clang/clang++进行任何的修改或替换操作。
也即 ... 目标程序的 build 规则简洁如下:
xxxxxxxxxx
1 cc_binary{
2 name:"afl-fuzzme",
3 srcs:[
4 "main.c",
5 ],
6 static_libs:[
7 "afl-llvm-rt", //// <=== 唯一要加的东西
8 ],
9 }
此外,AOSP的build系统支持一个叫做 SANITIZE_TARGET 的环境变量,当在此环境变量下加一个 coverage的时候,即可在编译过程中,自动地将 -fsantize-coverage=trace-pc-guard
选项开启。因此,编译命令简洁如下:
1SANITIZE_TARGET="address coverage" mmm afl-android-fuzzme
ps: coverage选项会和shadow call stack 缓解机制产生冲突,而scs在android10中某些项目中有启用,若仅用于测试目的,可将之手动关闭。
至此,我好了。
4. 参考链接
- https://github.com/google/AFL/commit/e75894a889fe854c02b9435186bd1e2927d6d490
- https://github.com/google/AFL/commit/077d73f7fc12b8b64e71befdb81744056640eaf1
- https://elinux.org/Android_Kernel_Features#ashmem
- https://groups.google.com/forum/#!msg/afl-users/UL46o4kTeFw/CYWKLXB0DgAJ
- https://github.com/ele7enxxh/android-afl
- https://github.com/google/AFL/issues/61
- https://source.android.com/setup/build
- https://ci.android.com/builds/submitted/6430522/linux/latest/view/soong_build.html
- https://gist.github.com/JoeyJiao/cca3bc6c440f7000b4969bb1ab4ccfac
- https://clang.llvm.org/docs/SanitizerCoverage.html
- https://github.com/google/AFL/tree/master/llvm_mode
2020年3月22日星期日
使用LLVM Dump 安卓中的Binder接口
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
@@ -46,6 +46,10 @@ var (
"-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