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

OOSUlZ·2023년 2월 22일
0
post-thumbnail

0. 서론

Fuzzing은 프로그램에 무작위의 데이터를 삽입하여 버그 및 취약점을 찾아주는 기술이다. 예를 들어, 프로그램에 들어가는 입력(표준입력 or 파일)을 변조시켜 정상적인 동작이 아닌, crash를 유발시키거나 메모리 corruption을 일으키는 테스트 사례를 찾는다.
Fuzzing 기술은 여러 취약점을 찾는데 아주 유용한 툴로써 최근에 활발하게 연구개발이 진행되어 왔다. 이 프로젝트는 fuzzing 기술을 적용하여 전에 알려지지 않았던 여러 open-source 소프트웨어의 버그 및 실제 취약점을 찾고, 패치를 생성하여 community에 제출하는 과제를 수행한다.


1. AFL && AFL++

Fuzzing는 프로그램 혹은 메모리 스택에 자동으로 반무작위 데이터를 주입하고 버그를 탐지하는 Fuzzer라는 소프트웨어 테스트 도구로 수행된다. 이 중 주로 AFL++라는 Fuzzer를 활용하여 여러 오픈소스 소프트웨어를 Fuzz하여 버그를 찾아볼 계획이다. AFL++는 AFL의 확장판이므로 AFL를 먼저 알아볼 필요가 있다.


1) AFL

AFL은 a novel type of compile-time instrumentation and genetic algorithms 과 결합된 퍼저이다. 또한, 코드 커버리지 기반(edge coverage를 기반으로 feedback), 뮤테이션 기반 (Dumb fuzzer) 이다. 브루드포스로 입력을 받지만, 거기서 끝나는게 아니라 커버리지를 넓혀가며 프로그램 제어 흐름에 대한 변경사항을 기록하고, 이를 로깅하여 유니크한 크래시를 발견한다.

(1) AFL 특징

  • 커버리지 기반 퍼저이기 때문에 매우 효율적인 퍼징이 가능하다
  • 코드 커버리지 측정을 위한 코드를 컴파일 타임에 삽입한다
  • 입력 구조에 대한 분석을 하지 않아도 되기 때문에 쉽고 빠르게 구현이 가능하다
  • 입력에 미리 정의되어있는 구조가 필요하거나 체크섬이 포함되어 있는 경우 유효한 입력을 생성하는데 어려움이 존재한다

(2) Code Coverage (코드 커버리지)

코드 커버리지가 넓다는 의미는 바이너리 내부의 여러 로직이 존재하는데, 생성되는 testcase를 통해 많은 내부 로직을 거치게 하는 것을 뜻한다.
이론적으로 코드 커버리지가 넓을수록 더 많은 버그를 찾을 수 있다.
edge coverage: 일반적인 코드 커버리지는 각각의 basic block이 실행이 되는지로 측정하나, edge code coverage는 basic block이 진화되는 것을 기반으로 측정
⇒ 더욱 사소하게 다음 실행 추적이 가능하며, 기본 코드에서 미묘한 오류 조건을 발견하는데 도움이 됨 (보안 취약점은 예상치 못한 문제 또는 새로운 basic block에 잘못되게 전환되는 경우와 연관되는 경우가 더 많기 때문)

(3) 뮤테이션

뮤테이션 테스팅 절차

1) 원본 프로그램을 일정한 변경 규칙에 따라 변경하여 여러 변형된 프로그램 버전을 생성한다. 원본 프로그램의 구문(syntax)을 변경하는 사전 정의된 변경 규칙은 ‘뮤테이션 오퍼레이터(Mutation operator)’라고 하며, 뮤테이션 오퍼레이터를 적용하여 생성된 원본과 약간 달라진 프로그램 버전을 ‘뮤턴트(mutant)’라고 부른다. 원래의 소스 코드가 개발자가 의도한 정확한 버전의 프로그램이라 가정했을 때 각 뮤턴트는 잘못 프로그래밍된 오류 버전(가상의 결함이 포함되어 있는 프로그램)을 나타낸다.

위 그림처럼 적용하는 뮤테이션 오퍼레이터의 수와 타입에 따라 하나의 원본 프로그램에 대해 무수히 많은 수의 뮤턴트가 생성된다. 각 뮤턴트는 한번에 단 한 개의 에러만을 포함하고 있다.

2) 기 준비된 일련의 테스트 케이스 집합(테스터가 주관적으로 도출했거나 또는 타 테스트 케이스 도출 기법을 적용하여 도출한 테스트 케이스들)으로 원본 프로그램과 뮤턴트를 실행하여 실행 결과를 비교한다.

💡 주의! 기 준비된 테스트 케이스들이 원본 프로그램 자체를 제대로 실행하지 못하거나 그 결과가 부정확하거나 하는 문제가 있으면 안 된다.

뮤턴트를 실행 하기에 앞서 이런 문제가 해결된 테스트 케이스들이 준비되어 있어야 한다.

⇒ 뮤턴트의 실행 결과가 원본 프로그램의 실행 결과와 다르면 현 테스트 케이스로 뮤턴트 프로그램을 구별해 낼 수 있다는 의미

Ex) 뮤테이션 방법 예시 ⇒ 비트플립, 랜덤화

Mutation 기반 퍼저는 입력값에서 무작위로 선택한 바이트(Byte) 또는 비트(Bit)를 변경하거나 삭제하여 새로운 입력값을 생성하고, 이렇게 생성된 입력값은 프로그램에 제공되어 실행된다. 실행 중에 오류가 발생하면 해당 입력값은 실패(Failure)로 분류되고, 그렇지 않으면 성공(Success)으로 분류된다.

여기서 가장 중요한 것은 어떠한 알려진 테스트케이스를 선택하느냐이다.
만약 이미지 뷰어 프로그램을 타겟으로 png 파일을 테스트케이스로 생성하고, 이를 기준으로 뮤테이션을 한다해도, 해당 파일이 jpg 파일을 처리하는 코드에 도달할 가능성이 없다.
⇒ 따라서 파일 형식도 잘 파악해서 테스트케이스를 선택할 필요가 있다.

(4) Dumb Fuzzer

입력값을 생성할 때, 아무런 분석도 하지 않고 무작위로 입력값을 생성합니다. 이 방식은 mutation 기반 퍼저에 비해 효율성은 떨어지지만, 퍼징(Fuzzing) 전반에 걸쳐서 사용되는 테스트 벡터(Test Vector)를 다양하게 만들어 줄 수 있습니다.

= 새로운 테스트 케이스를 생성하기 위해 입력 구조의 모델이 필요하지 않는다.
= 데이터 형식이나 입력 구조에 대한 명확한 이해가 어려울 때 사용한다.
⇒ 타겟 프로그램에 대한 이해도 불필요
= 입력에 대한 정보를 몰라도 되기때문에 간편하다.
= 만약 건드리면 안되는 헤더나 체크섬같은 것을 변조시키면 유효한 입력을 생성 하지 못한다.

(5) 작동 절차 및 기타 세부 사항

작동 절차

  1. 사용자가 test case를 만들어서 입력하면 각 test case들을 queue에 넣는다
  2. Queue에서 test case하나를 poll한다
  3. Test case를 최대한 단순화한다
  4. 여러 전통적인 fuzzing방식들을 활용해서 test case를 변형한다
  5. 변형된 test case가 code coverage를 증가시킬 경우, 해당 변형된 test case를 queue에 추가한다
  6. 2번으로 다시 되돌아간다
  7. 지금까지 발견된 test case들 중 오래되어서 별로 영향이 없는 것들을 제거한다. (정확히 추려낸다)

AFL의 부산물로 코드의 오작동을 일으킨 test case들은 스트레스 테스트(어디까지 버티는지 test해보는 작업)에 활용될 수 있다.

**TestCase 유의사항**

  1. Test case가 들어있는 파일의 용량을 작게 한다. 1kB 미만이 이상적이다.

  2. 각 test case는 완전히 다른 유형이어야 한다. 같은 유형의 test case는 어차피 변형을 통해 다양해질 것이기 때문이다.

(6) AFL 설치 및 실습

Ubuntu 16.04 64비트를 기준으로 설치하였다

  1. AFL을 git에서 clone한다
git clone https://github.com/google/AFL.git
  1. clone 후 AFL/qemu_mode 디렉토리로 이동한다
cd AFL/qemu_mode
  1. 다음 줄을 실행한다.
./build_qemu_support.sh

여기서 추가로 설치하라고 알려주는 프로그램들이 있다. libtool, glib2, bison 등 모두 설치해주면 된다.

모두 설치가 끝나고 나서 python의 버전이 3 이상일 경우 build가 안된다고 한다.

sudo update-alternatives --config python

위 줄을 쓰면 파이썬 컴퓨터에 설치되어 있는 여러 파이썬 버전들을 사용자가 조정할 수 있다. 다만 처음에는 설치된 python 각 버전을 update-alternatives에 등록 해야 한다. Python까지 2.7로 맞추면 build가 완료된다.

  1. AFL로 도로 나간다
cd ..
  1. 아래 줄을 실행하여 make install을 한다.
make install
  1. 마지막으로 테스트 파일을 통해 fuzz를 실시한다.

./afl-fuzz -i ./testcases/others/text -o .out ./test-instr_qemu

작업을 끝내고 싶다면, ctrl+c를 누르면 종료된다.

  1. 끝내면 output directory에 다음과 같이 crashes, hangs, 그리고 queue가 있는 걸 볼 수 있다.

= queue/ - test case들의 묶음
= crashes/ - 프로그램을 crash시킨 test case들의 모음
= hangs/ - Timeout된 test case들의 묶음
Timeout 시간의 기준은 따로 정해줄 수 있다.


profile
천천히,꾸준하게

0개의 댓글