2020年6月9日星期二

Why Memory Sanitizer support x86_64 only

> MemorySanitizer run-time library shares much common code with AddressSanitizer and ThreadSanitizer libraries. At startup it makes the lower protected area inaccessible, and maps Shadow and, optionally, Origin areas. MemorySanitizer is currently limited to Linux / x86 64, and these memory ranges (as specified in Figure 1) are always available at startup, provided that the application is linked as PIE (position-independent executable), and address space layout randomization (ASLR) is enabled.[1]


[1] https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/43308.pdf

2020年4月26日星期日

在AOSP上跑AFL的解决方案

在AOSP中跑AFL的解决方案

1. TL;DR

 

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++ 对其进行插桩。

也就是文档中给出的经典使用方法:

插桩的目的是为了对目标程序进行必要的改装,改装的方面有这么几个:

  • 插入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 规则简洁如下:

此外,AOSP的build系统支持一个叫做 SANITIZE_TARGET 的环境变量,当在此环境变量下加一个 coverage的时候,即可在编译过程中,自动地将 -fsantize-coverage=trace-pc-guard 选项开启。因此,编译命令简洁如下:

ps: coverage选项会和shadow call stack 缓解机制产生冲突,而scs在android10中某些项目中有启用,若仅用于测试目的,可将之手动关闭。

 

至此,我好了。

 

4. 参考链接

  1. https://github.com/google/AFL/commit/e75894a889fe854c02b9435186bd1e2927d6d490
  2. https://github.com/google/AFL/commit/077d73f7fc12b8b64e71befdb81744056640eaf1
  3. https://elinux.org/Android_Kernel_Features#ashmem
  4. https://groups.google.com/forum/#!msg/afl-users/UL46o4kTeFw/CYWKLXB0DgAJ
  5. https://github.com/ele7enxxh/android-afl
  6. https://github.com/google/AFL/issues/61
  7. https://source.android.com/setup/build
  8. https://ci.android.com/builds/submitted/6430522/linux/latest/view/soong_build.html
  9. https://gist.github.com/JoeyJiao/cca3bc6c440f7000b4969bb1ab4ccfac
  10. https://clang.llvm.org/docs/SanitizerCoverage.html
  11. https://github.com/google/AFL/tree/master/llvm_mode

 

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