History of fuzz test
1988년 Wisconsin 대학교의 - Barton Miller 교수는 전화 접속 시스템을 사용하여 UNIX 시스템에 접속하려 했지만 뇌우로 인한 피드백으로 프로그램이 계속 다운되었다. 외부의 noise가 프로그램에 영향을 준다는 것은 밀러에게 영감을 주었고 그들은 UNIX 프로그램이 예기치 않은 임의의 입력에 의해 ping될 때 crash 를 일으킨다는 것을 발견했다. Miller 는 Fuzzing for Software Security Testing and Quality Assurance 의 저자 중 한명이다.
What is fuzzing?
- 프로그램이 가지고 있는 잠재적인 취약점을 식별하는데 사용되는 소프트웨어 테스트 기법
- fuzzing을 통해 프로그램에 들어가는 입력(표준입력 or 파일) 을 변조시켜 정상적인 동작이 아닌, 크래시를 유발하거나 메모리 corruption을 일으키는 테스트 사례를 기록할 수 있고, 해당 사례를 분석하여 취약점을 트리거 시킬 수 있다.
- 응용프로그램, 커널, 등등이 전부 fuzzing의 대상이 될 수 있다.
A trivial example
사용자가 3개의 질문 중 선택한 결과를 저장하는 프로그램을 생각해보자.
사용자는 0, 1, 2 중 하나를 선택할 수 있다. 이는 정상적인 동작의 사례가 된다.
그러나 만약 3또는 255를 전송한다면, 정수는 정적 크기 변수로 저장되기 때문에 보낼 수 있다.
이 경우 예외 처리가 되어 있지 않다면 프로그램이 중단되어 고전적인 보안문제로 이어질 수 있다.
fuzzing 은 automatic bug finding 기술로, 소프트웨어 구현 오류를 찾아내고 가능하면 식별하는 역할을 한다.
Why fuzzing
- fuzzing 을 통해 버그를 자동으로 찾을 수 있다 .
- 정적 분석을 통해서도 버그를 찾을 수 있지만, 많은 시간과 노력이 들고, 자신의 실력에 따라 버그를 찾는게 달라진다.
- 분석하려는 애플리케이션의 구조가 매우 복잡할 수록 안에서 터질수 있는 버그가 더 많을 것이고, 복잡성이 증가하면 attack surface 도 당연히 늘어날 것이다. 이런 복잡한 애플리케이션을 정적으로 분석하는 것은 실력있는 분석가와 많은 시간이 필요하다. fuzzing이 효율적일 수 있다.
- 퍼징은 비용과 시간 모두에서 낮은 오버헤드를 가진다. 일단 퍼저가 가동되면, 수동/사람의 개입 없이 스스로 버그를 찾을 수 있고, 필요한 만큼 계속 버그를 찾을 수 있다.
Common issues with fuzzing
- 퍼징이 좋은 기술인건 확실하다. 그러나 만능은 아니다. fuzzing을 돌리기 위해 필요한 설정들을 세팅하는데 많은 시간이 걸리고, 입력되는 파일의 구조르 인식하여 퍼징을 하게 되는 경우, 입력되는 파일의 구조를 일일이 분석해야 하기 때문
- 퍼저가 모든 취약점을 다 찾을 수는 없다.
- 크래시가 난다 해도, 오탐일 수도 있고, 악용 불가능한 버그일 수도 있다.
- 그렇기에 검토 프로세스가 필요하다.
Two types of fuzzing
퍼징은 coverage-guided 와 behavioral의 두가지 유형이 있다.
coverage-guided fuzzing은 프로그램이 실행되는 동안 소스 코드에 초점을 맞추고 버그를 발견하기 위해 무작위 입력을한다. 새로운 test case 가 계속해서 생성되며 프로그램을 crashed 시키는게 목표이다. 이 crash 는 잠재적인 취약점을 의미하며, logging된 test cases 를 통해 이 crash를 재현할 수 있으므로 취약한 코드를 식별하려고 할 때 유용하다.
Behavioral fuzzing 은 다르게 작동한다. 프로그램이 어떻게 작동해야 하는지 보기 위해 사용하며 임의의 입력을 사용하여 프로그램이 실제로 어떻게 작동하는지 판단한다. 이 역시 버그나 기타 잠재적인 보안 위험을 발견할 수 있다.
Architecture of a typical fuzzer
일반적인 퍼저의 구성요소는 아래와 같다.
- Test Case Generator : fuzzing될 프로그램에 공급하기 위한 입력을 생성
- Worker : 주어진 입력으로 프로그램을 실행하고 예상치 못한 동작을 인식
- Logger : 흥미로운 테스트 케이스와 버그를 분석하기 위해 필요한 모든 것들을 기록
- Server / Master : 다른 세 부분을 조정하고 그들 사이의 통신을 관리
Test Case Generator
Fuzzing될 프로그램에 공급하기 위한 테스트 케이스를 생성한다. 정상적인 입력 파일을 mutate 시켜 테스트 케이스를 생성한다. 이때 입력 파일의 구조를 분석하여 그 구조에 맞는 테스트 케이스를 생성하는 것을 smart fuzzer, 무작위 하게 mutate 시키는 것을 dumb fuzzer 라고 한다.
Worker
테스트 케이스가 제공한 테스트 케이스를 실행시킨다. 예상치 못한 동작을 인식해야 한다. 메모리 손상과 같은 쉽게 탐지할 수 있는 버그 외에 에러가 나지 않는 버그의 경우 해당 상황에 조건을 걸어 의도적으로 에러 메시지를 띄워주는 sanitizer를 사용할 수 있다.
Logger
발견된 모든 충돌과 해당 테스트 케이스를 기록하거나 저장한다. 퍼저가 coverage-guided 인 경우, 새로운 code-coverage 를 찾은 테스트 케이스도 저장할 수 있다. logger를 사용하면 crash의 stack trace 를 사용하여 충돌을 쉽게 분석할 수 있다. 또한 logging된 test cases를 통해 충돌을 재현할 수 있다는 이점도 있다.
Guided Fuzzing
테스트 케이스 생성기에 의해 생성되는 테스트 케이스는 품질 면에서 큰 차이가 있다.
따라서 Test Case Generator 는 더 효과적인 test case를 mutate 시킬 필요가 있다.
가장 일반적인 방법으로는 code-coverage 이다.
이론적으로 퍼저가 더 많은 code coverage 에 도달할 수록 더 많은 버그를 발견하게 된다.
Dumb Fuzzing
새로운 테스트 케이스를 생성하기 위해 입력 파일의 구조가 필요하지 않은 Test Case Generator 를 dumb fuzzer 라고 한다. dumb fuzzer의 장점은 입력 구조에 대한 정보가 필요 없기 때문에 많은 조정 없이 다양한 프로그램을 퍼징하기에 적합하다는 것이다. 다만 대부분의 입력이 미리 정의된 구조를 필요로 하거나 checksum 을 포함하고 있으며 유효한 입력을 생성하는데 어려움을 겪을 것 이라는 것을 예상할 수 있다.
Smart Fuzzing
Test Case Generator 가 입력 파일의 구조를 인식한다. 입력 파일의 구조를 입력 모델이라고 하고 이는 프로그래밍 언어의 문법이거나 데이터 형식 모델일 수 잇다.
스마트 퍼저의 장점은 주로 유효한 입력 파일을 생성하기 대문에 더 높은 code coverage 로 이어질 수 있다.
그러나 스마트 퍼저는 일반적으로 특정 유형의 입력에 특화되어 있으며 버그를 유발할 가능성이 있는 test case가 필요하다.
Mutation Based Fuzzing
변이 기반 퍼저는 알려진 테스트 케이스를 변형하여 새로운 테스트 케이스를 만든다.
일반적으로 비트 플립 또는 랜덤화를 사용한다. Mutation Based Fuzzing의 중요한 점은 mutate를 일으킬 흥미로운 testcase를 선택하는 것이다.
Generation Based Fuzzing
처음부터 새로운 테스트 케이스를 생성한다. 입력 파일의 구조를 알아야 하며, 그렇지 않으면 단순히 무작위 바이트를 생성하게 된다.
일반적으로 설정 작업이 필요하며 특정 입력 유형에 특화되어 있다. Mutation Based Fuzzer 보다 더 많은 code coverage 를 생성하는 경향이 있기 때문에 뚜렷한 버그를 발견할 가능싱이 더 높다.
Test Case Minimization
mutation에 의해 생성된 test case 는 시간이 지남에 따라 크기와 복잡성이 증가하는 경향이 있다.
이로 인해 실행 시간이 길어지고 흥미로운 test case를 분석하는 것이 어려워 진다.
test case 가 작을 수록 fuzzing된 소프트웨어의 흥미로운 부분에 초점을 맞추는 것이 좋다. 다라서 test case 를 최소화할 수 있어야 한다.
Test Case Minimization의 목표는 초기 테스트 케이스가 트리거한 것과 동일한 동작을 트리거하면서 가능한 가장 작은 테스트 케이스를 찾는 것이다.
Exposing bugs
앞서 기술했듯이 버그를 노출하려면 퍼저가 정상동작과 예상치 못한 프로그램 동작을 구별할 수 있어야 한다.
하지만 fuzzer가 항상 이를 구별할 수는 없다.
자동화된 소프트웨어 테스트에서는 이를 test orcale problem 이라고도 한다.
일반적으로 fuzzer는 메모리 손상과 같은 버그는 탐지가 쉽다. 하지만 logic 버그는 쉽게 탐지하기 어렵다.
여러가지 sanitizer 를 사용해서 아래와 같은 버그들을 찾기 쉽게 만들 수 있다.
- buffer overflow, uaf ( using memory debugger such as AddressSanitizer)
- race condition, deadlocks ( ThreadSanitizer)
- undefined behavior ( UndefinedBehaviorSanitizer)
- memory leaks ( LeakSanitizer)
- control-flow integrity ( CFISanitizer)
자동으로 버그의 취약점 등급을 분류해주는 !exploitable 라는 툴도 있는 듯 하다.
- Exploitable
- Probably Exploitable
- Probably Not Exploitable, or
- Unknown.
의 등급으로 분류해준다고 한다.
Attack types
퍼저는 아래와 같은 공격 조합을 시도한다.
- numbers (signed, unsigned integers/ float …)
- chars (urls, command-line inputs)
- metadata : user-input text (id3 tag)
- pure binary sequences
fuzzing에 대한 일반적인 접근 방식은 각 유형에 대해 “위험할 것으로 알려진 값” (fuzz vector) 의 목록을 정의하고 이를 주입하거나 재조합하는 것이다.
- 정수의 경우 : 0, 음수일 수 있고, 매우 큰 숫자일 수도 있다.
- 문자의 경우 : escaped, interpretable characters / instructions ( ex : For SQL Requests, quotes / commands … )
- 바이너리의 경우 : random ones
Fuzzing Tools
- Sanitizers : compile 때 프로그램에 조건을 삽입하는 충돌 탐지 도구 ( ASNA, TSAN, MSAN, UBSan등,, )
- Radamsa : General Purpose dumb mutation based fuzzer, 설치와 사용이 용이
- AFL : 성능 좋은 coverage-guided, mutation based dumb fuzzer. dumb fuzzer 지만 지능형 설계로 인해 대부분 유효한 입력
- Peach Fuzzer : 유연한 퍼징 프레임, Smart Fuzzer 도는 dumb fuzzer 일 수 있음. 거의 모든 것을 퍼징할 수 있음.
- honggfuzz : feedback 중심의 진화적인 퍼저, coverage-guided 외 여러 feedback 방법을 지원,
- libFuzzer : 진화적 테스트 케이스 생성을 사용하는 coverage-guided fuzzer, in-process fuzzing
- domato : DOM fuzzer,기술적으로 도마토는 퍼저가 아니라, HTML, CSS, 자바스크립트만 생성할 수 있기 때문에 test case generator 이다.
- sqlmap : 충돌을 찾지 않는 퍼저의 예, SQL injection flaws 의 탐지 및 악용을 위한 침투 테스트 도구로 사용된다.
List of popular fuzzers
아래 글을 참조했습니다.
문제시 삭제조치 하도록 하겠습니다.
https://labs.withsecure.com/publications/what-the-fuzz
https://jeongzero.oopy.io/b95dc996-7d32-4933-921d-045bbdfe2e41
https://www.synopsys.com/glossary/what-is-fuzz-testing.html
https://en.wikipedia.org/wiki/Fuzzing
https://about.gitlab.com/topics/devsecops/what-is-fuzz-testing/