libFuzzer를 통한 퍼징

단순히 잘못되었거나 예상치 못하거나 임의적일 수 있는 데이터를 프로그램에 입력으로 제공하는 퍼징은 대규모 시스템에서 버그를 찾을 수 있는 매우 효과적인 방식이며 소프트웨어 개발 수명 주기의 중요한 부분이기도 합니다.

Android의 빌드 시스템은 LLVM 컴파일러 인프라 프로젝트 프로젝트의 libFuzzer를 포함하는 방식으로 퍼징을 지원합니다. 테스트 LibFuzzer는 테스트 아래의 기능과 연결되어 있으며, 퍼징 세션 도중에 발생하는 모든 입력 선택, 변형 및 충돌 보고를 처리합니다. LLVM의 새니타이저는 메모리 손상 감지 및 코드 범위 측정항목을 돕는 데 사용됩니다.

이 도움말은 Android의 libFuzzer를 소개하고 계측화된 빌드를 수행하는 방법에 대해 설명하며, fuzzer를 쓰고 실행하고 맞춤설정하는 내용까지 담고 있습니다.

설정 및 빌드

기기에서 제대로 작동하는 이미지를 실행하려면 공장 출고 시 이미지를 다운로드하고 기기를 플래시하면 됩니다. 또는 AOSP 소스 코드를 다운로드하고 아래의 설정 및 빌드 예시를 따를 수 있습니다.

설정 예시

이 예시에서는 타겟 기기가 Pixel (taimen)이고 이미 USB 디버깅 (aosp_taimen-userdebug)에 대한 준비를 마쳤다고 가정합니다. 드라이버 바이너리에서 다른 Pixel 바이너리를 다운로드할 수 있습니다.

mkdir ~/bin
export PATH=~/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
repo init -u https://android.googlesource.com/platform/manifest -b master
repo sync -c -j8
wget https://dl.google.com/dl/android/aosp/google_devices-taimen-qq1a.191205.008-f4537f93.tgz
tar xvf google_devices-taimen-qq1a.191205.008-f4537f93.tgz
./extract-google_devices-taimen.sh
wget https://dl.google.com/dl/android/aosp/qcom-taimen-qq1a.191205.008-760afa6e.tgz
tar xvf qcom-taimen-qq1a.191205.008-760afa6e.tgz
./extract-qcom-taimen.sh
. build/envsetup.sh
lunch aosp_taimen-userdebug

빌드 예시

퍼즈 타겟을 실행하는 첫 단계는 새로운 시스템 이미지를 가져오는 것입니다. 적어도 최신 버전의 Android를 사용하는 것이 좋습니다.

  1. 다음을 실행하여 초기 빌드를 수행합니다.
    m
  2. 기기를 플래시하려면 적절한 키 조합을 사용하여 기기를 fastboot 모드로 부팅합니다.
  3. 다음 명령어로 부트로더를 잠금 해제하고 새로 컴파일된 이미지를 플래시합니다.
    fastboot oem unlock
    fastboot flashall
    

이제 타겟 기기를 libFuzzer 퍼징에 사용할 수 있습니다.

퍼지 작성

Android의 libFuzzer를 사용하여 포괄적인 fuzzer를 작성하는 과정을 보여주려면 다음과 같은 취약한 코드를 테스트 사례로 사용하세요. 이는 fuzzer를 테스트하여 모든 부분이 올바르게 작동하는지 확인하고 충돌 데이터가 어떤 모습인지를 보여줄 수 있도록 도와줍니다.

다음은 테스트 함수입니다.

#include <stdint.h>
#include <stddef.h>
bool FuzzMe(const char *Data, size_t DataSize) {
    return DataSize >= 3  &&
           Data[0] == 'F' &&
           Data[1] == 'U' &&
           Data[2] == 'Z' &&
           Data[3] == 'Z';  // ← Out of bounds access
}

이 테스트 fuzzer를 빌드하고 실행하는 방법은 다음과 같습니다.

  1. Fuzz 대상은 빌드 파일 및 fuzz 타겟 소스 코드라는 두 파일로 구성됩니다. 파일을 추가하려는 라이브러리 옆의 위치에 파일을 만듭니다. fuzzer에 퍼징의 역할을 설명하는 이름을 지정하세요.
  2. libFuzzer를 사용하여 퍼즈 타겟을 작성합니다. 퍼즈 타겟은 지정된 크기의 데이터 blob를 취하여 퍼징하려는 함수에 전달하는 함수입니다. 다음은 취약한 테스트 함수를 위한 기본 fuzzer입니다.
    #include <stddef.h>
    #include <stdint.h>
    
    extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) {
      // ...
      // Use the data to call the library you are fuzzing.
      // ...
      return FuzzMe(data, size);
    }
    
  3. Android의 빌드 시스템에 fuzzer 바이너리를 생성하도록 지시합니다. fuzzer를 빌드하려면 이 코드를 Android.bp 파일에 추가합니다.
    cc_fuzz {
      name: "fuzz_me_fuzzer",
      srcs: [
        "fuzz_me_fuzzer.cpp",
      ],
      // If the fuzzer has a dependent library, uncomment the following section and
      // include it.
      // static_libs: [
      //   "libfoo", // Dependent library
      // ],
      //
      // The advanced features below allow you to package your corpus and
      // dictionary files during building. You can find more information about
      // these features at:
      //  - Corpus: https://llvm.org/docs/LibFuzzer.html#corpus
      //  - Dictionaries: https://llvm.org/docs/LibFuzzer.html#dictionaries
      // These features are not required for fuzzing, but are highly recommended
      // to gain extra coverage.
      // To include a corpus folder, uncomment the following line.
      // corpus: ["corpus/*"],
      // To include a dictionary, uncomment the following line.
      // dictionary: "fuzz_me_fuzzer.dict",
    }
    
  4. fuzzer를 타겟에서 실행하기 위해 만들려면 (기기):
    SANITIZE_TARGET=hwaddress m fuzz_me_fuzzer
    
  5. 호스트에서 fuzzer를 실행하기 위해 fuzzer를 만들려면 다음 안내를 따르세요.
    SANITIZE_HOST=address m fuzz_me_fuzzer
    

편의를 위해 fuzz 대상의 경로와 바이너리의 이름을 포함하는 일부 셸 변수를 정의합니다 (앞에서 작성한 빌드 파일에서).

export FUZZER_NAME=your_fuzz_target

이러한 단계를 수행하면 fuzzer가 빌드됩니다. fuzzer의 기본 위치(이 예시의 경우 Pixel 빌드)는 입니다.

  • $ANDROID_PRODUCT_OUT/data/fuzz/$TARGET_ARCH/$FUZZER_NAME/$FUZZER_NAME(기기의 경우)
  • 호스트의 경우 $ANDROID_HOST_OUT/fuzz/$TARGET_ARCH/$FUZZER_NAME/$FUZZER_NAME
  • 호스트에서 fuzzer 실행

  • Android.bp 빌드 파일에 추가합니다.
    host_supported: true,
    퍼징할 라이브러리가 호스트되는 경우에만 적용할 수 있습니다.
  • 빌드된 fuzzer 바이너리를 실행하여 호스트에서 fuzzer를 실행합니다.
    $ANDROID_HOST_OUT/fuzz/x86_64/$FUZZER_NAME/$FUZZER_NAME
  • 기기에서 fuzzer 실행

    adb을(를) 사용하여 기기에 복사하겠습니다.

    1. 이러한 파일을 기기의 디렉터리에 업로드하려면 다음 명령어를 실행합니다.
      adb root
      adb sync data
       
    2. 다음 명령어로 기기에서 테스트 fuzzer를 실행합니다.
      adb shell /data/fuzz/$(get_build_var TARGET_ARCH)/$FUZZER_NAME/$FUZZER_NAME \
        /data/fuzz/$(get_build_var TARGET_ARCH)/$FUZZER_NAME/corpus

    그러면 아래 예시 출력과 유사한 출력의 결과로 이어집니다.

    INFO: Seed: 913963180
    INFO: Loaded 2 modules   (16039 inline 8-bit counters): 16033 [0x7041769b88, 0x704176da29), 6 [0x60e00f4df0, 0x60e00f4df6),
    INFO: Loaded 2 PC tables (16039 PCs): 16033 [0x704176da30,0x70417ac440), 6 [0x60e00f4df8,0x60e00f4e58),
    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: 5 ft: 5 corp: 1/1b exec/s: 0 rss: 24Mb
    #10	NEW    cov: 6 ft: 6 corp: 2/4b lim: 4 exec/s: 0 rss: 24Mb L: 3/3 MS: 3 CopyPart-ChangeByte-InsertByte-
    #712	NEW    cov: 7 ft: 7 corp: 3/9b lim: 8 exec/s: 0 rss: 24Mb L: 5/5 MS: 2 InsertByte-InsertByte-
    #744	REDUCE cov: 7 ft: 7 corp: 3/7b lim: 8 exec/s: 0 rss: 25Mb L: 3/3 MS: 2 ShuffleBytes-EraseBytes-
    #990	REDUCE cov: 8 ft: 8 corp: 4/10b lim: 8 exec/s: 0 rss: 25Mb L: 3/3 MS: 1 ChangeByte-
    ==18631==ERROR: HWAddressSanitizer: tag-mismatch on address 0x0041e00b4183 at pc 0x0060e00c5144
    READ of size 1 at 0x0041e00b4183 tags: f8/03 (ptr/mem) in thread T0
        #0 0x60e00c5140  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0xf140)
        #1 0x60e00ca130  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x14130)
        #2 0x60e00c9b8c  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x13b8c)
        #3 0x60e00cb188  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x15188)
        #4 0x60e00cbdec  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x15dec)
        #5 0x60e00d8fbc  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x22fbc)
        #6 0x60e00f0a98  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x3aa98)
        #7 0x7041b75d34  (/data/fuzz/arm64/lib/libc.so+0xa9d34)
    
    [0x0041e00b4180,0x0041e00b41a0) is a small allocated heap chunk; size: 32 offset: 3
    0x0041e00b4183 is located 0 bytes to the right of 3-byte region [0x0041e00b4180,0x0041e00b4183)
    allocated here:
        #0 0x70418392bc  (/data/fuzz/arm64/lib/libclang_rt.hwasan-aarch64-android.so+0x212bc)
        #1 0x60e00ca040  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x14040)
        #2 0x60e00c9b8c  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x13b8c)
        #3 0x60e00cb188  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x15188)
        #4 0x60e00cbdec  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x15dec)
        #5 0x60e00d8fbc  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x22fbc)
        #6 0x60e00f0a98  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x3aa98)
        #7 0x7041b75d34  (/data/fuzz/arm64/lib/libc.so+0xa9d34)
        #8 0x60e00c504c  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0xf04c)
        #9 0x70431aa9c4  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x519c4)
    
    Thread: T1 0x006700006000 stack: [0x007040c55000,0x007040d4ecc0) sz: 1023168 tls: [0x000000000000,0x000000000000)
    Thread: T0 0x006700002000 stack: [0x007fe51f3000,0x007fe59f3000) sz: 8388608 tls: [0x000000000000,0x000000000000)
    Memory tags around the buggy address (one tag corresponds to 16 bytes):
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       08  00  cf  08  dc  08  cd  08  b9  08  1a  1a  0b  00  04  3f
    => 27  00  08  00  bd  bd  2d  07 [03] 73  66  66  27  27  20  f6 <=
       5b  5b  87  87  03  00  01  00  4f  04  24  24  03  39  2c  2c
       05  00  04  00  be  be  85  85  04  00  4a  4a  05  05  5f  5f
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
    Tags for short granules around the buggy address (one tag corresponds to 16 bytes):
       04  ..  ..  cf  ..  dc  ..  cd  ..  b9  ..  ..  3f  ..  57  ..
    => ..  ..  21  ..  ..  ..  ..  2d [f8] ..  ..  ..  ..  ..  ..  .. <=
       ..  ..  ..  ..  9c  ..  e2  ..  ..  4f  ..  ..  99  ..  ..  ..
    See https://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html#short-granules for a description of short granule tags
    Registers where the failure occurred (pc 0x0060e00c5144):
        x0  f8000041e00b4183  x1  000000000000005a  x2  0000000000000006  x3  000000704176d9c0
        x4  00000060e00f4df6  x5  0000000000000004  x6  0000000000000046  x7  000000000000005a
        x8  00000060e00f4df0  x9  0000006800000000  x10 0000000000000001  x11 00000060e0126a00
        x12 0000000000000001  x13 0000000000000231  x14 0000000000000000  x15 000e81434c909ede
        x16 0000007041838b14  x17 0000000000000003  x18 0000007042b80000  x19 f8000041e00b4180
        x20 0000006800000000  x21 000000000000005a  x22 24000056e00b4000  x23 00000060e00f5200
        x24 00000060e0128c88  x25 00000060e0128c20  x26 00000060e0128000  x27 00000060e0128000
        x28 0000007fe59f16e0  x29 0000007fe59f1400  x30 00000060e00c5144
    SUMMARY: HWAddressSanitizer: tag-mismatch (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0xf140)
    MS: 1 ChangeByte-; base unit: e09f9c158989c56012ccd88111b82f778a816eae
    0x46,0x55,0x5a,
    FUZ
    artifact_prefix='./'; Test unit written to ./crash-0eb8e4ed029b774d80f2b66408203801cb982a60
    Base64: RlVa
    

    예시 출력에서는 충돌이 10행의 fuzz_me_fuzzer.cpp에 의해 발생했습니다.

          Data[3] == 'Z';  // :(
    

    이는 데이터가 길이 3인 경우 아주 단순한 out-of-bounds입니다.

    fuzzer를 실행한 후에는 출력이 충돌의 결과로 이어지는 경우가 자주 발생하며, 문제의 입력은 corpus에 저장되어 ID를 부여받습니다. 이는 예시 출력에서 crash-0eb8e4ed029b774d80f2b66408203801cb982a60입니다.

    기기에서 뒤잡을 때 비정상 종료 정보를 검색하려면 이 명령어를 실행하여 비정상 종료 ID를 지정합니다.

    adb pull /data/fuzz/arm64/fuzz_me_fuzzer/corpus/CRASH_ID
    테스트 사례를 올바른 디렉터리에 저장하려면 위의 예시와 같이 corpus 폴더를 사용하거나 artifact_prefix 인수(예: ~-artifact_prefix=/data/fuzz/where/)를 사용합니다. my/crashes/go`).

    호스트에서 퍼징하면 fuzzer가 실행 중인 로컬 폴더의 비정상 종료 폴더에 비정상 종료 정보가 표시됩니다.

    libFuzzer에 대한 자세한 내용은 업스트림 문서를 참조하세요.