
안드로이드 커널 취약점 CVE-2019-2215 트리거링 및 분석
2017년 11월, use-after-free 버그가 syzkaller 시스템에 의해 리눅스 커널에서 발견되었습니다. 2018년 2월, 이 버그는 일부 리눅스 커널과 안드로이드 버전에서 패치되었습니다.
이 수정 사항은 Android monthly security bulletins에 포함되지 않아 Pixel 및 Pixel2와 같은 많은 새로 출시된 기기에서 패치되지 않았습니다.
2019년 9월, Project Zero가 안드로이드에 이 버그의 보안 영향을 알렸습니다. 그런 다음 안드로이드는 이 취약점에 CVE-2019-2215를 할당하여 공식적으로 알려지게 했습니다.
CVE-2019-2215는 binder.c의 use-after-free로, 안드로이드 애플리케이션에서 권한을 상승(루트 액세스 획득)시킬 수 있습니다. 이 취약점을 악용하기 위해 사용자의 상호작용이 필요하지 않습니다. 악성 로컬 애플리케이션을 설치하기만 하면 됩니다.
여기서는 이 안드로이드 커널 취약점을 더 자세히 소개하고, 이 취약점을 사용하여 전체 안드로이드 기기의 루트 액세스(권한 상승)를 얻는 방법을 설명합니다.
We will use this Proof of Concept (PoC):
https://github.com/cloudfuzz/android-kernel-exploitation
먼저 안드로이드 에뮬레이터에서 이 취약점을 트리거하여 커널 크래시를 유발하는 방법을 보여드리겠습니다. 그런 다음 이것이 얼마나 위험한지 확인하기 위해 PoC를 사용하여 시뮬레이션된 안드로이드 기기에서 루트 액세스를 얻는 과정을 계속합니다. 그런 다음 커널 코드를 분석하여 원인이 무엇인지 확인합니다(정적 분석과 동적 분석).
분석 후, 어떻게 루트 액세스를 얻었는지 확인합니다. 마지막으로 패치를 사용하여 이 취약점이 어떻게 완화되는지 살펴봅니다.
이 취약점을 트리거하여 커널 크래시를 발생시키기 위해 다음 단계를 사용합니다:
다음 비디오를 시청하세요:
[video]
이 (및 다음) 섹션에서는 정적 및 동적 분석을 사용하여 크래시가 발생하는 이유를 이해할 것입니다.
여기서는 문제를 이해하기 위해 커널 코드(정적 분석)를 분석합니다. crash_report.txt에는 KASan의 보고서가 있으며, 이는 use-after-free 버그라고 말합니다. 이는 객체가 힙에 할당되고(그리고 우리는 그에 대한 참조를 가지고 있음) 그런 다음 객체를 힙에서 해제한 후 참조로 잘못 호출했음을 의미합니다. 이 보고서에는 이 세 단계의 스택 추적이 인쇄되어 있습니다.
위에서 기억하시겠지만, 우리는 PoC에서 'trigger.cpp'를 사용했습니다.
다음은 trigger.cpp의 주요 코드입니다:``` int main() { //1 int fd, epfd; //2 struct epoll_event event = {.events = EPOLLIN}; //3 fd = open("/dev/binder", O_RDONLY); //4 epfd = epoll_create(1000); //5 epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); //6 ioctl(fd, BINDER_THREAD_EXIT, NULL); }
'trigger.cpp'가 무엇을 하는지 살펴보겠습니다.
Android에서는 (다른 Unix 기반 운영 체제와 마찬가지로) 몇 가지 프로세스가 있습니다. 우리가 실행하는 모든 프로그램은 하나(또는 그 이상)의 프로세스를 생성하며, 이러한 프로세스는 OS에 의해 처리됩니다. OS는 멀티태스킹을 위해 프로세스 간 전환을 하거나 프로세스를 종료하는 등의 작업을 수행합니다. 보안상의 이유로 프로세스는 기본적으로 서로 격리되어 있습니다.
어떤 경우에는 한 프로세스가 다른 프로세스와 데이터를 교환해야 할 필요가 있습니다. 이를 **프로세스 간 통신**(IPC, Interprocess Communication)이라고 합니다. Linux에서는 프로세스 간 통신을 위한 여러 방법이 있습니다. Android는 **'Binder'**라는 특정 IPC 메커니즘을 도입했습니다. Binder는 프로세스 간 통신을 용이하게 하는 커널 드라이버입니다.
Android에서 IPC는 일부 커널 메서드(대부분 drivers/binder.c에 있음)를 직접 호출하거나 고수준 구현(예: Java)을 사용하여 수행할 수 있습니다.

**binder**를 사용하려면 커널 바인더 모듈을 열어야 합니다. 이는 trigger.cpp의 3번째 줄에서 수행됩니다. 그러면 파일 디스크립터 포인터가 생깁니다. 이 fd를 사용하여 IPC의 커널 개시자와 수신자를 식별할 수 있습니다.
드라이버와의 모든 상호작용은 작은 집합의 **'ioctl'** 명령(BINDER_THREAD_EXIT, BINDER_WRITE_READ 등)을 통해 이루어집니다.
바인더에 대한 추가 정보: [link1](https://www.nds.ruhr-uni-bochum.de/media/attachments/files/2012/03/binder.pdf), [link2](http://rts.lab.asu.edu/web_438/project_final/CSE_598_Android_Architecture_Binder.pdf)
Linux에는 '**event polling**'이라는 개념이 있습니다. '**epoll**' API는 여러 파일 디스크립터(드라이버를 열거나 I/O 작업 시 사용하는 파일 디스크립터)를 모니터링하려고 할 때 사용됩니다.
**epoll**은 두 가지 중요한 필드를 가진 커널 구조체입니다.
* interest list = 모니터링하려는 파일 디스크립터의 목록.
* ready list = I/O가 준비된 파일 디스크립터의 목록.
이벤트 폴링을 사용하려면 먼저 epoll을 생성하고(4번째 줄), 그런 다음 커널의 **epoll_ctl** 메서드를 호출하여 파일 디스크립터(**fd**)와 연관된 이벤트(&event)를 생성한 epoll(**epfd**)에 추가(EPOLL_CTL_ADD)하거나 삭제합니다.
=> `epoll_event event`는 연관된 파일(fd)이 읽기 작업에 사용 가능할 때 트리거되는 이벤트입니다.
이제 **trigger.cpp**가 무엇을 하는지 이해했습니다(깊게 들어갈 필요는 없습니다!). **binder** 모듈을 열고, 준비되었을 때 이를 수신하기 위해 **epoll**을 생성합니다. 그런 다음 6번째 줄에서 3번째 줄에서 시작한 바인더를 종료합니다.
#### 할당(Allocate):
open()을 호출하면 실제로 **open_binder()** (binder.c의 open() 구현)를 호출하며, open_binder()에서 새로운 **'binder_proc'** 구조체가 생성되고:
` fd->pricate_data = binder_proc`
**epoll_create()**를 호출하면 새로운 epoll 구조체가 생성되고 큐 구조에 추가됩니다.

**epoll_ctrl(epdf, ADD, fd, event)**를 호출하면 새로운 **ep_item**이 생성되고, **fd**(수신할 파일 디스크립터)를 이 **ep_item**에 연결한 후 event_poll의 **red black tree**(ep가 ep_item을 저장하는 데이터 구조)에 삽입합니다. 또한 **ep_item_poll()**을 호출하며, 이 메서드는 ep_item에 콜백 함수 연결을 처리합니다.
새로운 **binder_thread** 구조체가 생성되고(**할당은 여기서 발생**), 위에서 생성된 **binder_proc**에 연결됩니다. 그런 다음 **epoll_entry** 구조체가 생성되며, 이 구조체는 두 개의 리스트, **epoll_entry->wait**와 **epoll_entry->whead**를 가지며, 이 두 리스트 모두 앞서 생성된 **binder_thread**에 대한 포인터를 가지고 있습니다.
그런 다음 **epoll_entry**가 ep_item에 연결됩니다(**ep_item->pwqlist**는 이 epoll_entry를 포함하는 리스트입니다).

#### 해제(Free):
**ioctl(fd, ...)**를 호출하면 fd->private_data를 통해 **binder_proc**에 접근한 후 **binder_thread** 구조체가 메모리에서 **해제**됩니다.
#### 사용(Use):
현재 프로세스가 종료되면 **epoll_ctl(epfd, DEL, fd, event)**가 호출됩니다.
이는 **ep_remove(event_poll, ep_item)**를 호출합니다. 이 메서드는 **ep_item->pwqlist**에서 **epoll_entry**를 가져온 후, **ep_item (ep_tem->wait)**의 대기 리스트(연결 리스트)를 가져와서 이 대기 리스트의 항목 중 하나를 제거하려고 합니다.
다음 코드를 사용합니다(의사 코드 사용):```
entry = wait->entry;
entry.next.prev = entry.prev;
entry.prev.next = entry.next;
여기서 wait->entry는 메모리에서 제거된 binder_thread에 대한 포인터입니다! 따라서 이것은 사용 후 해제(use-after-free) 버그를 유발합니다!!
red_black_tree를 가진 event_poll을 생성했습니다. 각 노드는 ep_item이며, 각 ep_item은 epoll_entry 목록 필드를 가지고 있습니다. 각 epoll_entry는 binder_thread 구조체에 대한 두 개의 포인터(wait, whead)를 가지고 있습니다.
ioctl()을 호출하여 binder_thread를 메모리에서 해제했습니다. 그런 다음 이 구조체가 여전히 사용 가능한 포인터를 통해 접근되면서 종료됩니다!
동적 분석은 실시간으로 데이터를 실행하여 프로그램을 테스트하고 평가하는 방법입니다. 프로그램 실행 중 오류를 찾는 데 사용됩니다.
단계: