Introduction

LibFuzzer는 in-pocess, coverage-guided, evolutionary fuzzing engine이다. LibFuzzer는 테스트 대상 라이브러리와 연결되며, 특정 fuzzing entrypoint를 통해 Fuzz 값을 넣어 fuzzing한다. Code Coverage를 최대화하기 위해 입력 데이터의 Corpus에 변형을 생성한다. LibFuzzer의 Code coverage는 LLVM의 SanitizerCoverage에 의해 측정된다.

LibFuzzer는 따로 seed를 입력하지 않는다. 그래서 라이브러리가 복잡하다면 효율성이 떨어진다.

Address Sanitizer

libFuzzer를 다루기 전에 AddressSanitizer(ASAN)에 대해 확실히 짚고 넘어가자.

AddressSanitizer는 C/C++ 파일에 대해 메모리 에러를 찾아주는 툴이다. 찾을 수 있는 에러는 다음과 같다.

  • Use after free
  • Heap buffer overflow
  • Stack buffer overflow
  • etc..

Using ASAN

gcc 가 아닌 clang 을 이용하여 컴파일을 하고 -fsanitize=address 라는 커맨드를 추가해줘야 한다. 간단한 코드를 예시로 들어봤다.

//uaf.c
#include <stdlib.h>
int main() {
  char *x = (char*)malloc(10 * sizeof(char*));
  free(x);
  return x[5];
}

free 후에 메모리를 참조하고 있다.

clang으로 단순히 컴파일을 하고 실행을 시켜보면 당연히 오류가 나지 않는다.

l0z1k@ubuntu:~/fuzzer$ clang++ -o uaf uaf.c
l0z1k@ubuntu:~/fuzzer$ ./uaf
l0z1k@ubuntu:~/fuzzer$ 

이번에는 ASAN을 붙여서 컴파일을 하고 실행시켜보자.

l0z1k@ubuntu:~/fuzzer$ clang++ -fsanitize=address -o uaf uaf.c
image

use-after-free가 발생했다는 것을 확인할 수 있다!

Getting Started

clang만 있으면 따로 필요한 건 없다.(아마도) 대신 6.0 이상 버전이어야 한다. 본인은 10.0 버전을 사용하여 실습하였다.

libFuzzer를 이용하는 것 또한 굉장히 간단하다. -fsanitize=fuzzer 라는 커맨드를 컴파일할 때 붙여주면 된다.

우선 Fuzzing할 대상을 구현해보자.

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size){
  DoSomethingInterestingWithMyAPI(Data, Size);
  return 0;
}

LLVMFuzzerTestOneInput 이라는 함수를 이용하여 fuzzing을 수행한다. fuzzer에서 자체적인 알고리즘을 통해 무작위로 Data를 생성한다.

bool FuzzMe(const uint8_t *Data, size_t DataSize) {
  return DataSize >= 3 &&
      Data[0] == 'F' &&
      Data[1] == 'U' &&
      Data[2] == 'Z' &&
      Data[3] == 'Z';
}

다음과 같이 Data를 입력받아 문자열을 비교하여 결과를 리턴해주는 함수가 있다. 언뜻보면 오류가 없을 것 같지만, DataSize가 3일때, Data[3]을 참조하게 되면서 문제가 발생한다. 이 함수를 libFuzzer로 퍼징해보자.

#include <stdint.h>
#include <stddef.h>
#include <stdio.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';
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  FuzzMe(Data, Size);
  return 0;
}

LLVMFuzzerTestOneInput()의 매개변수인 Data, Size는 계속해서 랜덤으로 들어간다. 그러다가 Size가 3이 될 때, 오류가 날 것이다.

$ clang++ -g -fsanitize=fuzzer,address -o fuzz fuzz_me.cc
$ ./fuzz
image image

보면 'FUZ'를 input으로 주었을 때 Heap Buffer Overflow가 발생한다고 한다. 'FUX'라면 Data[3]을 참조하지 않고 Data[2] 비교에서 False를 리턴할 것이기 때문이다.

+ Recent posts