확장 가능한 parallel fuzzing 인프라 구축 및 성능 비교 분석 (1-2) AFL++

OOSUlZ·2023년 2월 22일
0

2) AFL++

AFL(American Fuzzy Lop) 프로젝트에서 파생된 오픈소스 Fuzzing 툴입니다. AFL++은 AFL의 기능을 상속하면서도 성능 및 안정성을 개선하였습니다.

(1) AFL++ 특징

  1. Fuzzing 대상: Fuzzing의 대상은 input을 받으면서 실행되는 모든 코드들이다. 문서 리더기, pdf reader 등 입력을 받을 수 있는 모든 프로그램들이 대상이다.

  2. Source code의 유무: Fuzzing하고자 하는 대상 프로그램의 소스코드를 보유하고 있을 경우와 없는 경우로 나뉜다. 소스코드를 보유하고 있다면 afl을 통해 컴파일해서 fuzz 작업을 실행할 수 있다. 반면에 그렇지 않고 프로그램만 있는 경우, black box fuzzing을 할 수 있게끔하는 여러가지 mode들도 afl++에 준비되어 있다.

  3. AFL Compiler: 일반적인 c코드는 gcc, cpp코드는 g++로 컴파일이 되고, 이렇게 컴파일 된걸로 fuzzing할 수 있다. 하지만, 이렇게 하면 효율적이지 못한 경우가 발생한다. AFL++는 좀 더 효율적으로 fuzzing할 수 있게끔 자체적인 compiler를 여러 종류 지원한다.

    = afl-cc (가장 기본적인 컴파일러)
    = afl-clang
    = afl-clang-fast
    = afl-clang-lto (가장 빠른 컴파일러. 가능하면 이걸 사용한다.)
    = afl-gcc
    = afl-gcc-fast
    = afl-g++
    = afl-g++-fast
    = afl-lto
    = afl-lto++

    ++가 붙은 컴파일러들은 C++ 소스코드를 위한 컴파일러들이다. 이들을 모두 사용하려면 최소 clang/clang++ 11 버전 이상을 가지고 있어야 한다.

    💡 clang 이란? 흔히 C와 C++을 컴파일할 때 사용하는 컴파일러는 gcc와 g++이다. 하지만 이 컴파일러를 사용하는 경우, 그들의 규약에 따라 사용자는 사용하는 소스코드를 전부 공개해야 한다. 이 규약이 그렇게 달갑지 않은 많은 기업들은 다른 컴파일러를 찾아나섰고, 그것이 바로 clang/clang++이다. clang은 llvm project안에 속해 있는 컴파일러의 한 종류이다.
  4. Input Preparation: Fuzzing을 하기 위해서는 사용자가 넣고자 하는 input 파일들을 미리 준비해야 한다. 텍스트 파일, 이미지 파일, 동영상 파일 등, fuzzing하고자 하는 프로그램이 받을 수 있는 입력데이터에 해당하는 확장자면 다 괜찮다. 테스트해보고 싶은 모든 입력 파일들을 하나의 디렉토리에 넣으면 된다. AFL++에는 ./testcases 라는 디렉토리 안에 샘플들이 있어 이것들을 활용해도 된다

  5. afl-cmin: 입력 데이터가 많다고 좋은 게 아니다. 오히려 비슷한 결과물만 만들어내는 경우, 없느니만 못하다. afl-cmin을 활용해서 별로 도움이 되지 않는 input들을 걸러낼 수 있다.

(2) [Build] Configure , make , make install

configure

configure는 소스파일에 대한 환경설정을 해주는 명령어이다.
즉, 내게 필요한 도구가 다 있는지 라이브러리는 다 있는지 같은 것들을 체크해주고 빌드 환경을 만들어주는 스크립트이고, 옵션은 configure 뒤에 삽입 및 서버환경에 맞쳐 makefile을 생성해준다.
소스를 사용할 컴퓨터와 사용자의 환경에 맞게 내 컴퓨터는 어떤 기종이고 컴파일에 필요한 시스템 파일들은 어디에 위치해 있으며, 어떤곳에 설치를 하겠다고 지정을 하는 것이다.

예)
# ./configure --prefix = /usr/local/mysql 하게 되면 어떤 파일을 /usr/local/mysql 이라는 곳에 설치 하겠다는 뜻
-prefix 옵션 ⇒ 설치 디렉토리를 변경
설치할 때 옵션을 주지 않으면 기본으로 /usr/local/bin 이나 /usr/local/lib 밑에 설치하는데, 이 디렉토리들은 관리자 권한이 없이는 접근할 수 없음

configure 단계에서 에러를 내며 멈출 경우 보통은 빌더 환경이 잘 갖추어져 있지 않다는 것을 의미한다.
필요한 프로그램이나 라이브러리가 없는 경우가 대부분이므로, 오류 메세지나 빌드 문서를 잘 읽어보고 필요한 프로그램이나 라이브러리를 설치하면 된다.

configure 옵션을 잘못 주어서 다시 시작하고 싶을 때 make distclean이라고 치면 configure 설정을 모두 제거해주고 configure 부터 다시 시작해야 한다

make

make는 소스를 컴파일 하는 것 / make 과정이 끝나고 나면 설치파일(Setup 파일 같은 것)이 생성된다.
make에게 어떤 프로그램을 컴파일하고 링크해야 하는지 그 방법을 설명한 것이 makefile이다.
makefile ⇒ 소스파일의 의존성과 같은 필요한 명령 기술
매번 각 소스파일을 따로 명령어로 컴파일 하는 것도 매우 번거롭기 때문에, 프로그램의 빌드과정을 표준 문법으로 기술한 파일을 makefile이라고 부른다.

make가 실패하는 경우는 보통 컴파일 에러때문이다.
⇒ 이때는 컴파일 에러를 해결한 후 처음부터 다시 컴파일하는 것을 추천함

make clean 하면 컴파일이 취소되고, make를 치면 다시 컴파일할 수 있음

make install

make installmake를 통해 만들어진 설치파일(setup)을 설치를 하는 과정
즉, build된 프로그램을 실행 할 수 있게 파일들을 알맞은 위치에다가 복사를 한다.

make dep ⇒ 의존성 검사
커널 컴파일을 하기 전에 이것들의 소스들에 문제가 있는지 검사(설치에 필요한 것들이 제대로 있나 확인하는 과정)

이 과정을 이해하고, 빌드할 때 아래 과정처럼 AFL 컴파일러를 지정해야한다.

Configure build system: 리눅스를 통해 프로그램을 설치할 경우, wget을 써서 tar.gz 확장자 형태의 압축 파일을 그대로 들고 오는 경우가 많을 것이다. tar.gz의 압축을 해제하면 압축 해제된 파일 안에 **configure**라는 이름의 파일이 있는 경우가 많은데 이러한 프로그램을 configure build system들이라 한다. 원래는 configure를 실행하고 output directory만 명시해주면 알아서 프로그램을 설치해주지만, 우리는 해당 프로그램을 afl을 이용해 fuzzing해야 하기 때문에 이 과정에서 내가 사용할 afl compiler를 지정해줘야 한다.

예를 들자면,

./configure --prefix="$HOME/fuzzing_xpdf/install/"

이렇게 입력해서 프로그램을 일반적으로 설치한다. (뒤에 있는 —prefix=”경로”를 명시하면 프로그램 이 “경로”에 설치된다.)

여기에 afl compiler를 적용 시키려면 다음과 같이 입력한다.

CC=$HOME/AFLplusplus/afl-clang-fast CXX=$HOME/AFLplusplus/afl-clang-fast++ ./configure --prefix="$HOME/fuzzing_xpdf/install/"

앞에 CC와 CXX가 있고 바로 뒤에는 해당 compiler의 경로를 제시한다. 여기서 CC뒤에 오는 것은 C코드를 위한 컴파일러, CXX뒤에 오는 것은 C++코드를 위한 컴파일러를 각각 입력해줘야 한다. 가급적이면 둘다 입력해주는 것이 좋다. 위 예시에선 afl-clang-fast를 사용해서 해당 프로그램을 설치한다는 내용이다.

Other build systems: configure파일을 사용하는 프로그램도 있지만, 다른 빌드 시스템을 사용해서 설치하는 프로그램들도 있다. cmake에서 afl compiler를 적용하는 방법은 다음과 같다.

cmake의 경우:

mkdir build; cd build; cmake -DCMAKE_C_COMPILER=afl-cc -DCMAKE_CXX_COMPILER=afl-c++ ..
💡 cmake는 알아서 프로그램들간의 의존성과 관계 등을 파악해서 Makefile을 만들어주는 빌드 프로그램이다.
  1. Fuzzing: 위 준비 과정이 끝나면 아래와 같은 방법으로 afl-fuzz를 작동시킨다.
afl-fuzz -i input -o output -- bin/target -someopt @@
  • input: 사용자가 준비한 입력데이터들이 있는 디렉토리
  • output: 결과물을 저장하는 디렉토리
  • —bin/target: fuzzing하고자 하는 프로그램
  • -someopt: 여러 옵션을 설정할 수 있다.
  • @@: input이 여러개일 경우, afl-fuzz가 알아서 그들을 넘버링 할 수 있게끔 하는 옵션.

(3) ASAN (Address Sanitizer)

ASan (Address Sanitizer):
C/C++ 언어로 작성된 소프트웨어의 메모리 오류를 검출하기 위한 툴이다. ASan은 메모리 액세스 오버플로우, 메모리 누수, 사용 후 해제 등의 오류를 검출할 수 있으며, 이를 통해 소프트웨어의 안정성과 보안성을 향상시킬 수 있다.
configure와 make 그리고 make install 앞에 AFL_USE_ASAN=1 을 붙여서 활성화시킬 수 있다. crash에 대한 정보가 매우 자세하게 나온다.

AFL_USE_ASAN=1 CC=afl-clang-lto ./configure --prefix="경로"
AFL_USE_ASAN=1 make
AFL_USE_ASAN=1 make install

ASan을 사용하면 virtual memory를 많이 잡아먹기 때문에 afl-fuzz가 기본적으로 설정해 놓은 메모리 제한을 넘길 우려가 있다. 이를 방지하기 위해 afl-fuzz에 -m none을 붙여서 메모리 제한을 해제한다.

afl-fuzz -m none -i input -o output -- bin/target -someopt @@

(4) Parallelized Fuzzing

parallelized fuzzing은 Fuzz의 효율성과 속도를 높이기 위해 Fuzzer의 여러 인스턴스를 동시에 실행할 수 있는 AFL의 기능을 의미한다.

parallelized fuzzing에서 AFL은 input을 여러 부분으로 나누고 각 부분을 다른 Fuzz 프로세스에 할당한다. 그런 다음 각 프로세스는 독립적으로 실행되고 테스트 사례를 생성하며, 테스트 사례는 Fuzz 프로세스를 추진하기 위해 시스템에 다시 전달한다. 그런 다음 각 프로세스의 결과를 결합하여 테스트 중인 시스템의 동작에 대한 포괄적인 output을 생성한다.

이 접근 방식은 속도와 적용 범위를 높이고 효율성을 개선하며 버그와 취약성을 찾는 데 걸리는 시간을 단축하는 등 여러 가지 이점을 제공합니다. 또한 새로운 에지 사례를 식별하고 Fuzz 테스트 프로세스의 전반적인 효율성을 높이는 데 도움이 될 수 있다.

사용 예시)

-M -S 태그가 중요하다.

  1. MASTER FUZZER
afl-fuzz -m none -i ./afl_in -o afl_out -s 123 -x ./dictionaries/xml.dict -D -M master -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@

-x ⇒ 사전 경로 설정 -D ⇒ deterministic mutations 활성화

  1. SLAVE FUZZER
afl-fuzz -m none -i ./afl_in -o afl_out -s 234 -S slave1 -- ./xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@

(5) Custom Mutator

AFL++는 input값을 마음대로 변형시킨다. 하지만 경우에 따라서는 input의 형식을 지켜줘야 할 수도 있기 때문에 fuzzer에게 input의 구조를 이해할 수 있게 알려줘야 한다.

소스 코드 안에 정의를 내려야 할 함수들은 정해져 있다. C/C++ 기준 정의내려야 하는 메소드들이다.

void *afl_custom_init(afl_state_t *afl, unsigned int seed);
unsigned int afl_custom_fuzz_count(void *data, const unsigned char *buf, size_t buf_size);
size_t afl_custom_fuzz(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf, unsigned char *add_buf, size_t add_buf_size, size_t max_size);
const char *afl_custom_describe(void *data, size_t max_description_len);
size_t afl_custom_post_process(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf);
int afl_custom_init_trim(void *data, unsigned char *buf, size_t buf_size);
size_t afl_custom_trim(void *data, unsigned char **out_buf);
int afl_custom_post_trim(void *data, unsigned char success);
size_t afl_custom_havoc_mutation(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf, size_t max_size);
unsigned char afl_custom_havoc_mutation_probability(void *data);
unsigned char afl_custom_queue_get(void *data, const unsigned char *filename);
u8 afl_custom_queue_new_entry(void *data, const unsigned char *filename_new_queue, const unsigned int *filename_orig_queue);
const char* afl_custom_introspection(my_mutator_t *data);
void afl_custom_deinit(void *data);

저 메소드들을 전부 구현하면, 일반적인 컴파일을 해주고, 결과물은 .so (shared object)확장자여야 한다.

컴파일이 끝나면, afl-fuzz를 하기전에 위에서 컴파일한 파일의 경로를 환경변수에 지정시켜주면 된다.

export AFL_CUSTOM_MUTATOR_LIBRARY="/full/path/to/컴파일된.so파일"
afl-fuzz # 원래 하던 fuzzing 그대로...

(6) AFL++ 설치 및 실습

dependencies 설치

sudo apt-get update sudo apt-get install -y build-essential python3-dev automake git flex bison libglib2.0-dev libpixman-1-dev python3-setuptools sudo apt-get install -y lld-11 llvm-11 llvm-11-dev clang-11 || sudo apt-get install -y lld llvm llvm-dev clang sudo apt-get install -y gcc-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-plugin-dev libstdc++-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-dev

AFL++ 빌드

cd $HOME git clone https://github.com/AFLplusplus/AFLplusplus && cd AFLplusplus export LLVM_CONFIG="llvm-config-11" make distrib sudo make install

정상적으로 설치시, terminal에 afl-fuzz 을 입력하면 다음과 같이 출력되면 설치 완료이다.
작동방법은 AFL에서 사용했던 것과 같이 커맨드를 입력하면, 사용이 가능하다.

예시로, 소스코드는 디렉토리에서 주어진 test-instr.c를 활용했고, test input은 마찬가지로 testcases 폴더 내에 있는 others 폴더에 있는 것들을 써서 fuzz를 시도하였다.

제대로 작동하고 있는 모습:

profile
천천히,꾸준하게

0개의 댓글