링크모음 링크세상
링크세상 링크모음 링크 애니 웹툰 링크 드라마 영화 링크 세상의모든링크

안드로이드, 퍼징에 올인

퍼징은 소프트웨어 취약점을 찾는 데 효과적인 기술입니다. 지난 몇 년 동안 Android는 조직 전체에서 퍼징의 효율성, 범위, 편의성을 개선하는 데 중점을 두었습니다. 이러한 노력으로 인해 테스트 범위가 향상되고, 보안/안정성 버그가 줄어들고, 코드 품질이 향상되었습니다. 지속적인 퍼징 구현을 통해 소프트웨어 팀은 새로운 버그/취약점을 찾아내고 수동으로 퍼징 실행을 시작하지 않고도 자동으로 회귀를 방지할 수 있습니다. 이 게시물에서는 Android 퍼징의 간략한 역사를 설명하고, Google이 대규모 퍼징을 수행하는 방법을 공유하며, Android 전체에서 퍼징 자동화를 위한 인프라 구축에 대한 우리의 경험, 과제 및 성공을 문서화합니다. Android에서 퍼징에 기여하는 데 관심이 있는 경우 시작하는 방법에 대한 지침과 Android의 VRP가 취약점을 찾는 퍼징 기여에 보상하는 방법에 대한 정보가 포함되어 있습니다.

Android 퍼징의 간략한 역사

퍼징은 수년 동안 존재해 왔으며 Android는 Android를 가장 안전하고 안정적인 운영 체제로 만들기 위한 광범위한 목표의 일환으로 퍼징을 자동화하고 단위 테스트와 유사하게 우선순위를 지정하는 초기 대규모 소프트웨어 프로젝트 중 하나였습니다. 2019년에 Android는 퍼징을 코드 제출의 일부로 원활하게 만들어 퍼징을 제도화하는 것을 목표로 퍼징 프로젝트를 시작했습니다. Android 퍼징 프로젝트를 통해 전체 Android 생태계에서 확장 가능한 퍼징 기능을 지원하는 Pixel 휴대폰과 Google 클라우드 기반 가상 기기로 구성된 인프라가 탄생했습니다. 이후 이 프로젝트는 Android의 공식 내부 퍼징 인프라로 성장했으며 수백 개의 퍼저에서 하루 수천 시간의 퍼징을 수행합니다.

내부적으로: Android는 어떻게 퍼징되나요?

1단계: Android 저장소에서 모든 fuzzer 정의 및 찾기

첫 번째 단계는 퍼징을 Android 빌드 시스템(Soong)에 통합하여 빌드 퍼저 바이너리를 활성화하는 것입니다. 개발자가 코드베이스에 기능을 추가하는 동안 퍼저를 포함하여 코드를 퍼징하고 자신이 개발한 코드와 함께 퍼저를 제출할 수 있습니다. Android Fuzzing은 다음과 같은 빌드 규칙을 사용합니다. cc_fuzz (아래 예 참조). cc_fuzz(rust_fuzz 및 java_fuzz도 지원)는 바이너리에 내장할 수 있는 소스 파일과 종속성을 포함하는 Soong 모듈을 정의합니다.

cc_fuzz {
  name: "fuzzer_foo",

  srcs: [
    "fuzzer_foo.cpp",
  ],

  static_libs: [
    "libfoo",
  ],

  host_supported: true,
}

Soong의 패키징 규칙은 이러한 cc_fuzz 정의를 모두 찾아 자동으로 빌드합니다. 실제 퍼저 구조 자체는 매우 간단하며 하나의 주요 메소드(LLVMTestOneInput)로 구성됩니다.

#include <stddef.h>
#include <stdint.h>

extern "C" int LLVMFuzzerTestOneInput(
               const uint8_t *data,
               size_t size) {

  // Here you invoke the code to be fuzzed. 
  return 0;
}

이 fuzzer는 자동으로 바이너리로 빌드되며 정적/동적 종속성(Android 빌드 파일에 지정된 대로)과 함께 zip 파일로 패키징되어 아래 예와 같이 모든 fuzzer가 포함된 기본 zip에 추가됩니다.

2단계: 모든 fuzzer를 Android 빌드에 수집

Fuzzer가 Android 저장소에서 발견되고 바이너리에 내장되면 다음 단계는 백엔드에서 실행할 준비를 하기 위해 Fuzzer를 클라우드 저장소에 업로드하는 것입니다. 이 프로세스는 매일 여러 번 실행됩니다. Android 퍼징 인프라는 오픈 소스 연속 퍼징 프레임워크(Clusterfuzz)를 사용하여 Android 기기 및 에뮬레이터에서 퍼저를 지속적으로 실행합니다. Clusterfuzz에서 fuzzer를 실행하기 위해 fuzzers zip 파일은 빌드 후 이름이 바뀌고 최신 빌드가 실행됩니다(아래 다이어그램 참조).

안드로이드, 퍼징에 올인

fuzzer zip 파일에는 fuzzer 바이너리, 해당 사전은 물론 해당 종속성과 빌드에 해당하는 git 개정 번호(소스맵)가 포함된 하위 폴더가 포함되어 있습니다. 소스맵은 스택 추적을 강화하고 충돌 보고서를 생성하는 데 사용됩니다.

3단계: fuzzer를 지속적으로 실행하고 버그 찾기

Fuzzer를 지속적으로 실행하는 것은 각 작업이 일련의 물리적 장치 또는 에뮬레이터와 연결된 예약된 작업을 통해 수행됩니다. 또한 작업은 실행해야 하는 퍼징 작업을 나타내는 대기열에 의해 지원됩니다. 이러한 작업은 Fuzzer 실행, 이전 퍼징 실행에서 발견된 충돌 재현 또는 말뭉치 최소화 등의 조합입니다.

각 fuzzer는 여러 시간 동안 또는 충돌이 발견될 때까지 실행됩니다. 실행 후 Android 퍼징은 실행 중에 발견된 모든 흥미로운 입력을 가져와서 퍼저 코퍼스에 추가합니다. 그런 다음 이 자료는 fuzzer 실행 전반에 걸쳐 공유되고 시간이 지남에 따라 증가합니다. 그런 다음 새로운 적용 범위의 증가와 발견된 충돌(있는 경우)에 따라 후속 실행에서 fuzzer의 우선 순위가 결정됩니다. 이를 통해 가장 효과적인 fuzzer를 실행하고 흥미로운 충돌을 찾는 데 더 많은 시간을 제공할 수 있습니다.

4단계: 퍼저 라인 커버리지 생성

관심 있는 코드를 퍼징하지 않는다면 퍼저가 무슨 소용이 있을까요? 퍼저의 품질을 개선하고 Android 퍼징의 전반적인 진행 상황을 모니터링하기 위해 두 가지 유형의 적용 범위 측정항목이 계산되어 Android 개발자에게 제공됩니다. 첫 번째 측정항목은 제어 흐름 그래프(CFG)의 가장자리를 참조하는 가장자리 적용 범위에 대한 것입니다. 퍼저와 퍼징되는 코드를 계측함으로써 퍼징 엔진은 실행 흐름이 도달할 때마다 트리거되는 작은 코드 조각을 추적할 수 있습니다. 이렇게 하면 퍼징 엔진은 각 계측 지점이 실행될 때마다 적중되는 횟수(및 횟수)를 정확히 알 수 있으므로 이를 집계하고 적용 범위를 계산할 수 있습니다.

INFO: Seed: 2859304549
INFO: Loaded 1 modules   (773 inline 8-bit counters): 773 [0x5610921000, 0x5610921305),
INFO: Loaded 1 PC tables (773 PCs): 773 [0x5610921308,0x5610924358),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2      INITED cov: 2 ft: 2 corp: 1/1b lim: 4 exec/s: 0 rss: 24Mb
#413    NEW    cov: 3 ft: 3 corp: 2/9b lim: 8 exec/s: 0 rss: 24Mb L: 8/8 MS: 1 InsertRepeatedBytes-
#3829   NEW    cov: 4 ft: 4 corp: 3/17b lim: 38 exec/s: 0 rss: 24Mb L: 8/8 MS: 1 ChangeBinInt-
...

라인 적용 범위는 소스 코드에 라인을 지정하는 계측 지점을 삽입합니다. 라인 커버리지는 개발자가 코드에서 다루지 않는 영역을 찾아내고 그에 따라 퍼징을 업데이트하여 향후 퍼징 실행에서 해당 영역에 도달할 수 있으므로 매우 유용합니다.

안드로이드, 퍼징에 올인

폴더 중 하나를 자세히 살펴보면 파일별 통계를 표시할 수 있습니다.

파일 중 하나를 더 클릭하면 터치된 줄과 적용되지 않은 줄이 표시됩니다. 아래 예에서 첫 번째 줄은 최대 500만 번 퍼징되었지만 퍼저는 라인 3과 4에 포함되지 않았으며 이는 이 퍼저의 적용 범위에 공백이 있음을 나타냅니다.

안드로이드, 퍼징에 올인

우리는 전체 코드베이스에 걸쳐 퍼징 범위를 측정하는 대시보드를 내부적으로 보유하고 있습니다. 이러한 적용 범위 대시보드를 직접 생성하려면 다음 단계를 따르십시오.

퍼저의 품질을 측정하는 또 다른 방법은 1초에 얼마나 많은 퍼징 반복을 수행할 수 있는지입니다. 이는 퍼즈 대상의 계산 능력 및 복잡성과 직접적인 관계가 있습니다. 그러나 이 매개변수만으로는 퍼징이 얼마나 좋고 효과적인지 측정할 수 없습니다.

퍼저 버그를 처리하는 방법

Android 퍼징은 Clusterfuzz 퍼징 인프라를 활용하여 발견된 충돌을 처리하고 Android 보안 팀에 티켓을 제출합니다. Android 보안은 Android 심각도 지침을 기반으로 충돌을 평가한 다음 해결을 위해 취약점을 적절한 팀에 전달합니다. 재현 가능한 충돌을 찾아 Android 보안으로 라우팅한 다음 해당 문제를 담당 팀에 할당하는 전체 프로세스는 충돌 유형과 취약점의 심각도에 따라 짧게는 2시간에서 최대 일주일까지 걸릴 수 있습니다.

최근 퍼저 성공 사례 중 하나는 (CVE 2022-20473)로, ​​내부 팀이 20라인 퍼저를 작성하여 Android 퍼징 인프라에서 실행되도록 제출했습니다. 하루 만에 퍼저는 수집되어 퍼징 인프라로 푸시되어 퍼징을 시작했고 곧 치명적인 심각도 취약점을 발견했습니다! 이 CVE에 대한 패치가 서비스 팀에 의해 적용되었습니다.

Android가 퍼징에 계속 투자하는 이유

코드 회귀로부터 보호

Android 오픈소스 프로젝트(AOSP)는 많은 기여자가 참여하는 크고 복잡한 프로젝트입니다. 결과적으로 프로젝트에는 매일 수천 건의 변경 사항이 발생합니다. 이러한 변경 사항은 작은 버그 수정부터 대규모 기능 추가까지 다양하며 퍼징은 실수로 도입되어 코드 검토 중에 발견되지 않을 수 있는 취약점을 찾는 데 도움이 됩니다.

지속적인 퍼징은 이러한 취약점이 프로덕션 환경에 도입되어 공격자가 악용하기 전에 발견하는 데 도움이 되었습니다. 실제 사례 중 하나는 3년 전에 작성된 fuzzer가 발견한 취약점(CVE-2023-21041)입니다. 이 취약점은 Android 펌웨어에 영향을 미치며 추가 실행 권한 없이 로컬 권한 상승으로 이어질 수 있습니다. 이 fuzzer는 코드 회귀로 인해 이 취약점이 도입될 때까지 제한된 결과로 수년 동안 실행되었습니다. 이 CVE는 이후 패치되었습니다.

안전하지 않은 메모리 언어 함정으로부터 보호

Android는 Rust의 큰 지지자였으며, Android 13은 대부분의 새 코드가 메모리 안전 언어로 된 최초의 Android 릴리스입니다. Android에 진입하는 새로운 메모리 안전하지 않은 코드의 양은 줄어들었지만 여전히 수백만 줄의 코드가 남아 있으므로 퍼징의 필요성은 계속됩니다.

아니요 하나 코드는 안전하다: 메모리에 안전한 언어로 코드 퍼징하기

우리의 작업은 메모리가 아닌 안전하지 않은 언어로 끝나지 않으며 Rust와 같은 언어에서도 fuzzer 개발을 권장합니다. 퍼징에서는 C/C++와 같이 메모리에 안전하지 않은 언어에서 볼 수 있는 일반적인 취약점을 찾을 수 없지만 Android의 전반적인 안정성에 기여하는 수많은 비보안 문제가 발견되어 해결되었습니다.

퍼징 챌린지

종속성 누락과 같은 일반적인 C/C++ 바이너리 문제 외에도 fuzzer에는 고유한 문제 클래스가 있을 수 있습니다.

초당 낮은 실행: 효율적으로 퍼징하려면 돌연변이 수가 초당 수백 개 정도여야 합니다. 그렇지 않으면 퍼징이 코드를 처리하는 데 매우 오랜 시간이 걸립니다. 우리는 퍼저의 상태와 적용 범위의 급격한 감소를 지속적으로 모니터링하는 일련의 경고를 추가하여 이 문제를 해결했습니다. Fuzzer의 성능이 저하된 것으로 식별되면 Fuzzer 개선에 도움이 되는 세부 정보가 포함된 자동 이메일이 Fuzzer 작성자에게 전송됩니다.

잘못된 코드 퍼징: 모든 리소스와 마찬가지로 퍼징 리소스도 제한되어 있습니다. 우리는 이러한 리소스가 우리에게 가장 높은 수익을 제공하도록 보장하기를 원하며 이는 일반적으로 신뢰할 수 없는(예: 잠재적으로 공격자가 제어하는) 입력을 처리하는 퍼징 코드에 리소스를 사용한다는 것을 의미합니다. 여기에는 Bluetooth, NFC, USB, 웹 등을 포함하여 휴대폰이 입력을 받을 수 있는 모든 방식이 포함될 수 있습니다. 구조화된 입력을 구문 분석하는 것은 사양 복잡성으로 인해 프로그래밍 오류가 발생할 여지가 있기 때문에 특히 흥미롭습니다. 출력을 생성하는 코드는 퍼징에 특별히 흥미롭지 않습니다. 마찬가지로 공개적으로 노출되지 않는 내부 코드도 보안 문제가 덜합니다. 우리는 가장 취약한 코드를 식별하여 이 문제를 해결했습니다(다음 섹션 참조).

무엇을 퍼지해야합니까?

Android 소스 코드의 가장 중요한 구성 요소를 모호하게 만들기 위해 우리는 다음을 갖춘 라이브러리에 중점을 둡니다.

  1. 취약점의 역사: 역사는 맥락 변화 이후 먼 역사가 되어서는 안 되며 지난 12개월에 더 초점을 맞춰야 합니다.
  2. 최근 코드 변경: 연구 결과에 따르면 안정적인 코드보다 최근 변경된 코드에서 더 많은 취약점이 발견되었습니다.
  3. 원격 액세스: 원격으로 접근할 수 있는 코드의 취약점은 매우 중요할 수 있습니다.
  4. 권한 있음: #3과 마찬가지로 권한 있는 프로세스에서 실행되는 코드의 취약성은 매우 중요할 수 있습니다.

AOSP에 퍼저를 제출하는 방법

우리는 Android의 가장 민감한 영역을 다루기 위해 내부적으로 퍼저를 지속적으로 작성하고 개선하고 있지만 항상 개선의 여지가 있습니다. AOSP 영역에 대한 자체 fuzzer 작성을 시작하고 싶다면 그렇게 하여 Android를 더욱 안전하게 만드는 것이 좋습니다(예: CL).

  1. 안드로이드 소스 코드 받기
  2. 테스트용 전화가 있나요?
  3. 퍼지 대상 작성(‘퍼징할 내용’ 섹션의 지침을 따르세요)
  4. 퍼저를 AOSP에 업로드하세요.

libFuzzer를 사용한 Fuzzing에 대한 문서를 읽고 시작하고 Fuzzer를 Android 오픈 소스 프로젝트에 체크인하세요. Fuzzer가 버그를 발견하면 이를 Android Bug Bounty 프로그램에 제출하여 보상을 받을 수 있습니다!

 

Leave A Reply

Your email address will not be published.