Search
🎨

FANS: Fuzzing Android Native System Services via Automated Interface Analysis

Created
2023/02/10 13:23
Tags
Android
Fuzzing
Conference
Usenix
Published
2020

0. 소개

Android Native System Service는 유저 어플리케이션에 있어 핵심적인 지원 및 권한을 제공한다. 그러나 2019년 10월까지 안드로이드 시스템은 수백개의 취약점이 보고되었으며, 이는 대부분 C++로 작성된 native system service에 존재하고 있었다. 그리고 native system에서 취약점을 발견할 경우 privilege escalation 등의 파급력을 가지고 있기 때문에 이를 타겟으로 취약점 분석을 진행하였다.
FANS는 Android Native System Services를 타겟으로 만든 퍼징 인터페이스이다. Android Native System Service를 타겟으로 잡았을 때는 크게 3가지 문제가 존재한다.
IPC 매커니즘을 통한 communication 진행
인터페이스 모델을 만족하는 input값 (semantically-correct input generation)
변수 종속성 및 인터페이스 종속성을 충족하는 test case 생성
FANS는 이러한 어려움을 모두 해결하였으며, 모든 과정을 자동화하여 prototype까지 github에 공개하였다.

1. 배경지식

Android Native System

우선 안드로이드의 서비스는 크게 Java system services와 native service system으로 나뉜다. Java로 주로 구현되어있는 경우 Java system service, C++가 주가 되어 구현된 경우 native service system이라고 칭한다.
본 연구에서는 Android native system을, 그 중에서도 service manager에 register 되어있고 normal domain에 속해있는 시스템을 대상으로 연구를 진행하였다.

Application-Service Communication Model

Android안에서는 특정한 방법으로 system service가 사용이 되는데, 이 IPC를 착안하여 모든 system service에 적용시킬 수 있다.

2. 논문정리

Challenges

Multi-Level Interface Recognition
Service Manager에 register된 top-level interface를 제외하고도, 또 다른 interface가 호출하는 방식의 multi-level interface가 존재한다. 따라서 정말 모든 Android native system을 동적분석 하기 위해서는 이런 interface를 모두 fuzzing scope로 파악해야한다.
이와 더불어 AIDL(Android Interface Definition Language)로 컴파일 과정에서 동적으로 생겨나는 interface도 존재하기 때문에, 이러한 부분 또한 고려해야 한다.
Interface Model Extraction
각각 interface를 추출한 뒤에도, 어떤 transaction이 일어나고 data를 주어야 할지를 파악해야 한다. 이를 수동적으로 진행하는 것은 비효율적이고 자동적으로 이를 추출하는 것 또한 어려운 일이기 때문에 이에 대한 고려가 필요하다.
Semantically-correct Input Generation
Android는 기본적으로 다양한 sanity check를 진행하기 때문에, 이를 만족하는 값을 input으로 주어야 비로소 의미가 있는 fuzzing을 진행할 수 있다. 따라서 좋은 결과물을 도출하기 위해서는 이러한 semantic requirements를 파악하는 것이 필수적이다.

Overview

Interface Collector: 타겟 서비스의 interface를 뽑아낸다. 단순히 top-level interface 뿐만아니라 재귀적으로 호출해야 접근할 수 있는 multi-level interface까지 뽑아낸다.
Interface Model Extractor: Interface를 추출한 뒤로는 그에 해당하는 input 및 output format을 추출해야한다. 특히 Android는 자체적으로 sanity check를 많이 진행하기 때문에 어떤 데이터 타입인지, 어떤 조건을 가지고 있는지를 파악하는 것이 필수적이다.
Dependency Inferer: Intra-transaction과 inter-transaction과 변수 dependency등등 또한 파악해야한다.
Fuzzer Engine: Fuzzer Manager를 통해 host와 mobile phone사이의 데이터를 동기화하는 역할을 한다.

Design & Implementation

FANS는 AFL같은 기존 fuzzer를 사용하지 않고, C++와 Python으로 처음부터 모든 것을 제작했다. AFL을 사용하지 않은 이유는 총 3가지이다.
1.
기존의 퍼저를 안드로이드 시스템에 조정하는 것이 많은 노력이 든다.
2.
AFL기반의 퍼저들은 하나의 서비스를 집중적으로 테스팅하는데 특화되어있다. 그러나 ‘서비스’의 개념이 도입된 안드로이드 시스템에는 적합하지 않다.
3.
AFL은 IPC 기반의 서비스를 포함하여 서비스 기반의 어플리케이션을 퍼징하는데 적합하지 않다.

Interface Collector

Design
앞서 말했듯 interface를 추출할 때 multi-level interface까지 모두 추출하는 것이 퍼저의 coverage 측면에서 매우 중요하다. 모든 interface는 onTransact method에 의해 호출된다. 그러나 AOSP의 C/C++파일에서 직접 찾지 않고, AOSP의 컴파일 커맨드에 포함된 파일만을 대상으로 조사를 하였다. 이 방법을 택한 이유는 AIDL을 통해 동적으로(dynamically) 생성되는 인터페이스만 추출하도록 설계하였다.
Implementation
이는 AOSP codebase를 컴파일하면서 모든 컴파일 커맨드를 기록하였다. 이후, 본 커맨드에서 사용된 파일들을 스캔하였다. 이는 Python을 통해 쉽게 구현할 수 있었다.

Interface Model Extractor

Design
Implementation
AST를 통해 interface model을 추출하면서, 컴파일 커맨드를 ccl 커맨드로 바꿔주어야 한다. AST에 대해서는 대략적인 분할을 진행하며, input과 output값에 관련된 값들만 추출한다. 그 이후로는 Fuzzer Machine이 쉽게 이를 이용할 수 있도록 JSON 형태로 전처리를 진행한다.

Dependency Inferer

Design
Interface Model을 추출한 뒤에는, 서로의 dependency를 파악하여야 한다. 여기에서 dependency란, multi-level interface에서 특정 interface가 호출을 해야지 실행되는 interface dependency와 변수와 transaction 사이에서 발생하는 variable dependency로 나뉜다.
Interface dependency에서는 각각 generation dependency, 그리고 use dependency로 나누어 구분하였다. 그리고 variable dependency의 경우 intra-transaction dependency와 inter-transaction dependency로 나누어 진행하고 있다. 특히 intra-transaction dependency의 알고리즘은 아래와 같다.
Implementation
Interface Model Extractor를 통해 전해진 JSON파일을 통해 구조를 파악하며 필요한 dependency를 파악한다. 이는 위의 Algorithm 1을 통해 파악된다.

Fuzzer Machine

Design
Dependency까지 파악이 완료되면 비로소 퍼징을 진행할 수 있다. 우선 퍼저는 fuzzer 바이너리, interface model, 그리고 dependencies의 sync를 맞춘다. 이후 fuzzer는 test case를 생성한다.
특히 Android native system을 퍼징할 때는 transaction code기반으로 퍼징을 진행하게 된다. 주로 transaction은 랜덤하게 하나가 진행되며, 이후에 유발되는 interface를 따라가게 된다. 단, transaction을 생성할 때는 다양한 sanity check나 constraint를 확인하여 생성한다.
Implementation
Fuzzer Machine은 여러개의 핸드폰에서 fuzzing을 진행할 수 있도록 구현하였다. 이를 위해 AOSP를 ASan 옵션과 같이 빌드하였고, fuzzer는 C++을 통해 구현되었다. 참고로 몇몇 Android native service는 RPC request시 권한을 확인하기 때문에 root 권한으로 실행하였다.
마지막으로 크래시가 발생하는 경우, 이를 분석하기 위해 Android 자체의 logcat 도구와 /data/tombstones/에 기록된 크래시 로그를 사용하였다.

Evaluation (+ Case Studies)

Evaluation
크게 다음과 같이 가지 평가지표를 통해 평가하였다. 평가 기준은 다음과 같다.
PC: Ubuntu 18.04 with i9-9900K CPU, 32 GB memory, 2.5 T SSD
Android test devices: Pixel * 1, Pixel 2XL * 4, and Pixel 3XL * 1 (AOSP build number PQ3A.190801.002, i.e., android-9.0.0_r46)
1.
얼마나 많은 interface를 발견했는가? 그리고 그 사이의 관계가 무엇인가?
우선 Android Native System에서 초점을 맞추어 연구를 진행한 것은 FANS가 유일하다. 또한 Android Native System안에서 발견된 Interface들을 보면, 약 37%가 multi-level인 것을 확인할 수 있다. 이를 통해 단순히 service manager에 register된 interface 외에도 탐구해야 할 필요성이 부각된다.
앞서 언급했듯, FANS는 multi-level interfaces에 대해서 관찰하고 탐구한다. 아래 Figure 4의 경우 가장 깊은 depth를 가지고 있는 IMemory interface의 그래프이다. 이 경우 총 5개의 multi-layer를 가지고 있으며, 다양한 interface가 IMemory를 호출할 수 있다는 것을 알 수 있다. 이를 기반으로 보았을 때 다양한 fuzzing path를 확보할 수 있다.
2.
추출된 interface model은 어떤 모습인가? 모델이 완전하고 구체적인가?
FANS는 기본적으로 AOSP를 컴파일 한 뒤, interface의 관계를 파악하여 interface model을 추출한다. 이를 기반으로 추출하였기에 top-level transaction과 multi-level transaction을 모두 추출해낼 수 있었다.
Interface 종류에 이어 변수 또한 추출해내었다. Android native system은 많은 조건을 통해서 처리가 이루어지기 때문에 특정 조건을 만족해야 하는 변수인지 확인하는 과정이 필요하다. 분석 결과 sequential variable, conditional variable, 그리고 loop variable로 나뉘는 것을 확인할 수 있었고 각 경우의 특징을 추출하였다. 그에 대한 내용은 다음과 같다.
이러한 결과에 대한 ground truth가 따로 존재하지 않기 때문에, 자체적으로 랜덤한 10개의 interface를 선정하여 잘 추출되었는지 확인하였다. 완벽하지는 않지만, 취약점을 찾기에는 충분했고, 관련 연구 중 이러한 접근을 처음 시도한 연구이기에 충분히 의미가 있다고 주장한다.
3.
FANS가 Android native system 서비스에서 취약점을 찾는데 얼마나 효율적인가?
비록 잦은 크래시 및 크래시로 인한 수동 복구가 필요했지만, 이를 감수하고도 총 30개의 다른 취약점을 발견할 수 있었다. 총 22개의 Android native system안의 취약점, 5개의 라이브러리 취약점(libcutils.so, libutils.so, libgui.so), 그리고 3개의 Linux system 요소의 취약점을 발견하였다.
전체 취약점 리스트는 다음과 같다. 이외로 138개의 Java exception을 발견하였으며, 이에 대해서는 추가 분석을 진행중이다.
Case Studies
Case Study 1: new_capacity overflow Inside readVector of IDrm
BnDrm::readVector함수에서 버퍼를 할당하기 위해 insertAt를 invoke하는데, 여기에서 할당될 사이즈를 정하는 data 안의 size 벡터에 대한 검증과정이 존재하지 않는다. 따라서 datasize를 -1 등으로 설정하는 경우, 단순히 integer underflow를 기반으로, 리소스 과다사용 등을 통한 DoS 등의 공격으로도 발전시킬 수 있다. 이때 FANS에서는 -1과 같은 값들을 자동적으로 확인하고 있다.
IPC 안에서 버퍼 할당은 매우 중요한 부분이지만, 이 case와 같이 사이즈 체크를 하지 않는 경우가 종종 있다. 또한 문제가 되었던 BnDrm 객체의 경우, 해당 객체 안에서만 해도 readVector가 30번 이상 호출되었다. 이를 막기 위해서는 입력값의 크기를 검증하는 Parcel::readByteVector를 사용하는 것이 바람직하다.
Case Study 2: Out-of-bound Access Inside informAllUidData of statsd
statsd는 Android 9에서의 daemon이다. 이 안에서 Call::INFORMALLUIDDATA에서 statsddata에서 int32_t, int64_t, 그리고 ::android::String16인 값들을 역직렬화(deserialize)한다. 이 정보들은 updateMap에서 처리하게 된다. updateMap에서는 총 3개의 벡터의 길이가 같음을 추정하고 하나의 uid값 만을 기반으로 loop의 길이를 정한다. 즉, 3개의 벡터 중 하나라도 uid값을 더 길게 설정하는 경우, Out-of-Bound 취약점이 발생하게 된다. FANS는 생성된 AST를 통해 이러한 자료형이 다르다는 것을 파악하였으며, 이를 기반으로 해당 취약점을 발견해낼 수 있었다.
본 버그의 경우 같은 인덱스를 다른 vector에 적용시켜서 OOB 취약점이 발생하였다. 이에 대해서는 구글이 CVE 발급을 완료하였고(CVE-2019-2088), 자체적으로 fix하였기 때문에 추가적인 mitigation을 제안하지 않았다.
Case Study 3: Stack Overflow Inside ip(6)tablesrestore
ip(6)tables-restore 바이너리에서 stack overflow가 발생한다. 이는 직접적으로 Android Native System에 연관되어있지 않기 때문에 직접적으로 취약점이 발현되지는 않는다. 결론적으로 Call::WAKEUPADDINTERFACEwakeupAddInterfaceadd_param_to_argv 의 형태로 넘어가서 취약점이 발현된다.
비록 FANS는 Android native system만을 타겟으로 했지만, 결론적으로 Linux의 취약점을 찾아내었다. FANS는 리눅스의 요소들을 퍼징하는 새로운 방법임을 시사한다. 본 취약점은 CVE-2019-11360을 받았다.

Discussions

Interface Model Accuracy: 현재 interface model이 완벽하지는 않음. 이를 개선시킬 경우 전반적인 성능이 좋아질 것으로 예상한다.
Coverage Guided Fuzzing: FANS는 Coverage Guided Fuzzing을 택하고 있지 않다. 따라서 Coverage Guided Fuzzing을 FANS에 접목할 경우 훨씬 더 좋은 성과를 얻을 수 있을 것이다.
Fuzzing Efficiency: daemon이나 permission관련 이슈때문에 root권한으로 퍼징을 진행하고 있다. 이때 10분에 한번씩 수동으로 재부팅을 해주어야 하기 때문에 현재로서는 매우 비효율적이다.
Interface-based Fuzzing in Android: 본 연구에서는 normal domain의 native system에서만 주목했지만, 다른 domain(vendor domain, hardware domain)의 interface도 비슷한 면을 가지고 있기 때문에 본 연구가 좋은 자료로 쓰일 수 있을 것으로 예상된다.

Conclusion

본 연구팀은 Android native system을 퍼징할 수 있는 프레임워크인 FANS를 개발하였다. FANS는 Android 시스템 퍼징의 문제를 트랜잭션을 자동 생성 및 인터페이스 호출을 통해 타파한다. 또한 인터페이스 모델이 가변 패턴, 유형 별명 및 가변 종속성의 세 가지 측면에서 매우 정교하다는 것을 알 수 있다.
나아가 FANS를 기반으로 30일간 6개의 안드로이드 디바이스를 대상으로 퍼징을 진행하였으며 20개의 취약점을 인정받을 수 있었다.

3. Reference

Other Android fuzzing solutions

Andoroid 전반적인 이야기