概述
本文发表在NDSS21上,作者是德国亥姆霍兹信息安全中心(CISPA)的Abdallah Dawoud和Sven Bugiel。
自2011年至今,学术界对Android系统的Permission Mapping这一话题的研究从未停止过。但是除了这个话题的开山之作以外,其他工作无一例外使用的都是基于静态分析的技术。本文旨在将近年来日益发展壮大的动态测试工具及其思路,应用到这一领域,以弥补纯静态分析的不足。提出了一个叫Dymamo的动态测试工具,对Android Framework层的api进行Fuzz,从而建立Permission Mapping。基于这一工具得到的结果,作者将其于现有的来自Arcade的最新结果进行比对,从差异中发现各自的不足,并从而帮助其他工作更好的完善各自的工具。
简介
Permission Mapping
Permission Mapping即权限映射,在本文中是指将Android Framework层提供的系统API以及调用该API时所需要申请的权限建立映射。
Permission Mapping的意义有:
- 帮助开发者编写符合最小特权原则的app
- 可以用于恶意软件检测或者识别过量申请权限的app
- 可以识别安卓系统中自带的权限漏洞
Motivation
现有的工作基本上都是基于静态分析的,而静态分析尤其固有的缺陷,比如很难处理动态变量的值、IPC等。而目前的Permission Mapping结果几乎完全基于静态分析,这导致结果的不准确性,而对其他依赖于该结果的工作造成影响。因此作者认为有必要用动态测试的方法来重新审视这个结果。
Background
Android Framework API
所有已注册的系统服务都能够在ServiceManager类中被找到,作者基于这一事实来提取系统API的入口点。
Binder IPC
Binder是安卓系统特有的进程间通信机制,底层原理是使用内核内存贡献来在进程间通信。对于一些比较敏感的底层系统API,安卓系统通过Binder封装后对外提供一些High-Level的API用于调用,在调用时则会进行权限检查。
Permission
安卓的权限管理可以分为3类:
- UID检查: 只有指定UID的进程才能调用特定的API
- 跨用户检查:对于使用手机分身的情况,不同分身代表不同用户,不同用户之前权限有差异
- AppOps:权限申请(如相机权限)首先需要在Manifest中静态申请,而申请完成后的权限是否能够动态的调用则由AppOps进行管理
Research Questions
本文想要设计一个动态测试工具来为Android Framework层API建立权限映射,主要有以下几个Research Questions:
- RQ1: 如何识别这些API的入口并触发它们。难点在于这些API分散在不同的Service之中,并且可能分别由Java或者C++代码实现。
- RQ2: 如何为这些API构建输入。属于时Fuzz的经典问题。
- RQ3: 如何衡量动态测试的覆盖率。同样也属于时Fuzz的经典问题。
- RQ4: 如何检测出不同类型的权限管理。有些是集中管理的很好识别,有些代码甚至是inline的,不容易发现。
- RQ5: 如何构建反馈通道。即怎样将某个API的测试结果反馈给Fuzzer
- RQ6: 如何保留不同权限检查之间的关系和顺序。
Implementation
Overview
基于以上的Research Questions,作者设计的工具如下:
它有三个主要组成部分:
- Testing Service(TS): 这是作者编写的一个app,安装在测试机上,用于接收TM的控制指令并调用系统API。作者提到,现有的Fuzzer都是建立在最高特权的基础上的,因此对于权限检查的API测试意义不大。
- Instrumentation Server(IS): 本质上是个Frida Server,可以对测试的API以及权限检查相关函数进行hook,打印调用栈并上报给TM
- Testing Manager(TM): 本质上是个Frida Client,运行在PC端,起到统筹全局的作用,可以为每个测试的API生成输入,发送给TS进行API调用,并接收由IS中收集到的调用结果反馈,通过Analyser Module来分析覆盖率情况,控制当前的测试状态。
Collecting Public APIs
对于如何找到系统API的问题(RQ1),作者利用ServiceManager会维护所有注册的系统Service的这一事实,利用Java反射来从ServiceManager中获取所有能够找到的Service的Handle,并将其强转为对应Service的Proxy对象,在这些对象中就能找到这个Service的所有API的方法签名了。
在测试这些API时,作者采用多台设备并行的方式进行,每台设备一次只测试一个API。
Generating Input
对于如何生成输入的问题(RQ2),作者为Java基本类型和Android基本类型预定义了一些输入,其中部分输入由源码静态分析其入参名称得来。而对于复杂类型,作者使用递归的方式完成,通过为其传入基本类型来调用其构造函数从而生成输入。
Measuring Coverage
对于如何衡量覆盖率的问题(RQ3),作者使用了一个叫WALA的工具,来对每个API进行可达性分析,对于每个API可以得到一个有限的方法集合,调用API可以最终到达这些方法。通过Hook这些方法,当其被调用时打印调用栈,如果确实是由该API所触发并且调用者为TS时(用于排除噪声),统计该Trace。最后覆盖率的计算公式为Unique Trace的数量比上集合中的方法数量。
DYNAMO’s Testing Strategies
对于如何检测不同类型的权限检测的问题(RQ4),作者预定义了多种测试策略,每种策略旨在发现不同类型的安全检测。工作流程大致如下:
就是循环遍历不同inputs和Strategies,然后遇到安全检查没通过就hook一下尝试绕过。具体的case如下:
对于特定权限检查的情况(图中INTERACT_ACROSS_USERS)会有统一的函数完成(checkPermission函数),通过hook就能知道是否触发了这种检查以及具体的参数类型。
对于inline检查UID的情况,作者通过Hook Binder.getCallingUid函数来不断变更自己的UID,如果发现某一次变更后通过了权限检查,则说明存在inline UID检查。
Instrumenting Targets
对于构建反馈通道的问题(RQ5),作者使用了Frida动态Hook框架优点是能够兼容不同的系统,而无需修改安卓源码,能够满足对一些闭源的OEM厂商的测试需求。
Modeling of Permission Mapping
最后是如何为Permission Mapping建模的问题(RQ6),作者想要得到下图中List2中的结果作为输出。
作者可以使用RQ4中的方法来得到具体UID值的检查以及具体权限检查的两种情况,但是对于UID是否等于入参的情况,作者通过不断变更入参的方式来检查。
Evaluation
Evaluation environment
Android versions
- Android 6, 8.1, 10, and 11 of vanilla Android
- other vendor images
hardware devices
- Pixel 4, Nexus 5, and One+
Emulators
- Cuttlefish and Android Studio emulators
- running different CPU architectures(x86, x86_64, arm, and arm64)
Evaluating Previous Permission Mappings
与Arcade的对比结果大致如上图所示
Limatation
- 部分Service并不在ServiceManager中,在API提取过程中被丢失
- 人工预定义的测试策略是不完美的,容易忽视特定的情况
- 对于FN需要人工验证,无法在大数据集上统计出正确的FN
- 动态测试非常耗时
- 要求手机ROOT或者系统以Debug Mode编译