Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

libFuzzer를 통한 퍼징

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

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

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

설정 및 빌드

기기에서 실행 중인 이미지가 작동 중인지 확인하려면 아래의 설정 및 빌드 예시를 따르세요.

표준 Android 빌드로 기기를 플래시한 후에는 지침에 따라 AddressSanitizer 빌드를 플래시한 다음 SANITIZE_TARGET='address' 대신 SANITIZE_TARGET='address coverage'를 사용하여 범위를 켜세요.

설정 예시

이 예시에서는 타겟 기기가 Pixel(sailfish)이고 이미 USB 디버깅(aosp_sailfish-userdebug)에 대한 준비를 마쳤다고 가정합니다.

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-sailfish-nde63p-c36cb625.tgz
    tar xvf google_devices-sailfish-nde63p-c36cb625.tgz
    extract-google_devices-sailfish.sh
    wget https://dl.google.com/dl/android/aosp/qcom-sailfish-nde63p-50a5f1e0.tgz
    tar xvf qcom-sailfish-nde63p-50a5f1e0.tgz
    extract-qcom-sailfish.sh
    . build/envsetup.sh
    lunch aosp_sailfish-userdebug
    

빌드 예시

재현 가능한 퍼징 세션이 가능하게 해주는 계측화된 시스템 이미지 생성을 위한 2단계 빌드 프로세스가 있습니다.

먼저 Android의 전체 빌드를 수행한 후 기기에 플래시합니다. 그런 다음 기존 빌드를 시작점으로 활용하여 Android의 계측화된 버전을 빌드합니다. 빌드 시스템은 필요한 바이너리만 빌드하여 올바른 위치에 배치할 수 있을 정도로 정교합니다.

  1. 다음을 실행하여 초기 빌드를 수행합니다.
    make -j$(nproc)
  2. 기기를 플래시하려면 적절한 키 조합을 사용하여 기기를 fastboot 모드로 부팅합니다.
  3. 다음 명령어로 부트로더를 잠금 해제하고 새로 컴파일된 이미지를 플래시합니다. (-w 옵션은 정리된 초기 상태 보장을 위해 사용자 데이터를 삭제함)
    fastboot oem unlock
        fastboot flashall -w
        
  4. 계측화된 빌드를 수행하고 수정된 바이너리를 기기에 플래시합니다.
    make -j$(nproc) SANITIZE_TARGET='address coverage'
        fastboot flash userdata
        fastboot flashall

이제 타겟 기기를 libFuzzer 퍼징에 사용할 수 있습니다. 빌드가 계측화된 빌드인지 확인하려면 adb를 루트로 사용하여 /data/asan/lib의 존재를 확인합니다.

adb root
    adb shell ls -ld /data/asan/lib*
    drwxrwx--x 6 system system 8192 2016-10-05 14:52 /data/asan/lib
    drwxrwx--x 6 system system 8192 2016-10-05 14:52 /data/asan/lib64
    

계측화되지 않은 일반 빌드에는 이러한 디렉터리가 존재하지 않습니다.

퍼지 작성

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

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

#include <stdint.h>
    #include <stddef.h>
    bool FuzzMe(const uint8_t *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. Android 소스 트리에 디렉터리를 생성합니다(예: tools/fuzzers/fuzz_me_fuzzer). 다음과 같은 파일이 이 디렉터리에 생성됩니다.
  2. libFuzzer를 사용하여 퍼즈 타겟을 작성합니다. 퍼즈 타겟은 지정된 크기의 데이터 blob를 취하여 퍼징하려는 함수에 전달하는 함수입니다. 다음은 취약한 테스트 함수를 위한 기본 fuzzer입니다.
    extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
          FuzzMe(buf, len);
          return 0;
        }
        
  3. Android의 빌드 시스템에 fuzzer 바이너리를 생성하도록 지시합니다. fuzzer를 빌드하려면 이 코드를 Android.mk 파일에 추가합니다.
    LOCAL_PATH:= $(call my-dir)
    
        include $(CLEAR_VARS)
    
        LOCAL_SRC_FILES := fuzz_me_fuzzer.cpp
        LOCAL_CFLAGS += -Wno-multichar -g -O0
        LOCAL_MODULE_TAGS := optional
        LOCAL_CLANG := true
        LOCAL_MODULE:= fuzz_me_fuzzer
    
        Include $(BUILD_FUZZ_TEST)
        

    이를 작동하도록 만들기 위한 대부분의 논리는 build/core/fuzz_test.mk.에서 정의된 BUILD_FUZZ_TEST 매크로에 포함되어 있습니다.

  4. 다음을 실행하여 fuzzer를 생성합니다.
    make -j$(nproc) fuzz_me_fuzzer SANITIZE_TARGET="address coverage"
        

이러한 단계를 수행하면 fuzzer가 빌드됩니다. fuzzer의 기본 위치(이 예시의 경우 Pixel 빌드)는 out/target/product/sailfish/data/nativetest/fuzzers/fuzz_me_fuzzer/fuzz_me_fuzzer입니다.

fuzzer 실행

fuzzer를 빌드한 후에는 fuzzer, 그리고 연결 기준으로 삼을 취약한 라이브러리를 업로드합니다.

  1. 이러한 파일을 기기의 디렉터리에 업로드하려면 다음 명령어를 실행합니다.
    adb root
        adb shell mkdir -p /data/tmp/fuzz_me_fuzzer/corpus
        adb push $OUT/data/asan/nativetest/fuzzers/fuzz_me_fuzzer/fuzz_me_fuzzer
         /data/tmp/fuzz_me_fuzzer/
         
  2. 다음 명령어로 테스트 fuzzer를 실행합니다.
    adb shell /data/tmp/fuzz_me_fuzzer/fuzz_me_fuzzer /data/tmp/fuzz_me_fuzzer/corpus

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

    INFO: Seed: 702890555
    INFO: Loaded 1 modules (9 guards): [0xaaac6000, 0xaaac6024),
    Loading corpus dir: /data/tmp/fuzz_me_fuzzer/corpus
    INFO: -max_len is not provided, using 64
    INFO: A corpus is not provided, starting from an empty corpus
    #0
    READ units: 1
    #1
    INITED cov: 5 ft: 3 corp: 1/1b exec/s: 0 rss: 11Mb
    #6
    NEW    cov: 6 ft: 4 corp: 2/62b exec/s: 0 rss: 11Mb L: 61 MS: 1 InsertRepeatedBytes-
    #3008
    NEW    cov: 7 ft: 5 corp: 3/67b exec/s: 0 rss: 11Mb L: 5 MS: 1 CMP- DE: "F\x00\x00\x00"-
    #7962
    NEW    cov: 8 ft: 6 corp: 4/115b exec/s: 0 rss: 11Mb L: 48 MS: 1 InsertRepeatedBytes-
    #35324
    NEW    cov: 9 ft: 7 corp: 5/163b exec/s: 0 rss: 13Mb L: 48 MS: 1 ChangeBinInt-
    =================================================================
    ==28219==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xe6423fb3 at pc 0xaaaae938 bp 0xffa31ab0 sp 0xffa31aa8
    READ of size 1 at 0xe6423fb3 thread T0
    #0 0xef72f6df in __sanitizer_print_stack_trace [asan_rtl] (discriminator 1)
        #1 0xaaab813d in fuzzer::Fuzzer::CrashCallback() external/llvm/lib/Fuzzer/FuzzerLoop.cpp:251
        #2 0xaaab811b in fuzzer::Fuzzer::StaticCrashSignalCallback() external/llvm/lib/Fuzzer/FuzzerLoop.cpp:240
        #3 0xef5a9a2b in $a.0 /proc/self/cwd/bionic/libc/arch-arm/bionic/__restore.S:48
        #4 0xef5dba37 in tgkill /proc/self/cwd/bionic/libc/arch-arm/syscalls/tgkill.S:9
        #5 0xef5ab511 in abort bionic/libc/bionic/abort.cpp:42 (discriminator 2)
        #6 0xef73b0a9 in __sanitizer::Abort() external/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cc:141
        #7 0xef73f831 in __sanitizer::Die() external/compiler-rt/lib/sanitizer_common/sanitizer_termination.cc:59
        #8 0xef72a117 in ~ScopedInErrorReport [asan_rtl]
        #9 0xef72b38f in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) [asan_rtl]
        #10 0xef72bd33 in __asan_report_load1 [asan_rtl]
        #11 0xaaaae937 in FuzzMe(unsigned char const*, unsigned int) tools/fuzzers/fuzz_me_fuzzer/fuzz_me_fuzzer.cpp:10
        #12 0xaaaaead7 in LLVMFuzzerTestOneInput tools/fuzzers/fuzz_me_fuzzer/fuzz_me_fuzzer.cpp:15
        #13 0xaaab8d5d in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned int) external/llvm/lib/Fuzzer/FuzzerLoop.cpp:515
        #14 0xaaab8f3b in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned int) external/llvm/lib/Fuzzer/FuzzerLoop.cpp:469
        #15 0xaaab9829 in fuzzer::Fuzzer::MutateAndTestOne() external/llvm/lib/Fuzzer/FuzzerLoop.cpp:701
        #16 0xaaab9933 in fuzzer::Fuzzer::Loop() external/llvm/lib/Fuzzer/FuzzerLoop.cpp:734
        #17 0xaaab48e5 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned int)) external/llvm/lib/Fuzzer/FuzzerDriver.cpp:524
        #18 0xaaab306f in main external/llvm/lib/Fuzzer/FuzzerMain.cpp:20
        #19 0xef5a8da1 in __libc_init bionic/libc/bionic/libc_init_dynamic.cpp:114

    SUMMARY: AddressSanitizer: heap-buffer-overflow
    ...
    ==28219==ABORTING
    MS: 1 CrossOver-; base unit: 10cc0cb80aa760479e932609f700d8cbb5d54d37
    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/tmp/fuzz_me_fuzzer/corpus/CRASH_ID

libFuzzer에 대한 자세한 내용은 업스트림 문서를 참조하세요. Android의 libFuzzer는 업스트림보다 몇 버전 이전입니다. 따라서 시도 중인 작업을 인터페이스에서 지원하는지 확인하려면 external/llvm/lib/Fuzzer를 검토해야 합니다.