Notice
Recent Posts
Recent Comments
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

topcue

[논문 리뷰] DIFUZE : Interface Aware Fuzzing for Kernel Drivers 본문

논문 리뷰

[논문 리뷰] DIFUZE : Interface Aware Fuzzing for Kernel Drivers

topcue 2021. 2. 22. 11:10

[논문 리뷰] DIFUZE : Interface Aware Fuzzing for Kernel Drivers

원문 : https://acmccs.github.io/papers/p2123-corinaA.pdf


DIFUZE 논문에서는 UNIX 계열의 커널 드라이버퍼징하기 위한 방법을 제시한다.

DIFUZE는 그 자체로 완전한 퍼저는 아니고 다른 퍼징 엔진을 필요로 한다.

논문 저자는 MANGO라는 prototype fuzzer를 제시하고 syzkaller와 비교한다.

본 논문의 기여는 다음과 같다.

  • 자동으로 드라이버를 복구하는 interface-aware fuzzing 방법 제시.
  • interface-aware fuzzing이 커널 드라이버를 퍼징하는데 효과적임을 보임.
  • 최신 안드로이드 모바일 기기 7개를 분석하여 32개의 unkown 취약점을 발견하였다.

[목차]


Abstract

device driver(이하 장치 드라이버)는 하드 디스크, 프린터, 디지털카메라, 블루투스 스피커 등 물리적 장치에서 작업을 처리하기 위한 최신 UNIX 계열 시스템에서 필수적이다. 특히 모바일 기기에서 새로운 하드웨어가 급증하면서, 시스템 커널의 장치 드라이버가 폭발적으로 증가하고 있다. 이런 드라이버는 third-party 개발자가 제공하는데 이는 보안상 취약하고 적절한 검증도 부족하다. 장치 드라이버의 복잡한 입력 데이터 구조체는 fuzz testing과 같은 기존의 분석 도구의 효율을 저하하며 커널 드라이버 보안 관련 연구는 지금까지 많지 않았다.

본 논문에서는 유효한 입력을 자동으로 생성하고 커널 드라이버의 실행을 트리거 하는 인터페이스 인식(:interface-aware) 퍼저인 DIFUZE를 소개한다. 정적 분석을 활용하여 userspace에서 잘 구조화된(:correctly-structured) 입력을 생성 하여 커널 드라이버를 탐색한다. DIFUZE는 드라이버 핸들러 식별부터 장치 파일명(device file name) 매핑, 복잡한 인자 인스턴스 구성까지 완전히 자동화했다. 우리의 접근 방식으로 7개의 최신 스마트폰을 평가했다. 그 결과 커널 드라이버 버그를 효과적으로 식별하고 임의 코드 실행(arbitrary code execution)을 포함한 32개의 알려지지 않은(unknown) 취약점을 발견하면서 DIFUZE의 성능을 입증했다.


1. Introduction

금융 거래, 의사소통 등 다양한 기능을 가지고 있는 스마트폰과 같은 모바일 기기는 현대 생활에 중요하다. 특히 개인적으로 또는 상업적인 목적뿐 아니라 정부 활동을 용이하게 하기도 한다.

이렇게 중요한 기기를 장악하면 공격자는 막대한 악영향을 끼칠 수 있게 된다. 그래서 스마트폰의 보안을 보장하기 위해 많은 노력을 기울이고 있다. 스마트폰 보안은 userspace 어플의 여러 mitigation(ASLR, DEP, SELinux 등)을 활용하고 개발의 첫 번째 목표를 보안으로 설정함으로써 구성한 복잡한 sandbox를 통해 보장된다. 하지만 모바일 기기의 커널에 여전히 보안 약점이 존재한다.

여러 mitigation을 사용할 수 있는 userspace의 어플과는 달리, 최신 OS의 커널은 사용 가능한 보호 기법이 있음에도 상대적으로 공격에 취약하다. 그래서 userspace 어플의 취약점이 더 희귀하고 공격자는 커널 취약점을 노린다. 예를 들어 안드로이드 커널 취약점이 최근 3년간 4%(2014년)에서 39%(2016년)까지 증가했다. 이는 커널 버그를 찾아 제거할 필요성을 보여주는 단적인 예시다.

커널은 core kernel codedevice driver 두 가지로 나눌 수 있다. 코어 커널 코드는 system call(이하 syscall) 인터페이스를 통해 사용자가 파일을 열거나(open() syscall) 프로그램을 실행(execve() syscall)하는 등의 동작을 할 수 있도록 해준다. device driver(장치 드라이버)는 POSIX 호환 시스템(모바일 폰 시장의 98% 이상을 차지하는 Linux/Android와 FreeBSD/iOS 등)에서 일반적으로 ioctl 인터페이스를 통해 접근한다. 특정 syscall로 구현된 ioctl 인터페이스는 input dispatch를 장치 드라이버에서 처리할 수 있도록 해준다. 구글에 따르면 제보된 안드로이드 커널 관련 버그의 85%는 third-party 기기 공급 업체(:vendor. 이하 벤더사)의 드라이버 코드에서 발견되었다. 모바일 기기의 사용도 증가하고 보안도 중요해지는 현재, 공격자가 취약점을 exploit 하기 전에 장치 드라이버의 취약점을 자동으로 식별하는 방법이 필요하다.

syscall 인터페이스의 자동 분석 방법은 syzkallerTrinity와 같은 연구도 있었지만 ioctl은 제외되었다. syscall과의 interaction은 잘 정의된 일관된 spec이 있지만, ioctl 과의 interaction은 장치 드라이버에 따라 다르기 때문이다. 특히, ioctl 인터페이스는 유효한 command set 각각에 대한 구조화된 인자로 구성되며, command와 data structure는 드라이버마다 다르다. 이런 구조체의 포인터, dynamically-sized field, union, sub-structure 등은 구조체의 잘못된 파싱으로 인한 취약점의 가능성을 높인다. 이런 특징은 보안과 관련 있지만 동시에 장치의 분석을 어렵게 만든다. 이런 장치의 자동화된 분석은 반드시 인터페이스를 인식(interface-aware) 해야 한다. 즉 효과적으로 분석하려면 command identifier와 data structure를 이용해 ioctl과 interaction 해야 한다.

본 논문에서는 DIFUZE를 소개한다. 이 퍼저는 interface-aware fuzzing을 가능하게 하고, 장치 드라이버에서 제공한 ioctl interface를 동적으로 탐색할 수 있다. DIFUZE는 커널 드라이버 코드를 자동화으로 정적 분석하여 수행하여 valid command 및 관련 data structure를 포함하는 특정 ioctl 인터페이스를 복구(:recover) 한다. 이렇게 복구한 인터페이스를 이용해 userspace에서 커널로 보낼 수 있는 ioctl call 입력을 생성한다. 이런 입력은 드라이버가 사용하는 command나 구조체와 일치해서 ioctl을 효과적이고 깊게 탐색할 수 있다. 복구된 인터페이스는 퍼저가 데이터를 변이할 때 더 의미 있는 선택을 할 수 있도록 해준다. 즉 pointer, enum, integer와 같이 타입이 있는 field를 단순한 바이트열로 다루지 않도록 해준다. DIFUZE는 해당 드라이버를 인지하여(:stresses assumptions) 심각한 취약점을 노출한다. 본 연구에서 최신 모바일 기기 7대를 분석하여 36개의 취약점을 발견했으며, 그중 32개는 unknown 취약점이다(나머지 4개는 연구를 진행하던 중 패치되었다.). DoS를 유발해 crash를 발생시키는 취약점부터 기기를 완전히 장악할 수 있는 취약점까지 severity는 다양하다.

본 논문의 기여는 다음과 같다.

  • Interface-aware fuzzing. POSIX 시스템의 ioctl 커널 드라이버 인터페이스와 같이 인터페이스에 민감한(:interface-sensitive) 대상을 효율적으로 퍼징할 수 있는 방법을 제안한다.
  • Automated driver analysis. 장치의 커널을 자동으로 분석할 수 있는 퍼징 프레임워크를 개발했다. 모든 드라이버의 ioctl entry와 구조체, 장치 파일명을 식별할 수 있다. 이 기술을 이용해 7개의 기기를 분석했고, 36개의 취약점을 식별했다. 발견한 취약점은 DoS부터 코드 실행 취약점까지 다양한데, 이는 우리의 접근 방식의 타당성을 입증해 준다. 발견한 취약점은 각 회사에 알리고 있다.
  • DIFUZE prototype. DIFUZE가 향후 보안관련 연구에 유용하게 쓰이길 바라면서 github open source로 공개한다.

2. Background and Related Work

본 Section에서는 해결해야 할 문제들(:challenge)을 설명하고 이 문제들이 왜 최신 시스템에 ioctl fuzzing을 적용하기 어렵게 하는지 설명한다. 그리고 퍼징을 수행할 플랫폼인 Android를 소개하고 취약점을 찾는 이전 연구들과 비교한다.

2.1 POSIX Device Drivers

POSIX standard는 userspace 어플과 장치 드라이버 간의 interaction을 위한 인터페이스를 명세한다. 이 인터페이스는 장치 파일(device file)를 통해 interaction 할 수 있도록 해준다. device file은 kernel-resident 장치 드라이버의 userspace 존재를 나타내는 특수한 파일이다. userspace 어플이 open() syscall로 장치 파일의 핸들러를 획득한 뒤에, 어플은 여러 방법으로 이 파일들과 interaction 할 수 있다.

기기마다 다른 기능을 위해 다양한 syscall을 필요로 한다. 예를 들어 read(), write(), seek()와 같은 syscall은 하드 드라이버의 내용을 하나의 파일로 표시하는 하드 드라이버 장치에 적용할 수 있다. 오디오 장치에서는 read()가 마이크를 통해 raw 음성 데이터를 읽는 역할일 것이고, write()는 raw 음성 데이터를 스피커로 출력하는데 사용할 것이며, seek()는 사용되지 않을 것이다.

기존의 syscall로는 구현할 수 없는 기능이 있을 수도 있다. 예를 들어 오디오 장치에서 userspace 어플이 녹음 또는 재생을 할 때 sampling rate를 어떻게 설정할 수 있을까? POSIX 표준은 ioctl() 인터페이스를 통해 이런 기능을 이용할 수 있도록 해준다. 이 syscall은 드라이버가 기존 파일로는 모델링 하기 힘든 기능을 사용(:expose) 할 수 있도록 해준다.

이를 generic하게 사용하기 위해 ioctl() 인터페이스는 임이의 드라이버에 맞게 구현된(:driver-specified) 구조체를 입력으로 받을 수 있다. c prototype은 int ioctl(int file_descriptor, int request, ...)로 표현할 수 있다. 첫 번째 인자는 open file decriptor, 두 번째 인자는 흔히 command identifier라고 부르는 integer 타입이다. 나머지 인자의 타입과 수는 드라이버와 command identifier에 따라 다르다.

  • Challenges.

    전술한 특징은 ioctl syscall의 취약점에 큰 영향을 미친다.

    1. read()write()와 다르게 iotctl()의 인자는 종종 아주 복잡한 비표준 data structure의 instance다. 이런 구조체를 파싱하는 것은 아주 어렵고, 사소한 실수라도 kernel context에 직결되는 심각한 취약점으로 이어질 수 있다.
    1. 대부분의 data structure는 ioctl() 인터페이스 분석을 어렵게 만든다. 분석하는 사람은 해당 드라이버가 각 command identifier를 처리하는 방법과 optinal argument에 대해 예상되는 데이터 타입을 알고 있어야 한다.

    이는 본 연구에서 해결하려는 핵심적인 문제들이다. command identifier와 data structure를 자동으로 복구하고, 필요한 복잡한 data structure를 build하고 ioctl() 인터페이스를 이용해 장치를 자동으로 퍼징할 수 있도록 DIFUZE를 설계했다.

2.2 Android Operating System

안드로이드는 스마트폰 OS로 설계되었다. 최근에 안드로이드가 2016년 3분기에 86.8%의 점유율을 보이며 스마트폰 OS 시장을 장악했다. 안드로이드 설계자는 기기를 안전하게 설계하려고 노력하지만 여러 취약점이 존재한다. 안드로이드의 인기와 보안 이슈가 급증하고 있기 때문에 안드로이드를 메인 타겟으로 결정했다. 물론 다른 UNIX 계열 시스템에서 DIFUZE를 사용할 수도 있다.

안드로이드는 단일 아키텍처(:monolithic architecture)인 리눅스 커널을 기반으로 한다. 장치 드라이버와 같은 커널 모듈은 일정한 수준의 모듈성을 제공하지만, 커널 전체가 단일 메모리 공간에서 실행되고 모두 동일한 권한을 갖는다는 점에서 여전히 monolithic 하다. 그래서 장치 드라이버에서 발생하는 어떤 취약점이라도 커널 전체로 확장될 수 있다. 실제로 2016에 제보된 안드로이드 커널 버그의 80% 이상은 벤더사가 작성한 드라이버 코드에서 발견되었다. Android Open Source Project는 Sony나 HTC 같은 벤더들이 디지털카메라나 가속도계, GPS 같은 새로운 하드웨어를 탑재할 수 있도록 안드로이드 커널 드라이버를 수정할 수 있게 해주었다. 이런 회사의 경우 종종 시장 출시 시 보안은 뒷전으로 밀려나기 때문에 개발 프로세스에 보안 취약점을 도입하기 어렵다. 다행히도 안드로이드의 개방성은 GNU 라이선스에 따라 소스 코드를 공개적으로 사용할 수 있게 한다. 이는 본 연구에서 드라이버에 대한 고수준의 그리고 의미상(:semantically) 풍부한 정보에 대한 접근을 용이하게 해주었다.

2.3 Fuzz Testing

퍼징은 프로그램의 입력으로 사용할 랜덤 데이터를 생성해 프로그램을 테스트하는 기술이다. SPIKE, Valgrind, PROTOS 등 많은 퍼징 기술이 연구되었다.

  • Fuzzing.

    퍼징의 주요 전망은 '주로 유효한' 입력을 생성하여 타겟 프로그램을 실행하고 여러 기능을 테스트한 뒤, 취약점으로 이어질 수 있는 coner case를 트리거 하는 것이다. dynamic taint tracking은 잠재적인 입력(potential input)을 생성하기 위해 자주 쓰이는 방법이다. 입력 생성을 위해 taint tracking을 사용하는 DowserBuzzFuzz는 특정 클래스의 취약점을 트리거 할 확률이 높다. 하지만 아주 제한적인 입력을 필요로 하는 ioctl 함수에게 이런 접근 방식은 비효율적이다. taint analysis 기반의 접근 방식은 ProspexREWARDS와 같은 프로그램의 입력 포맷을 복구하기 위해 사용된다. 하지만 값들 간의 cross-dependency는 복구하지 못한다(예를 들어 command identifier가 주어졌을 때 ioctl 핸들러가 특정 타입의 추가 인자를 기대하는 경우).

    진화(evolutionary) 기술은 퍼징 시스템에서 또 다른 입력 생성 기법 중 하나이다. [관련 연구는 AFL, KameleonFuzz, GAFuzzing 등을 참고]. VUzzerSymFuzz는 정적 분석과 변이 기반 진화 기술을 합쳐서 입력을 효율적으로 생성한다. 그러나 이런 기술은 입력에 제한이 많은 경우 비효율적이다. DIFUZE는 이런 문제를 해결하기 위해 사용 가능한 ioctl command를 먼저 모은 뒤에, 유효한(:expected) 입력 포맷에 맞게 비제한적인 값들만 퍼징하는 방법을 사용한다.

    프로그램의 입력 포맷을 알면 유효한 입력의 spec 덕분에 퍼징이 더 유리해진다. Peach는 industry standard tool 중 하나이다. 하지만 다른 데이터를 포함하는 active pointer와 같은 live data는 생성할 수 없다. 그리고 Section 8에서 다루겠지만, 많은 장치 드라이버는 포인터를 포함하는 input structure를 필요로 한다. 문법 기반(grammer-based) 기술은 파일 포맷(QuickFuzz), 인터프리터(Grammar-based Whitebox Fuzzing, Fuzzing with Code Fragments), 컴파일러(Fuzzing the Rust Typechecker Using CLP, Finding Deep Compiler Bugs via Guided Stochastic Program Mutation)를 퍼징하는데 쓰이지만, 고정된 input foramt을 필요로 한다.

  • Kernel and driver fuzzing.

    OS 인터페이스나 syscall을 퍼징하는 것은 OS 커널을 테스트할 수 있는 실용적인 접근 방법이다. 대부분의 드라이버는 userspace와 interact 하기 위해 POSIX 표준인 ioctl 함수를 사용한다. ioctl은 복잡하고 사용자가 생성하는 특정한 command와 data format을 요구한다. 유효한 command value들 그리고 그들과 연관된 data structure를 식별하는 것은 ioctl fuzzing에서 중요한 두 가지의 key problem이다. iofuzz, ioattack, ioctlbf, ioctlfuzzer과 같이 Windows 커널 퍼저도 있다. 그러나 이 퍼저들은 Windows만의 ioctl command format뿐 아니라 Windows 커널이 제공하는 광범위한 정보들을 logging 하고 tracing 하는 것에 의존하고 있다. 게다가 이런 도구들은 대부분 본질적으로 단순하다. 이 퍼저들은 단순히 프로세스에 attach 한 뒤 Windows ioctl call을 후킹 한다. 후킹에 성공하면 call 할 때의 값을 변이한다. 하지만 이 방법은 프로세스가 드라이버의 기능 전체를 커버하지 못하거나 들어오는 데이터의 타입 정보를 알지 못하는 등 부족한 점들이 있다. 이런 문제를 해결하기 위해 DIFUZE는 유효한 command와 그에 대응하는 data structure를 식별하기 위해 장치 드라이버의 소스 코드를 분석한다. 이 분석 기술은 실제 기기를 수정하기 않고 할 수 있다.

    유효한 ioctl command를 추출하는 방법은 Stanislas의 연구 Fuzzing IOCTLs with angr에서 이미 시도했으나 real-world kernel module로 확장할 수는 없었다. DIFUZE는 실제 기기에서 large kernel module로 확장할 수 있었고 취약점을 발견했다.

    Trinity와 syzkaller는 Linux syscall 퍼징을 위해 개발되었다. 이 퍼저들은 장치 드라이버의 ioctl 핸들러를 퍼징할 때는 좋은 대안이 아니다. syzkaller는 더 많은 버그를 찾기 위해 Kernel Address Sanitizer와 같은 추가적인 instrumentation 기술을 사용하기도 한다. 하지만 이런 기술은 공급업체 기기에서 직접 수행할 수 없어서 커스텀 펌웨어로 기기를 다시 flash 해야 한다. Discovering and exploiting 802.11 wireless driver vulnerabilities, Fuzzing wi-fi drivers to locate security vulnerabilities, Don't trust your USB! How to find bugs in USB device drivers, Fuzzing the out-of-memory killer on embedded Linux: an adaptive random approach, perf fuzzer와 같은 접근 방식은 특별하게 선택된 syscall과 드라이버를 집중적으로 퍼징한다. 하지만 그들은 특정 함수에만 집중하기 때문에 다른 syscall과 드라이버를 일반화할 수는 없다. DIFUZE는 커널을 수정하지 않고 실제 기기에서 모든 Linux 커널 드라이버를 완벽하게 자동화해서 퍼징할 수 있는 최초의 퍼저다.

2.4 Other Analyses

퍼징 외에도 symbolic executionstatic analysis 두 가지 분석 방법이 본 연구과 관련이 있다. 두 매커니즘을 소개하고 본 연구에서 어떤 영향을 주었는지 설명한다.

  • Symbolic execution.

    symbolic execution은 제한된 입력을 생성하고 complex check를 만족하기 위해 symbolic variable을 사용하는 기술이다.

    DART, SAGE, Fuzzgrind, Driller는 코드 커버리지를 증가시키기 위해 symbolic execution과 random testing을 함께 사용한다. BORG는 symbolic execution을 이용해 buffer overread를 트리거 할 확률이 높은 입력을 생성한다. 이런 기술은 커널 드라이버에 실용적이지 않다. 그 이유는 raw 한 기기에서 symbolic execution을 수행해야 하는 (복잡한 시스템 커널로 인해 더 힘들어진) engineering issue와 근본적인 path explosion problem 때문이다.

  • Static analysis.

    정적 분석(static analysis)은 대상 프로그램을 실행하지 않고 취약점을 찾는 기술이다(참고). 정확도(:precision)를 높이기 위해 보통 분석을 위한 소스 코드가 필요하다. Linux 커널을 포함해 많은 시스템 커널과 장치 드라이버는 open-source이므로 커널 보안은 정적 분석을 통해 큰 이점을 가질 수 있다. 예를 들어, Ashcraft는 연구에서 Linux와 OpenBSD 커널에서 신뢰할 수 없는 소스로부터 읽은 integer를 catch하기 위해 컴파일러 extension을 개발했다. Post는 연구에서 deadlock과 memory leak을 찾기 위해 bounded model checker를 사용했다. Ball은 연구에서 Windows 드라이버의 correctness를 증명하기 위해 rule set을 이용해 동적 분석 도구를 설계했다.

    정적 분석 도구 대부분의 공통된 문제는 오탐(:false positive)률이 높다는 것이다. 본 연구에서는 취약점 탐지 단계에서 퍼징을 활용한다. 따라서 false positive를 완전히 회피할 수 있으며, 발견된 모든 취약점은 실제 취약점이다. 정적 분석 기술의 또 다른 단점은 종종 보안 정책(security policy)과 규칙의 manual spec이 필요하다는 것이다.


3. Overview

본 Section에서는 인터페이스 인지 퍼징 방법에 대한 개요와 ioctl fuzzing을 통해 장치 드라이버에서 취약점을 찾기 위한 적용 과정을 다룰 것이다. 또한 시스템을 꼼꼼하게 이해할 수 있도록 논문 전체에서 참조할 예제를 제시한다.

Figure 1은 시스템의 high-level workflow를 나타낸 그림이다.

Figure 1: The DIFUZE approach diagram. DIFUZE는 유효한 ioctl command나 argument structure type과 같은 드라이버 인터페이스 정보를 추출하여 분석하기 위해 커널 소스 코드를 제공받는다. 이런 structure의 인스턴스를 합성하고 대상 기기로 디스패치하면 주어진 입력으로 ioctl 실행이 트리거되고, 장치 드라이버에서 crash를 발견한다.

DIFUZE는 입력으로 사용할 target host의 커널 소스 코드(device driver 코드 포함)가 필요하다. Linux는 GNU 라이센스에 따라 관련 소프트웨어는 모두 공개되었으므로 커널 드라이버 인터페이스 코드 또한 공개되어 있다. 따라서 안드로이드 기기의 커널 소스 코드를 읽거나 분석하는데 사용할 수 있다.

주어진 이 입력으로 여러 단계(:phase)를 통해 장치 드라이버를 위한 interaction 인터페이스를 복구하고, 이 인터페이스를 실행하기 위한 유효한 구조체를 생성하고, target host의 커널에 의해 구조체 processing을 트리거 한다. 커널 버그를 트리거 하는 것은 hang이나 reboot을 유발하는 등 시스템을 불안정하게 만든다. 이 때문에 DIFUZE의 final stage는 vivo[중국 벤더사]의 target host에서만 성공했다. 다른 stage는 외부 analysis host에서 실행되기 때문에 input replay를 위해 local에 결과를 저장한다. 그리고 네트워크나 디버깅용 인터페이스를 통해 타겟 호스트로 전송된다.

각 stage의 자세한 설명은 아래와 같다:

  • Interface recovery. 먼저 target host에서 어떤 드라이버가 사용 가능한지, 어떤 device file이 interaction에 쓰이는지, 어떤 ioctl command를 받을 수 있고 어떤 구조체가 command를 통해 전달되길 기대하는지 등을 탐지하기 위해 제공된 소스 코드를 분석한다. 이 일련의 분석은 LLVM을 이용해 구현했으며 Section 4에서 다룬다. 이 단계의 결과는 타겟 드라이버, 타겟 ioctl command, 구조체 타입 정의 등을 포함하는 tuple set이다.
  • Structure generation. 각 구조체에서 DIFUZE는 이전 단계에서 복구한 타입 정보의 인스턴스화를 나타내는 메모리 contents에 관한 구조체 인스턴스를 연속적으로 생성한다. 이 인스턴스들은 관련 타겟 device filename과 대상 ioctl command identifier와 함께 로깅되고 target hist로 전송된다. 자세한 내용은 Section 5에서 다룬다.
  • On-device execution. 실제 ioctl triggering component는 target host에 있다. target device filename, target ioctl command, 생성된 구조체 인스턴스를 받으면 executor가 ioctl의 실행을 트리거 한다. 이 단계는 Section 6에서 다룬다.

DIFUZE는 타겟으로 전송하는 일련의 입력을 모두 기록한다. 따라서 버그가 트리거되고 기기에 크래시가 발생하면 입력은 재생산(reproducibility)이나 수동 triage/analysis에 쓰인다.

3.1 Example

DIFUZE의 이해를 돕기 위해 간단한 드라이버의 예시를 제공한다. Listing 1은 구조체의 정의, Listing 2는 분석을 약간 복잡하게 만들기 위한 copy_from_user 함수의 wrapper 코드, Listing 3는 그들의 iotctl 핸들러, Listing 4는 메인 드라이버 초기화 코드로 구성되어 있다.

Listing 4driver_init() 함수는 커널 초기화 과정에서 호출되는 드라이버 초기화 함수다. 이 함수는 line 8에서 example_device라는 이름으로 장치를 등록하고, userspace 어플이 devie file(/dev/example_device)에서 ioctl syscall을 수행(line 10-11)할 때 ioctl_hander를 호출하도록 한다. 파일명이 example_device이지만, 파일의 절대 경로는 장치의 타입에 따라 다를 수 있다. 예시에서는 장치가 character device이고 /dev 디렉토리 하위에 생성된다. 하지만 device file의 타입에 따라 다른 디렉토리에 생성될 수도 있다. 예를 들어, proc device는 /proc 디렉토리 하위에 생성된다.

  • Listing 1: 예시에서 사용할 구조체의 정의. DIFUZE는 자동으로 이 구조체를 복구하고 타겟 드라이버의 구조체를 인식하여 퍼징한다.
typedef struct {
	long sub_id;
	char sub_name[32];
} DriverSubstructTwo;

typedef union {
	long meta_id;
	DriverSubstructTwo n_data;
} DriverStructTwo;

typedef struct {
	int idx;
	uint8_t subtype;
	DriverStructTwo *subdata;
} DriverStructOne;
  • Listing 2: 많은 real-world 드라이버처럼 copy_from_user 함수의 wrapper 함수를 예시로 제공한다. DIFUZE는 이 예시처럼(혹은 더 복잡한) wrapper 함수를 분석해야 하기 때문에 중첩(:nested) 함수의 분석을 지원한다.
int copy_from_user_wrapper(void *buff, void *userp, size_t size) {
	// copy size bytes from address provided by the user (userp)
	return copy_from_user(buff, userp, size);
}
  • Listing 3: command identifier를 위한 구체적인 값이나 각 command를 위한 적절한 구조체 형태의 데이터를 입력으로 사용하는(:expected) ioctl hander의 예시다. ioctl processing은 여러 함수로 나뉜다.
DriverStructTwo dev_data1[16];
DriverStructTwo dev_data2[16];
static bool enable_short;
static bool subhandler_enabled;

long ioctl_handler(struct file* file, int cmd, long arg) {
	uint32_t curr_idx;
	uint8_t short_idx; void* argp = (void*)arg;
	DriverStructTwo* target_dev = NULL;
	switch (cmd) {
	case 0x1003:
		target_dev = dev_data2;
	case 0x1002:
		if (!target_dev)
			target_dev = dev_data1; // program continues to execute
		if (!enable_short) {
			if (copy_from_user_wrapper((void*)&curr_idx, argp,
				sizeof(curr_idx))) {
				return -ENOMEM; // failed to copy from user
			}
		}
		else {
			if (copy_from_user_wrapper((void*)&short_idx, argp,
				sizeof(short_idx))) {
				return -ENOMEM; // failed to copy from user
			}
			curr_idx = short_idx;
		}
		if (curr_idx < 16)
			return process_data(&(target_dev[curr_idx]));
		return -EINVAL;
	default:
		if (subhandler_enabled)
			return ioctl_subhandler(file, cmd, argp);
	}
	return -EINVAL;
}

long ioctl_subhandler(struct file* file, int cmd, void* argp) {
	DriverStructOne drv_data = { 0 };
	DriverStructTwo* target_dev;
	if (cmd == 0x1001) {
		if (copy_from_user_wrapper((void*)&drv_data, argp,
			sizeof(drv_data))) {
			return -ENOMEM; // failed to copy from user
		}
		target_dev = dev_data1;
		if (drv_data.subtype & 1)
			target_dev = dev_data2;
		// Arbitrary heap write if drv_data.idx > 16
		if (copy_from_user_wrapper((void*)&(target_dev[drv_data.idx]),
			drv_data.subdata,
			sizeof(DriverStructTwo))) {
			return -ENOMEM; // failed to copy from user
		}
		return 0;
	}
	return -EINVAL;
}
  • Listing 4: 예시 코드의 메인 드라이버 초기화 함수. DIFUZE가 이름을 복구해야 할 driver file을 동적으로 생성하고, 마찬가지로 DIFUZE가 복구해야 할 고수준의 ioctl 핸들러를 등록한다.
static struct cdev driver_devc;
static dev_t client_devt;
static struct file_operations driver_ops;

__init int driver_init(void)
{
	// request minor number
	alloc_chrdev_region(&driver_devt, 0, 1, "example_device");
	// set the ioctl handler for this device
	driver_ops.unlocked_ioctl = ioctl_handler;
	cdev_init(&driver_devc, &driver_ops);
	// register the corresponding device.
	cdev_add(&driver_devc, MKDEV(MAJOR(driver_devt), 0), 1);
}

4. Interface Recovery

장치 드라이버의 ioctl을 효율적으로 퍼징하기 위해서 DIFUZE는 해당 드라이버의 인터페이스를 복구해야 한다. 장치 드라이버의 드라이버는 드라이버와 communicate 하기 위해 쓰이는 장치 파일의 이름/경로, 장치의 ioctl command를 위한 유효한 값, 다른 ioctl command에 대한 ioctl 인자의 구조체 정의로 구성되어있다.

이 데이터를 복구하기 위해 LLVM에 구현된 분석 방법을 사용한다. Linux 커널은 LLVM만으로 분석하기에 적절하지 않아서, 다른 build 과정을 추가했다. 장치 드라이버가 생성한 devie file의 파일명을 식별하고, ioctl 핸들러를 찾고, 유효한 ioctl command identifier의 set을 복구하고, ioctl command의 인자에 대한 구조체 정의를 검색(:retrieve)한다.

4.1 Build System Instrumentation

DIFUZE가 Linux 장치 드라이버에서 LLVM 분석을 할 수 있도록 몇 가지 단계를 수행한다.

  • GCC compilation. 먼저 GCC를 이용해 컴파일할 target host의 커널과 드라이버 소스를 수동으로 설정하는 단계를 수행한다. 보통은 이 과정은 잘 문서화되어있지만, 모바일 벤더사는 GPL이 요구하는 소스코드 릴리즈를 쉽게 컴파일 할 수 있도록 도움을 주지는 않기 때문에 어느 정도 수동으로 configuration을 해줘야 한다. source tree를 GCC로 컴파일 할 수 있으면, 전체 컴파일을 수행하고 모든 명령어를 기록한다.
  • GCC-to-LLVM conversion. DIFUZE를 위해 만든 GCC-to-LLVM command conversion utility를 사용하여 컴파일 단계에 실행한 command를 기록한다. 이 utility는 GCC 형식에서 LLVM 형식으로 command-line flag를 변환하고, LLVM을 통해 커널 소스를 컴파일 할 수 있도록 해준다. LLVM은 컴파일 과정에서 각 소스 파일에 대한 bitcode file을 생성한다. debug 정보를 bitcode file에 포함(:embedded)할 수 있도록 했다. 이는 구조체 정의를 추출할 수 있도록 도와주는데 자세한 내용은 Section 4.6에서 설명한다.
  • Bitcode consolidation. DIFUZE는 드라이버마다 다른 방법으로 분석한다. 따라서 다양한 bitcode file을 통합하여 각 드라이버별 bitcode file을 생성한다. 이를 통해 각 bitcode file에 대한 인터페이스 복구 분석을 수행할 수 있고, 이는 분석을 단순하게 해준다. 이렇게 통합된 bitcode file은 분석을 위한 이후 단계에서 쓰인다.

4.2 ioctl Handler Identification

대부분의 장치 드라이버는 ioctl 인터페이스를 이용해 interaction 한다. 어플리케이션은 userspace에서 ioctl() syscall을 호출하여 드라이버의 장치 파일, command identifier, 필요한 구조화된 데이터 인자에 file descriptor를 전달한다. 이 syscall이 kernel space에 도착하면 해당 드라이버의 ioctl 핸들러가 호출된다. 그러면 핸들러는 command identifier에 따라 드라이버 내부의 다른 기능에 request 한다. 이 예시에서 ioctl_handler()가 ioctl 핸들러 함수에 해당한다.

유효한 command identifier와 추가적인 ioctl 인자의 구조체 정의를 복구하기 위해서 먼저 고수준의 ioctl 핸들러를 식별해야 한다. 각 드라이버는 각 device file마다 고수준의 ioctl 핸들러를 등록할 수 있고, Linux kernel에서 여러 가지 방법으로 등록할 수 있다. 그러나 이러한 모든 방법은 이 목적을 위해 성생된 구조체 set(이런 구조체에 대한 최소한 72개의 variation이 있음) 중 하나의 생성을 포함한다. 이 구조체의 필드 중 하나는 ioctl 핸들러에 대한 함수 포인터다. 이러한 구조체 전체 리스트와 커널 중 하나에 대한 해당 filed name은 Appendix A에 있다.

ioctl 핸드러를 식별하기 위한 분석은 다음과 같다: LLVM의 분석 기능을 이용해 드라이버에서 이러한 구조체의 모든 사용을 찾고 ioctl 핸들러 함수 포인터의 할당 값을 복구한다. 예시에서는 file_operations 구조체(Listing 4의 line 9)의 unlocked_ioctl 필드에 대한 쓰기를 식별한다. 그러고 나면 ioctl_handler() 함수를 ioctl 핸들러로 인식할 수 있다.

4.3 Device File Detection

ioctl 핸들러에 해당하는 device file을 찾기(:determine) 위해서 ioctl 핸들러를 등록할 때 제공되는 이름을 식별해야 한다. 예를 들어 예시에서는 device file은 /dev/example_device이다. (Listing 4의 line 7)

Linux 커널에서 장치의 타입에 따라 file name을 등록하는 여러 가지 방법이 있을 수 있다. 예를 들어 character device의 등록은 장치의 이름과 연관시키기 위해 alloc_chrdev_region method를 이용한다. 게다가 장치 타입에 따라 device file의 디렉토리가 다를 수 있다.

ioctl 핸들러가 주어지면 해당하는 device name을 식별하기 위해 다음과 같은 절차를 따른다.

(1) Appendix A에 나열된 operation structure의 필드 중 하나에 주소를 저장하는 LLVM store instruction을 찾는다.

(2) registration functions에서 operation structure에 대한 reference를 확인한다. (참고)

(3) device filename에 대한 인자를 분석하고 상수인 경우 return 한다.

예시의 경우 Listing 4에서 ioctl_handler()를 ioctl 핸들러 함수라고 했었다. 먼저 line 9에서 ioctl_handler() 함수가 file_operations 구조체(i.e. driver_ops)에 저장되어 있는지 식별해야 하고, 이후 line 10에서 cdev_init() 함수의 인자인 driver_ops의 usage를 확인해야 한다. cdev_add() 함수는 장치가 character device 임을 나타낸다. line 7에서 세 번째 인자가 device name인 device metadata(alloc_chrdrv_region)에 대한 allocation function으로 역추적(:backtrack)하여 constant string으로 detect 하고, device name으로 /dev/example_device를 return 한다.

Listing 5처럼 드라이버는 동적으로 생성된 파일명을 사용할 수 있다. 그러나 정적 분석의 한계로 인해 이러한 파일명을 놓칠 수 있어서 수동으로 분석해야 한다. 물론 모든 과정을 자동화하고 싶다면 이런 장치를 그냥 무시하면 된다.

  • Listing 5: 동적으로 생성된 Huawei Honor phone의 RNIC driver의 device name. DIFUZE는 이 드라이버의 device name을 찾지 못했다.
VOS_INT __init RNIC_InitNetCard(VOS_VOID) {
	...
	snprintf(pstDev->name, sizeof(pstDev->name),
		"%s%s",
		RNIC_DEV_NAME_PREFIX,
		g_astRnicManageTbl[ucIndex].pucRnicNetCardName);
	...
}

다음으로 주어진 ioctl 핸들러의 유효한 command identifier를 식별한다.

4.4 Command Value Determination

ioctl 핸들러가 주어지면 cmd 값(ioctl()의 두 번째 인자)에 대한 모든 등식 조건(:equality constraint)을 모으기 위해 정적으로 절차 간 path-sensitive 분석을 수행한다. 그런 다음 비교 연산에 대한 가능한 값을 복구하기 위해 Range Analysis를 수행한다. Listing 3의 ioctl 예시에서 다음과 같은 조건들을 모은다: cmd == 0x1003 (line 10), cmd == 0x1002 (line 12), cmd == 0x1001 (line 32 → Line 41). 비교 연산이 상수이므로 Range Analysis를 하면 상수는 각각 0x1003, 0x1002, 0x1001이 된다.

cmd value에서 eqaul(==) 비교 연산만 고려한다. 본 연구에서 관찰 결과 거의 모든 드라이버가 유효한 command ID를 확인하기 위해 equal(==) 비교만 사용하기 때문이다. V4L2 드라이버처럼 드라이버 특정 함수가 다른 드라이버에 의해 중첩된 방식(:nested manner)으로 호출되는 특이한 ioctl 함수도 있다. Appendix B에서 이런 경우에 대한 solution을 확장하여 제시한다.

4.5 Argument Type Identification

ioctl command identifier와 해당 data structure 정의에는 다대다(:many-to-many) 관계가 있다. 각 ioctl commnad는 여러 가지 다른 구조체(예를 들어 global configuration을 기반으로 하는 구조체)를 가질 수 있으며, 각 command structure는 여러 ioctl command에 전달될 수 있다. 이런 구조체를 찾기 위해서 먼저 Linux 커널이 userspace에서 kernel space로 데이터를 복사하기 위해 사용하는 copy_from_user 함수의 모든 경로를 식별한다(Listing 3의 line 16 → Listing 2의 line 3). source operand(즉 copy_from_user() 함수의 두 번째 인자)가 ioctl 함수의 인자로 전달되지 않는 call-site는 무시한다. 이런 경우는 ioctl 인자 타입을 결정하는 데 도움이 되지 않기 때문이다. 대신 나머지 call-site 각각에서 source operand의 타입을 찾는다. 이것이 ioctl 핸들러에 대한 사용자 데이터 인자가 준수해야 하는 type definition이다.

pointer casting은 실제 구조체 타입을 숨길 수 있다. 예를 들어 예시에서 Listing 2의 line 3에 있는 copy_from_user()가 여러 경로(line 16, 21, 32 → line 41)에서 Listing 3의 ioctl_handler인 ioctl 핸들러에 도달할 수 있다. 그러나 call-site에서 source operand의 실제 타입은 void* 형이다. 또한 copy_from_user() 함수는 wrapper 함수에 있고 ioctl 함수(예를 들어 Listing 3의 line 16 → Listing 2의 line 3)에 의해 간접적으로 호출될 수 있다. 이 ioctl 함수는 다른 함수나 파일에 분산되어 있다.

이를 위해 각 경로에서 copy_to_user() 함수의 source operand에 할당될 수 있는 가능한 모든 타입을 결정하기 위해 절차 간 path-sensitive한 type propagation을 수행한다.

commnad identifier를 각 구조체 타입에 연관시키기 위해 type propagation를 수행하는 동안 경로를 따라 equality constraint(==)도 모은다. copy_from_user() 함수에 도달하는 경로의 command value에 대한 조건은 구조체 타입과 관련된 가능한 command identifier를 나타낸다.

Listing 3의 예시에서 먼저 copy_from_user call-site에 도달하는 모든 경로를 식별한다(실제 호출은 wrapper 함수 copy_from_user_wrapper를 통해 발생한다 ). Table 1의 2열은 모든 경로를 보여준다. 단순하게 하기 위해 cmd에 동일한 constraint가 있는 경로를 무시하고 동일한 call-site에 도달했다.

Table 1: ioctl 핸들러 (Listing 3)에서 copy_from_user() call-site까지의 관련 경로.

source operand가 사용자 인자가 아니기 때문에 Path 6도 무시한다(즉, Listing 3의 line 49에서 copy_from_user_wrapper()의 두 번째 인자는 argp가 아니다). 마지막으로 나머지 경로에 대해 타겟 copy_from_user call-site의 target operand 타입을 식별하여 commnad value type을 결정한다. 예를 들어 Table 1의 Path 1의 경우 line 6에서 uint32_t로 정의된 argp의 타입은 Listing 3의 line 16에 있는 destination operand인 curr_idx와 같다. 각 command value에서 여러 타입을 얻을 수 있다. 예를 들어 Table 1에 볼 수 있듯이 Path 1과 Path 2는 동일한 cmd constraint value지만 인자의 타입은 다르다. Table 1에서 command value 0x1003은 인자의 타입 uint32_t와 uint8_t와 연관될 수 있다. 다음으로 인자의 구조체 정의를 추출해야 한다.

4.6 Finding the Structure Definition

타입의 정의를 찾으려면 모든 타입의 정의를 찾아야 한다. 예시의 경우 Listing 1에서 DriverStructOne 타입의 정의를 추출하려면 DriverStructTwo과 DriverSubstructTwo의 정의를 모두 추출해야 한다.

Section 4.5에서 식별된 각 타입에 대해 Section 4.1에서 계산한 디버그 정보를 사용하여 해당 copy_from_user() 함수의 소스 파일명을 찾는다. 소스 파일(파일명)을 알면 GCC-LLVM pipeline을 사용하여 해당 전처리 파일을 생성한다. 전처리 된 파일은 모든 필수 타입의 정의를 포함해야 하므로 식별된 타입의 정의를 찾는다. 그런 다음 c2xml tool로 C 구조체 정의를 XML 형식으로 parsing하여 타입에 대한 필요한 정의를 추출한다.


5. Structure Generation

DIFUZE는 ioctl 인터페이스를 복구한 후 on-device execution engine에 전달할 구조체 인스턴스 생성할 수 있다. 이를 위한 절차는 간단하다. DIFUZE는 구조체를 인스턴스화하고 필드를 임의의 데이터로 채우고, ioctl에 대한 복잡한 입력을 build 하기 위한 포인터를 적절하게 설정한다.

  • Type-Specific Value Creation: 특정 값은 다른 값보다 더 많은 코드 커버리지를 트리거 할 가능성이 높다. 예를 들어 시스템 코드의 버퍼 길이는 종종 128, 256바이트와 같은 bit boundary에 맞춰지므로 그 주변의 값이 corner case(예를 들어 string termination를 잘 처리하지 않아서 생기는 single-byte overwrite)를 트리거 할 가능성이 높다. 이런 고찰은 많은 연구에서 사용되고 있다. DIFUZE도 이런 concept을 활용하며 2162^{16} 이나 216±12^{16}\pm1을 선호한다. 물론 이런 값에만 국한되는 것은 아니다.

    구조화되지 않은 데이터 (예: char* pointer) 또는 구조체 정의를 복구할 수 없는 (예: void* 데이터) 데이터를 참조하는 포인터가 있다. 이 데이터에 대해 DIFUZE는 임의의 content page를 할당한다.

  • Sub-structure Generation:

    ioctl에 대한 입력은 종종 nested structure 형태다. 이때 top-level(:최상위) 구조체는 다른 구조체에 대한 포인터를 가지고 있다. DIFUZE는 이러한 구조체 인스턴스를 개별적으로 생성하여 on-device execution component로 보낸다. 이 component는 다음 단계에서 ioctl에 전달하기 전에 nested structure로 합한다(:merge).


6. On-device Execution

DIFUZE의 이전 단계는 analysis host에서 실행되는 반면, ioctl의 실제 실행은 target host에서 실행되어야 한다. 따라서 generation component는 생성된 구조체를 target device 드라이버 filename과 ioctl command identifier와 함께 on-device execution component로 보낸다. 그러면 component가 구조체들을 finalize 하고 ioctl을 트리거 한다.

6.1 Pointer Fixup

일부 구조체는 포인터로 연결된 여러 메모리 영역으로 구성된다. 공간을 save 하기 위해 구조체 생성 component는 서로 다른 메모리 영역 인스턴스를 결합 방법에 대한 metadata와 함께 독립적으로 전송하며, on-device execution component는 이 데이터를 사용하여 전체 구조체를 build 한다. 이렇게 하면 서로 다른 구조체로 built된 구조체에 동일한 데이터를 사용할 수 있으므로, analysis host와 target host 간의 bandwidth가 보존된다. 예를 들어 tree structure의 각 노드가 개별적으로 전송되기 때문에 이러한 노드를 사용하여 tree structure의 다양한 final configuration을 만들 수 있다.

재귀적인 structure도 있다. 예를 들어 linked list node는 다음 linked list node의 포인터를 포함한다. on-device execution component가 생성하려는 구조체 조합의 수에 대한 boundary를 설정하기 위해 DIFUZE는 이러한 구조체의 recursion을 threshold 만큼으로 제한한다.

6.2 Execution

구조체가 생선 되면 DIFUZE의 on-device execution component는 적절한 device file을 열고 ioctl command identifier와 적절한 data structure를 사용하여 ioctl syscall을 트리거 한다. 이때 DIFUZE는 target device에 crash를 발생시키는 커널 버그를 감시한다. 이는 analysis host와 target host 간의 heartbeat signal을 유지하면서 진행된다. DIFUZE가 버그를 발견하면 나중에 재현(:reproduction)과 분류(:triage)를 위해 host device로 전송된 일련의 입력을 전부 기록한다.

  • System restart. 버그가 트리거 되면 target host가 inconsistent state거나 crash가 발생한다. inconsistent state의 경우 on-device execution component가 다른 ioctl command 및 기타 드라이버에 대한 퍼징을 재개하기 장치 재부팅을 트리거 한다. 크래시가 발생한 경우 크래시가 발생한 방법에 따라 장치가 스스로 재부팅 될 수 있다. 이 경우 DIFUZE는 스스로 재개할 수 있다. 그렇지 않으면 사용자가 퍼징을 재개하기 전에 수동으로 장치를 재부팅해야 한다.

7. Implementation

Figure 1에서 볼 수 있듯이 DIFUZE가 완전히 자동화되도록 시스템을 설계했다. 사용자는 단순히 컴파일 가능한 kernel source archive를 제공하고, target host(i.e. mobile phone)를 analysis host에 연결하고, target host에서 on-device execution component를 실행하면 된다. 그리고 나면 single command로 전체 pipeline이 실행된다.

  • Interface Extraction: 인터페이스 추출 기술을 구현하기 위해 LLVM 3.8을 사용했다. 인터페이스 추출의 모든 component는 개별 LLVM pass로 구현된다. 유효한 command identifier를 복구하기 위해 기존의 Range Analysis를 사용했다.

7.1 Interface-Aware Fuzzing

Section 5와 6의 구현체를 MangoFuzz라고 한다. MangoFuzz는 인터페이스 인식 퍼징을 위한 analysis host의 구조 생성과 ioctl의 on-device execution으로 이루어져 있다. MangoFuzz는 결과에 영향을 줄 수 있는 다른 최적화 없이 인터페이스 인식 퍼징의 효과를 테스트하기 위해 의도적으로 단순하게 만든 prototype이다.

MangoFuzz는 특히 실제 안드로이드 장비의 ioctl syscall을 대상으로 한다. Section 5에서 설명한 방법으로 관련 구조체와 함께 ioctl call의 랜덤 sequence를 생성하고, target host에서 실행 중인 on-device execution component로 전송한다.

"production-ready" variation을 위해서 DIFUZE를 최신 Linux syscall fuzzer인 syzkaller에 통합했다. 본 연구는 가능한 최고의 퍼저를 만들려는 목표를 위해 syzkaller의 오픈 소스에 기여할 것이다.

syzkaller는 사용자가 (수동으로) syscall 설명(:description)을 제공한 뒤에 해당 syscall을 퍼징하는 리눅스 syscall fuzzer다. syzkaller는 해당 포맷이 수동으로 지정된 경우 구조체를 syscall 인자로 처리할 수 있다. DIFUZE와 통합하기 위해 인터페이스 복구 단계의 결과를 syzkaller의 형식으로 자동으로 변화해 인터페이스를 인식한다. syzkaller는 일반적으로 커버리지 정보와 KASAN(또는 다른 sanitizer)로 컴파일 된 커널에 사용된다. 그러나 수정되지 않은 실제 안드로이드 장치에서 실행하기 위한 configuration이 있다.


8. Evaluation

DIFUZE의 인터페이스 복구와 bug-finding 기능을 모두 평가한다. 가장 인기 있는 5개 벤더사의 7가지 안드로이드 폰에서 수행하며 다양한 장치 드라이버를 포함한다. Table 2에서 칩셋 벤더사와 함께 휴대폰 정보를 볼 수 있다.

Table 2: 평가에 사용된 안드로이드 휴대폰. (커널 버전은 연구를 진행할 당시의 휴대폰의 최신 버전으로 평가했다.)

먼저 시스템의 핵심 component인 인터페이스 복구의 효과와 효율성을 평가한다. 결과를 검증하기 위해 ioctl 및 구조체의 random sampling을 수동으로 분석하고 복구된 시스템의 인터페이스와 비교한다. 그런 다음 MangoFuzz와 syzkaller에 대한 개선 사항을 fuzzing component로 사용하여 DIFUZE의 bug-finding 기능을 비교 평가한다.

8.1 Interface Extraction Evaluation

인터페이스 추출의 모든 단계는 Ubuntu 16.04.2 LTS를 실행하는 Intel Xeon CPU E5-2690 (3.00GHz)이 장착된 동일한 실험 플랫폼에서 수행된다. 평균적으로 커널에 대한 전체 인터페이스 추출 단계는 55.74분이 걸렸다.

Table 2에 있는 장치의 커널에서 인터페이스 추출의 여러 단계의 효과를 평가한다. Table 3에서 다른 커널에 대한 인터페이스 추출의 결과를 확인할 수 있다. DIFUZE는 7개 장치의 커널에서 총 789 개의 ioctl 핸들러를 식별했다. 핸들러의 수는 해당 폰의 driver의 개수와 거의 일치한다.

Table 3: DIFUZE가 휴대폰의 커널에서 복구한 인터페이스.
  • Device Name Identification: device name 식별에 대한 접근 방식(Section 4.3)은 다양한 벤더별 장치에서 작동할 수 있다. DIFUZE는 ioctl 핸들러의 59.44%를 차지하는 469개의 device name을 자동으로 식별할 수 있다. 식별에 실패한 경우는 대부분 커널 mainline 드라이버에서 발생한다. 예를 들어 Xperia XA 벤더사의 드라이버에서만 이름을 복구하면 90% 이상을 복구할 수 있었다. 벤더사 드라이버는 정적 이름을 사용하는 반면, mainline 드라이버는 동적으로 생성된 이름(Listing 5)을 사용하는 경향이 있기 때문에 이러한 불일치가 발생한다. 동적으로 생성된 장치명을 수동으로 추출했다.
  • Valid Command Identifiers: Table 3의 4열인 Valid Command Idenifiers은 해당 커널의 모든 entry point에서 추출된 유효한 command identifier의 수를 나타낸다. DIFUZE는 모든 커널의 모든 드라이버에서 3,565개의 유효한 command identifier를 찾았다. 유효한 command identifier의 수는 커널에 따라 차이가 많이 난다. Table 3Table 5에서 볼 수 있듯이 퍼저가 발견한 crash 개수는 유효한 command identifier와 양의 상관관계(:positively correlated)를 갖는다.

    Figure 2는 ioctl 핸들러 당 유효한 command identifier 수의 분포를 나타낸다. 11%의 ioctl 핸들러는 어떤 명령어도 기대하지 않는다. 이런 ioctl의 코드는 조건부로 컴파일되고 커널 configuration에 의해 보호된다. 컴파일하는 동안 ioctl 핸들러 코드가 비활성화된다. 따라서 생성된 bitcode 파일에서 해당 ioctl 핸들러가 비어있는 것으로 나타나고, command identifier 프로세스에서 coammnd value 개수가 0이 된다. ioctl 해들러의 50%는 single command identifier를 기대한다. 대부분은 v4l2_ioctl_ops의 결과로 본다(:attribute). Appendix B에 나와있듯이 이들은 (단일) 특정 command를 관리하는 중첩(:nested) 핸들러다. 대부분(98.3%)의 ioctl 핸들러는 유효한 command identifier가 20개 미만이다. 20개 이상의 command identifier를 사용하여 ioctl의 나머지(1.7%)를 수동으로 조사한 결과, 우리의 접근 방식이 일부 ioctl 함수에 대한 함수 포인터를 over approximate 한다는 사실을 발견했다. 이러한 과대 추정(:over estimation)은 후속 퍼징 단계에서 유효하지 않은 fuzz unit을 추가적으로 발생시키지만 전체 성능에 미치는 영향은 미미하다. (특히 이런 경우의 비율이 낮다는 점을 감안하면 더욱 그러하다.)

Figure 2: 유효한 command identifier의 수에 따른 ioctl handler 퍼센트의 CDF 그래프
  • User argument types:

    Table 3의 마지막 4개 열은 사용자가 전달한 인자(ioctl 핸들러에 대한 3번째 인자)가 처리되는 방식을 보여준다.

    1,688(47%) 개의 command identifier에 대해 copy_from_user를 찾을 수 없었다.

    이는 두 가지 카테고리 중 하나에 해당한다.

    (1) 사용자 인자는 C 타입 long으로 처리되므로 사용자 인자가 raw value로 처리된다. 따라서 인자 타입을 식별할 필요가 없고, copy_from_user가 존재하지 않는다.

    (2) copy_to_user가 존재한다. 대신 사용자는 커널이 사용자에게 정보를 복사하는 어떤 타입에 대한 포인터를 제공해야 하는 경우다.

    커널이 사용자 데이터를 처리하지 않기 때문에 이런 경우에도 타입 식별을 신경 쓰지 않는다.

    나머지 1,877개(53%)의 command identifier에 대해 사용자 인자는 특정 데이터 타입에 대한 포인터가 될 것으로 예상된다. 즉 copy_from_user를 호출해 데이터를 복사해야 한다. 이런 포인터 인자는 다음 세 가지로 더 분류될 수 있다.

    (i) command identifier 중 526개(15%)는 scalar pointer 인자를 기대한다. 예를 들어 Table 1에 표시된 대로 command ID 0x1003과 0x1002는 scalar type uint32_t나 uint8_t를 가리키는 사용자 인자를 기대하므로 이 카테고리에 속한다.

    (ii) 961개(30%)의 command iderntifier는 사용자 인자가 포인터를 포함하지 않은 C 구조체를 가리킬 것을 기대한다. (예: Listing 1의 DriverStructTwo)

    (iii) 390개(11%)의 command identifier에 대해 date type은 embedded 포인터를 포함하는 C 구조체다.

    예시의 경우 Table 1과 같이 command ID 0x1001은 이 카테고리에 속하며, 사용자 인자가 embedded pointer(Listing 1)를 포함하는 DriverStructOne을 가리킬 것으로 예상한다. 사용자 인자는 그 자체가 포인터(유효한 포인터 여야 함)를 포함하는 구조체를 가리킬 것으로 기대하기 때문에, 인자 타입 정보 없이 이러한 command를 효과적으로 퍼징하는 것은 매우 어렵다.

  • Random Sampling Verification: 테스트 set에서 7개의 안드로이트 휴대폰 각각에 대해 5개의 ioctl의 random sample을 선택하고, 추출된 타입이 올바른지 수동으로 확인했다. 이 35개의 ioctl에는 총 327개의 command가 있으며, 그중 294개의 인자와 command를 정확하게 식별하여 90%의 정확도를 제공한다.

8.2 Evaluation Setup

DIFUZE가 장치 드라이버에서 실제 버그를 얼마나 잘 찾을 수 있는지 그리고 추출된 인터페이스 정보를 사용할 때의 효과를 확인하기 위해 prototype fuzzer인 MangoFuzz와 syzkaller를 사용하여 테스트한다. 퍼징과 on-device execution component로 MangoFuzz와 syzkaller를 각각 사용할 때 DIFUZE를 나타내는 identifier DIFUZE(m)과 DIFUZE(s)를 사용한다. 또한 syzkaller에 제공하는 인터페이스의 양을 변경하여 시스템을 평가한다. 이렇게 하면 다양한 수준의 인터페이스 추출이 결과에 어떤 영향을 미치는지 조사할 수 있다. 특히 구체적으로 다음과 같은 DIFUZE confgiuration을 실행한다:

  • Syzkaller base. syzkaller는 장치 파일 open을 위한 syscall open()과 ioctl 핸들러를 트리거 하기 위한 ioctl을 사용해 퍼징해야 한다고 지정한다. 기본 configuration에는 여러 표준 장치 파일 이름과 일반 Linux 장치 용 ioctl과 함께 일부 표준 타입의 구조체가 포함된다.
  • Syzkaller+path. 이 configuration에서는 syzkaller가 퍼징해야 하는 추출된 드라이버 경로의 spec을 추가한다. 그러나 나머지 인터페이스 정보는 제공되지 않는다.
  • DIFUZE(i). 추출된 장치 경로 및 ioctls의 인터페이스 정보는 syzkaller를 fuzzer로 사용한다. 이 configuration은 ioctl command 핸들링을 트리거 할 수 있지만 복잡한 구조체를 처리하는 코드를 탐색 할 수는 없다.
  • DIFUZE(s). 이 configuration은 ioctl 인자 구조체 포맷의 자동 식별을 포함한 모든 인터페이스 복구를 syzkaller와 통합한다. syzkaller의 많은 최적화를 활용할 수 있으므로 최고 성능의 configuration이 될 것이라고 생각한다.
  • DIFUZE(m). 마지막 configuration은 인터페이스 복구를 간단한 fuzzer prototype인 MangoFuzz와 통합한다. 이는 다른 최적화 없이, 인터페이스 인식 퍼징이 발견한 버그 수에 미치는 영향을 탐색하기 위한 configuration이다.

구글의 현재 flagship 모델과 Samsung Sony, HTC를 포함한 7개의 최신 안드로이드 기기에서 시스템을 평가했다. 각 기기를 최신 버전으로 업데이트한 뒤 루팅(:rooting)했다. one-device execution component는 root로 실행되어 앱 수준 권한에서 접근할 수 있는 드라이버뿐 아니라 모든 드라이버를 퍼징할 수 있다. Section 9에서 다루겠지만 component는 표준 어플리케이션의 형태일 수도 있지만 장치 파일과 해당 ioctl 핸들러에 대한 access 가능성이 낮아진다. 이 설정에서는 커널을 다시 컴파일하고 non-stock kernel을 플래싱해야하므로 code coverage feedback이나 KASAN을 사용할 수 없다. 이러한 compile-time instrumentation에 대한 자세한 내용은 Section 9에서 다룬다. 앞서 언급 한 모든 DIFUZE configuration은 각 안드로이드 기기에서 5시간 동안 실행되며, 단일 드라이버에서 crash가 자주 발생하는 경우 버그가 있는 ioctl 핸들러를 블랙리스트에 추가한다. 이 블랙리스트는 휴대폰이 반복적으로 crash를 일으키거나 그로 인한 재부팅이 실험에 악영향을 미치지 않도록 도와준다.

8.3 Results

평가 단계에서 모든 crash log와 syscall의 crashing sequence를 수집하고 수동으로 분류하여 몇 가지 중복된 크래시를 필터링했다. 테스트에 사용된 7개의 Android 기기에서 총 36개의 unique 버그를 발견했다. Table 5에서 발견한 버그의 개요를 확인할 수 있다.

Table 5: 각 장치의 fuzzing configuration마다 발견한 버그의 수.

Galaxy S6에서 syzkaller를 동작시킬 수 없었고, DIFUZE(m)은 버그를 트리거 할 수 없었기 때문에 Galaxy S6는 버그가 없는 유일한 Android 기기가 되었다. 다른 기기에서는 취약점을 최소 두 개(Honor 8의 경우)부터 14개(Xperia XA의 경우)까지 발견했다.

syzkaller의 기본 configuration(인터페이스 정보가 없는 경우)은 테스트에서 버그를 찾지 못했다. 드라이버의 올바른 경로(syzkaller + path)를 제공하면 모든 장치에서 3번의 crash만 발생했다. 이는 정보 없이(:blindy) 커널 드라이버를 퍼징하는 것이 효과적이지 않다는 것을 보여준다. 기기를 출시(:shipped)하기 전에 벤더사에서 테스트를 수행했기 때문일 수도 있다.

DIFUZE(i)는 추출된 ioctl number의 형태로 부분적인 인터페이스 정보를 추가하여 22개의 버그를 찾을 수 있다. 게다가 인터페이스에 나머지 인터페이스 정보(ioctl 인자 구조체 정의)를 추가하면 발견된 버그 수가 54.5% 증가하여 총 34개의 버그를 발견했다. 이 결과는 인터페이스 인식 퍼징의 효과를 보여주며, 복구된 ioctl command identifier와 구조체 정보가 ioctl 핸들러 분석에 미치는 중요성을 보여준다.

실험 결과 DIFUZE(m)이 DIFUZE(s)보다 버그를 겨우 4개 더 적게 발견했다는 점이 흥미로웠다. syzkaller는 많은 퍼징 전략과 최적화가 내장된 최신 도구인 반면, MangoFuzz는 Section 6에서 설명한 기능을 제외하면 최적화가 없는 단순한 fuzzing prototype이다. 이는 정확한 인터페이스 정보를 사용한 퍼징이 매우 효과적이라는 것을 보여준다.

각 crash를 간략하게 분류(:triage)하고 crash 발생 원인을 빠르게 분석했다. 이 결과는 Table 4에서 확인할 수 있다. crash는 종종 심각한 버그로 이어진다. 예를 들어 assertion error는 공격자가 더 강력한 primitive를 얻기 위해 조작할 가능성이 있는 더 심각한 underlying bug에 의해 트리거 될 수 있다. 발견한 더 흥미로운 버그 중 하나는 대부분의 assert를 우회할 수 있다는 것이다. 이런 bypass 버그는 실현이 어려울 수도 있었던 여러 시나리오를 현실화했다. 결과의 심각성을 입증하기 위해 arbitrary write 취약점 중 하나를 이용하여 커널에서 코드를 실행하고 앱 수준 권한에서 root로 권한으로 상승시켰다.

Table 4: DIFUZE가 발견한 버그의 유형

현재 벤더사에게 취약점을 알려 공개하기 위해 노력하고 있다. 그리고 실험 과정에서 4개의 버그가 패치된 것을 확인했다. 36개의 버그 중 나머지 4개는 현재 0-day 버그다.

다음 몇 개의 하위 섹션에서는 실험에서 발견한 두 가지 버그에 대한 case study를 제시하여 그 영향과 버그 탐지 시 인터페이스 정보의 필요성을 보여준다.

8.4 Case Study I: Design issue in Honor 8

가장 흥미로운 버그 중 하나는 (커널 버그에 대한 일반적인 것처럼) OS crash가 아니라 target host의 매우 이상한 동작을 감지하여 발견했다. Huawei Honor 8에 대한 몇 번의 fuzzing round 후, Listing 6와 같이 장치의 serial number가 변경되었다는 사실을 발견했다. 장치의 serial number는 높은 EL3 권한 수준(참고:ARM Exception levels)에서 실행되는 boot loader만 변경할 수 있는 read-only 속성이어야 한다. 그러나 이 경우 커널 드라이버를 exploit 하여 최소 권한 수준 EL0에서 실행하는 안드로이드의 userspace 어플에서 serial number를 실제로 변경할 수 있음을 보여준다. 따라서 이는 design-level의 취약점이다.

  • Listing 6: A design issue found by DIFUZE while fuzzing nve driver.
# before fuzzing
HWFRD:/ $ getprop ro.serialno RNV0216811001641
# after fuzzing
HWFRD:/ $ getprop ro.serialno ^RDO>l

이 버그는 driver nve를 퍼징하여 발견했다. Honor 8에는 장치 configuration 정보를 저장하는 flash nvme에 파티션이 있다. 이러한 configuration 옵션 중 일부는 권한이 없으며, 안드로이드에서 수정할 수 있다. 여기에는 device unlock 가능 여부와 ramdump 허용 여부가 포함되지만 특히 부트 로더에서만 수정할 수 있는 board identifier 및 serial number와 같은 속성은 제외된다. 그러나 device /dev/nve의 ioctl 핸들러는 이러한 옵션을 읽고 쓰는 방법을 제공한다. 또한 configuration 옵션의 타입을 확인하지 않으며 악의적인 userspace 어플이 권한 있는 configuration 옵션을 읽거나 쓸 수도 있다.

권한 있는 configuration 옵션에 대한 수정을 허용하지 않도록 하면 이 문제를 해결할 수 있다. 권한 수준 EL1에서 실행되는 Android 커널이 더 높은 권한으로 실행되는 부트 로더에 속하는 옵션을 읽거나 쓸 수 없어야 한다. 물론 이에 대한 진짜 해결 방법은 권한 있는 옵션과 권한 없는 옵션을 분리하고, 다른 privileged code로 접근 가능한 다른 파티션에 저장하는 것이다.

8.5 Case Study II: qseecom bug

이 Section에서는 가장 높은 수준의 인터페이스 추출 (즉, type recovery/complex structure instantiation)에서만 발견된 버그의 예를 다룬다. 아래 나와있는 코드(Example 1)를 참조할 것이다.

  • Example 1: qseecom bug
static int qseecom_mdtp_cipher_dip(void __user* argp)
{
	struct qseecom_mdtp_cipher_dip_req req;
	u32 tzbuflenin, tzbuflenout;
	char* tzbufin = NULL, * tzbufout = NULL;
	int ret;
	do {
		ret = copy_from_user(&req, argp, sizeof(req));
		if (ret) {
			pr_err("copy_from_user failed, ret= %d\n",
				ret);
			break;
		}
		...
			/* Copy the input buffer from
			userspace to kernel space */
			tzbuflenin = PAGE_ALIGN(req.in_buf_size);
		tzbufin = kzalloc(tzbuflenin, GFP_KERNEL);
		if (!tzbufin) {
			pr_err("error allocating in buffer\n");
			ret = -ENOMEM;
			break;
		}

		ret = copy_from_user(tzbufin, req.in_buf,
			req.in_buf_size);
		...
	} while (0);
		...
			return ret;
}

long qseecom_ioctl(struct file* file, unsigned cmd,
	unsigned long arg)
{
	int ret = 0;
	void __user* argp = (void __user*) arg;
	switch (cmd) {
		...
	case QSEECOM_IOCTL_MDTP_CIPHER_DIP_REQ: {
			...
				ret = qseecom_mdtp_cipher_dip(argp);
			break;
		}
		...
	}
	return ret;
}

// EOF

실험 과정에서 패치된 4개의 버그 중 하나인 CVE-2017-0612를 예시로 든다. 이 버그는 Google의 flagship Android 폰인 Pixel의 시스템에서 발견되었다. 드라이버의 ioctl 함수는 line 31에서 시작하여 ioctl의 common design을 따른다. userspace 어플은 cmd와 arg를 지정한다.

QSEECOM IOCTL MDTP_CIPHER_DIP_REQ cmd가 주어지면 line 39에서 qseecom_mdtp_cipher_dip를 입력한다. line 9에서 사용자 데이터가 qseecom_mdtp_cipher_dip_req 구조체에 복사된 것을 볼 수 있다. line 16에 버그가 있다. tzbuflenin은 사용자가 제어하는 req.in_buf_size 값에서 PAGE_ALIGN을 호출하여 계산된다. userspace에서 어플이 여기에 큰 값을 전달하면 PAGE_ALIGN이 overflow 되어 req.in_buf_size 작은 값 즉 0이 된다. 다음으로 line 17에서 계산된 크기를 kalloc 하려는 것을 볼 수 있다. 마지막으로 line 24에서 드라이버는 구조체의 embedded pointer를 할당된 버퍼로 copy_from_user를 시도한다. 버퍼의 크기가 적절하게 계산되지 않았기 때문에 이 copy_from_user는 crash가 발생한다. 그러나 crash를 관찰하려면 사용자가 제공한 req.in_buffer가 유효한 포인터여야 한다. 그렇지 않으면 copy_from_user가 실패하고 오류를 return 한다. 따라서 ioctl에 적절하게 인스턴스화된 인자가 없으면 이 crash는 트리거 되지 않는다.

8.6 Augmenting with Coverage-guided Fuzzing

커버리지 기반 fuzzing은 잘 연구된 기술이며 좋은 커버리지를 달성하기 위한 효과적인 방법이다(참고). 만약 coverage-guidance를 사용할 수 있다면 interface-aware 기술이 여전히 필요한 기술일까? 물론 그렇다. coverage-guided fuzzing을 위한 인터페이스 정보를 제공하는 것은 드라이버에서 성능을 향상시키는데 중요하다.

시연을 위해 x86-64 커널에서 coverage-guided mode로 syzkaller를 실행하여 구조체 인터페이스 정보를 포함하거나 포함하지 않는 SCSI_IOCTL_SEND_COMMAND(단순한 인터페이스 포함)와 CDROM_SEND_PACKET(복잡한 인터페이스 포함)를 각 4시간씩 퍼징했다. Table 6에서 이런 조합의 결과를 확인할 수 있다. 마지막 열인 Percentage increase는 인터페이스 정보가 제공되었을 때 도달한 basic block의 증가율이다. 이는 인터페이스 정보가 coverage-guided fuzzing 성능을 향상시키는데 중요한 역할을 한다는 것을 보여준다.

Table 6: 인터페이스 정보 제공 여부에 따른 커버리지 기반 퍼징의 성능

recompile이나 가끔은 backporting kcov, kernel reflashing을 해야 하기 때문에 이 평가를 상용 기기로 확장하기는 어렵다. 이는 본 연구의 범위를 벗어난 상당한 노력이 필요하다.


9. Discussion

인터페이스 인식 퍼징을 사용하면 잠재적으로 위험한 버그를 발견하여 커널 보안을 향상시킬 수 있음을 보여주었다. 그러나 이 방식에는 여전히 몇 가지 약점과 개선 방향이 있다.

9.1 Weaknesses

버그가 존재하는 드라이버에서 crash가 너무 일찍 발생하여 퍼저가 더 많은 기능을 탐색하지 못한다는 문제가 있다. 이전 버그가 자주 발생하고 그 버그에 hit 할 때마다 휴대폰이 재부팅되기 때문에 아직 발견하지 못한 버그가 있을 가능성이 높다. 현재 기술에서 유일한 해결 방법은 특정 command identifier나 ioctl 핸들러 전체를 퍼징하지 않고 다른 곳을 퍼징하는 것이다.

또 다른 문제는 인터페이스에서 구조체 필드 간의 복잡한 관계를 추출할 수 없다는 것이다. 구조체의 한 필드가 다른 필드와 관련 있는 경우는 꽤 흔한 일이다. 예를 들어 길이 필드가 버퍼의 크기를 지정할 수 있다. 그러나 DIFUZE는 이런 관계를 인식하지 못하므로 중요할 수도 있는 정보를 퍼저에게 제공하지 못한다.

9.2 Future Work

뛰어난 퍼저들은 런타임 커버리지(run-time coverage)를 기반으로 하기도 한다. 현재 DIFUZE에는 이 기술을 사용하지 않는다. 런타임 커버리지 정보를 사용하려면 커널을 recompile 하고 장치에 flash 해야 하는데 이는 몇 가지 문제가 있다.

  1. 세분화된 커버리지 정보를 얻으려면 개발 보드가 필요하다.

    이는 비용이 많이 들거나 대부분의 경우 실제 기기에서 사용할 수 없다.

  1. recompile할 최신 커널 소스코드를 항상 찾을 수 있는 것은 아니다.

    사소한 커널 업데이트 과정에서 ioctl 인터페이스가 크게 바뀔 가능성이 낮고, 실제 실행은 target host의 최신 소프트웨어 버전에서 수행될 것이기 때문에 DIFUZE에서 큰 문제는 아니다. 그러나 이전 (instrumented) 커널이 target에 flash 된 경우 버그를 발견해도 큰 의미가 없을 수 있다.

  1. 일부 벤더사는 부트 로더를 lock 하고 다른 보안 검사를 수행하여 새 kernel device에 쉽게 flash 하지 못하도록 한다.

이러한 이유로 우리는 커널에 코드 커버리지 측정이나 KASAN(Kernel Address Sanitizer)을 추가하지 않고 instrument 했다. KASAN과 커버리지 정보 모두 DIFUZE의 결과를 향상시킬 수 있다. KASAN은 memory corruption을 감지하고 assertion failure를 트리거 하여 버그를 찾는 데 도움을 준다. KASAN이 없으면, corrupted memory가 사용되지 않거나 중요한 데이터가 corrupted 되지 않은 경우 exploitable bug가 crash를 발생시키지 않을 수도 있다. 커버리지 정보는 이전에 탐색하지 못한(:neglected) 드라이버 기능을 트리거 하는 입력을 mutate 하므로 더 깊은 곳도 탐색할 수 있다.


10. Conclusion

본 논문에서는 Linux 커널 드라이버와 같은 interface-sensitive 코드에 대한 자동 분석의 효율성을 높이기 위해 interface aware 퍼징을 제안했다. 이러한 코드를 퍼징하기 위해 ioctl 인터페이스 spec을 복구하는 일력의 기술을 제공했다. 모든 기술은 single command로 soruce archive에서 직접 작동하는 자동화된 pipeline에서 구현했다. 우리의 기술이 효과적으로 대부분의 드라이버에 대한 component, device file name, valid command identifier와 해당 인터페이스의 인자 타입을 복구하는 것을 보였다. 7개의 안드로이드 휴대폰 모델이서 DIFUZE의 여러 configuration을 사용하여 평가를 진행했다. 그 결과 32개의 unkown 취약점을 포함해 총 36개의 버그를 발견하였고 인터페이스 인식 퍼징의 효과를 입증했다.

최신 모바일 기기의 안전성을 보장하는데 기여하기 위해 DIFUZE를 open source 하여 공개한다.


Comments