
전체 시스템 에뮬레이션을 지원하는 AFL/QEMU 퍼징.
새 소식: TriforceAFL과 TLSF를 사용해보려는 분들을 위해 Richard Johnson이 두 가지를 모두 설치해 주는 Dockerfile을 만들었습니다(심지어 Linux 커널도 빌드해 줍니다). https://hub.docker.com/r/moflow/afl-triforce/tags/에서 확인할 수 있습니다.
또한 새 소식: afl-tmin이 이제 forkserver와 함께 작동합니다!
https://github.com/nccgroup/TriforceAFL Jesse Hertz [email protected] Tim Newsham [email protected]
이것은 QEMU를 사용한 전체 시스템 퍼징(full-system fuzzing)을 지원하는 AFL의 패치 버전입니다. 포함된 QEMU는 x86_64용 시스템 에뮬레이터 실행 시 브랜치 추적이 가능하도록 업데이트되었습니다. AFL의 forkserver를 시작하고 퍼즈 설정을 지정하며 테스트 케이스의 시작과 종료를 표시하는 추가 명령어가 포함되어 있습니다.
참고: 모든 AFL 도구가 새로운 변경 사항으로 테스트된 것은 아닙니다. 다음 도구들은 일부 테스트가 수행되었습니다:
빌드하려면:
make
커버리지 맵을 얻으려면:
echo hello > /tmp/hello
./afl-showmap -o coverage.txt -QQ --
./afl-qemu-system-trace -kernel ../bzImage
-initrd ../initramfs.cpio.gz -m 1G -nographic
-append "console=ttyS0" -aflFile /tmp/hello
cat coverage.txt
퍼징하려면:
egrep ' (panic|log_store)$' ../mykern/kallsyms ffffffff8108e570 t log_store ffffffff8181064b T panic
mkdir inputs
echo hello > inputs/hello
./afl-fuzz -i inputs -o outputs -QQ --
afl-qemu-system-trace -kernel bzImage -initrd root.cpio.gz
-m 1G -nographic -append "console=ttyS0"
-aflPanicAddr ffffffff8181064b -aflDmesgAddr ffffffff8108e570
-aflFile @@
(참고: "-Q" 옵션을 사용할 때와 달리, "-QQ" 옵션을 사용할 때는 afl-qemu-system-trace에 전체 명령줄을 지정해야 합니다).
이 수정된 AFL 버전 사용에 대한 자세한 내용은 https://github.com/nccgroup/TriforceLinuxSyscallFuzzer 의 Linux syscall 퍼저를 참조하십시오.
새 AFL 플래그: -QQ - 사용자 모드(-Q) 대신 전체 시스템 에뮬레이션에서 qemu 사용
새 QEMU 플래그: -aflFile - 퍼저 입력이 포함된 파일의 이름 -aflPanicAddr - 패닉 감지를 위한 커널 패닉 주소 -aflDmesgAddr - 로깅 감지 및 로그 메시지 가로채기를 위한 dmesg 로깅 함수의 Linux 커널 주소
새 QEMU 명령어: 0f 24 - aflCall edi=1 startForkserver(esi=enableTicks) Start AFL's fork server. After this point each test will run in a separate forked child. If enableTicks is non-zero, QEMU will re-enable the CPUs timer after forking a child, otherwise it will not be enabled. edi=2 getWork(esi=ptr, edx=sz) Fill ptr[0..sz] with the next input test case. Returns the actual size filled (<= sz). edi=3 startWork(esi=ptr) Tell AFL to start tracing. The argument points to a buffer with two quadwords giving the start and end address of the code to trace. Instructions outside of this range are not traced. edi=4 doneWork(esi=exitCode) Tell AFL that the test case has completed. If a panic is detected, AFL will stop the test case immediately. Otherwise it will run until doneWork is called. The exitCode specified is returned to AFL. (The code can, but currently does not, OR in the value 64 to all exit codes if any dmesg logs were detected during the test case.)
새 QEMU 블록 드라이버: -drive filename=privmem: 이 블록 드라이버는 드라이브 이미지를 copy-on-write 메모리에 유지하므로 변경 사항이 디스크에 영구 저장되지 않습니다. 한 테스트 케이스가 수행한 변경 사항은 다른 테스트 케이스와 격리됩니다.
작성 및 유지보수: Michal Zalewski [email protected]
저작권 2013, 2014, 2015, 2016 Google Inc. 모든 권리 보유. Apache License, Version 2.0의 약관 및 조건에 따라 배포됩니다.
새 버전 및 추가 정보는 다음에서 확인하십시오: http://lcamtuf.coredump.cx/afl/
다른 사용자와 의견을 교환하거나 주요 새 기능에 대한 알림을 받으려면 [email protected]로 메일을 보내십시오.
** 이 파일을 읽을 시간이 없다면 QuickStartGuide.txt를 참조하십시오. **
퍼징(Fuzzing)은 실제 소프트웨어에서 보안 문제를 식별하는 가장 강력하고 검증된 전략 중 하나입니다. 현재까지 보안이 중요한 소프트웨어에서 발견된 원격 코드 실행 및 권한 상승 버그의 대부분은 퍼징 덕분입니다.
불행히도 퍼징은 비교적 얕습니다. 맹목적인 무작위 변이는 테스트 대상 코드의 특정 코드 경로에 도달할 가능성을 매우 낮추며, 일부 취약점은 이 기법의 범위를 완전히 벗어나게 됩니다.
이 문제를 해결하려는 시도는 많았습니다. 초기 접근 방식 중 하나인 코퍼스 증류(corpus distillation)는 Tavis Ormandy가 개척했습니다. 이 방법은 커버리지 신호를 사용하여 방대하고 고품질의 후보 파일 코퍼스에서 흥미로운 시드의 하위 집합을 선택한 다음 전통적인 방식으로 퍼징합니다. 이 접근 방식은 매우 효과적이지만, 그러한 코퍼스가 readily 제공되어야 합니다. 게다가 블록 커버리지 측정은 프로그램 상태에 대한 매우 단순한 이해만 제공하므로 장기적으로 퍼징 노력을 안내하는 데는 덜 유용합니다.
그 외에도 더 정교한 연구는 프로그램 흐름 분석("concolic execution"), 기호 실행(symbolic execution), 정적 분석(static analysis)과 같은 기법에 초점을 맞추었습니다. 이러한 방법들은 실험 환경에서는 매우 유망하지만, 실제 사용에서는 신뢰성과 성능 문제가 따르는 경향이 있으며, 현재로서는 "단순한(dumb)" 퍼징 기법에 대한 실용적인 대안을 제공하지 못합니다.
American Fuzzy Lop은 매우 단순하지만 견고한 계측 기반 유전 알고리즘(instrumentation-guided genetic algorithm)과 결합된 무차별 대입(brute-force) 퍼저입니다. 변형된 형태의 엣지 커버리지(edge coverage)를 사용하여 프로그램 제어 흐름의 미묘하고 국지적인 변화를 쉽게 감지합니다.
약간 단순화하면 전체 알고리즘은 다음과 같이 요약할 수 있습니다:
사용자가 제공한 초기 테스트 케이스를 큐에 로드한다.
큐에서 다음 입력 파일을 가져온다.
프로그램의 측정된 동작을 변경하지 않는 가장 작은 크기로 테스트 케이스를 줄이도록 시도한다.
균형 잡히고 잘 연구된 다양한 전통적인 퍼징 전략을 사용하여 파일을 반복적으로 변이시킨다.
생성된 변이 중 하나라도 계측에 의해 기록된 새로운 상태 전이를 초래한 경우, 변이된 출력을 큐의 새 항목으로 추가한다.
2로 이동한다.
발견된 테스트 케이스는 또한 주기적으로 정리되어 더 새롭고 커버리지가 높은 발견으로 대체된 항목을 제거하며, 계측 기반의 여러 다른 노력 최소화 단계를 거칩니다.
퍼징 과정의 부산물로 이 도구는 흥미로운 테스트 케이스로 구성된 작고 자족적인 코퍼스를 생성합니다. 이는 노동 또는 리소스 집약적인 다른 테스트 방식의 시드로 매우 유용합니다. 예를 들어 브라우저, 오피스 애플리케이션, 그래픽 제품군 또는 클로즈드소스 도구의 스트레스 테스트에 사용할 수 있습니다.
이 퍼저는 맹목적 퍼징이나 커버리지 전용 도구보다 훨씬 뛰어난 기본 성능을 제공하도록 철저히 테스트되었습니다.
소스 코드가 제공되는 경우, 타사 코드의 표준 빌드 프로세스에서 gcc 또는 clang을 대체하여 사용할 수 있는 동반 도구로 계측을 주입할 수 있습니다.
이 계측은 성능 영향이 상당히 작습니다. afl-fuzz가 구현한 다른 최적화와 결합하면 대부분의 프로그램을 전통적인 도구로 가능한 것과 같거나 더 빠른 속도로 퍼징할 수 있습니다.
대상 프로그램을 다시 컴파일하는 올바른 방법은 빌드 프로세스의 세부 사항에 따라 다를 수 있지만, 거의 모든 경우에 적용되는 접근 방식은 다음과 같습니다:
$ CC=/path/to/afl/afl-gcc ./configure $ make clean all
C++ 프로그램의 경우 CXX=/path/to/afl/afl-g++도 설정해야 합니다.
clang 래퍼(afl-clang 및 afl-clang++)도 같은 방식으로 사용할 수 있습니다. clang 사용자는 llvm_mode/README.llvm에 설명된 대로 더 높은 성능의 계측 모드를 활용할 수도 있습니다.
라이브러리를 테스트할 때는 stdin 또는 파일에서 데이터를 읽어 테스트 대상 라이브러리에 전달하는 간단한 프로그램을 찾거나 작성해야 합니다. 이 경우 이 실행 파일을 계측된 라이브러리의 정적 버전에 링크하거나, 런타임에 올바른 .so 파일이 로드되도록 해야 합니다(일반적으로 LD_LIBRARY_PATH를 설정). 가장 간단한 옵션은 정적 빌드이며, 일반적으로 다음을 통해 가능합니다:
$ CC=/path/to/afl/afl-gcc ./configure --disable-shared
'make' 호출 시 AFL_HARDEN=1을 설정하면 CC 래퍼가 단순한 메모리 버그를 더 쉽게 감지할 수 있도록 코드 하드닝 옵션을 자동으로 활성화합니다.
참고. ASAN 사용자는 중요한 주의 사항을 위해 notes_for_asan.txt 파일을 검토하는 것이 좋습니다.
소스 코드를 사용할 수 없는 경우, 퍼저는 블랙박스 바이너리의 빠른 실시간 계측(on-the-fly instrumentation)에 대한 실험적 지원을 제공합니다. 이는 잘 알려지지 않은 "사용자 공간 에뮬레이션(user space emulation)" 모드로 실행되는 QEMU 버전을 통해 구현됩니다.
QEMU는 AFL과 별개의 프로젝트이지만, 다음을 통해 이 기능을 편리하게 빌드할 수 있습니다:
$ cd qemu_mode $ ./build_qemu_support.sh
추가 지침 및 주의 사항은 qemu_mode/README.qemu를 참조하십시오.
이 모드는 컴파일 타임 계측보다 약 2-5배 느리며, 병렬화에 덜 적합하고, 다른 몇 가지 특이한 점이 있을 수 있습니다.
올바르게 작동하려면 퍼저는 대상 애플리케이션이 일반적으로 기대하는 입력 데이터의 좋은 예를 포함하는 하나 이상의 시작 파일이 필요합니다. 두 가지 기본 규칙이 있습니다:
파일을 작게 유지하십시오. 1kB 미만이 이상적이지만 반드시 필요한 것은 아닙니다. 크기가 중요한 이유에 대한 논의는 perf_tips.txt를 참조하십시오.
여러 테스트 케이스는 서로 기능적으로 다른 경우에만 사용하십시오. 이미지 라이브러리를 퍼징하기 위해 50장의 서로 다른 휴가 사진을 사용하는 것은 의미가 없습니다.
이 도구와 함께 제공되는 testcases/ 하위 디렉토리에서 시작 파일의 좋은 예를 많이 찾을 수 있습니다.
참고. 선별을 위해 대량의 데이터 코퍼스를 사용할 수 있다면, afl-cmin 유틸리티를 사용하여 대상 바이너리에서 서로 다른 코드 경로를 실행하는 기능적으로 구별되는 파일의 하위 집합을 식별할 수 있습니다.
퍼징 프로세스 자체는 afl-fuzz 유틸리티가 수행합니다. 이 프로그램은 초기 테스트 케이스가 포함된 읽기 전용 디렉토리, 결과를 저장할 별도의 장소, 그리고 테스트할 바이너리의 경로가 필요합니다.
stdin에서 직접 입력을 받는 대상 바이너리의 일반적인 구문은 다음과 같습니다:
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program [...params...]
파일에서 입력을 받는 프로그램의 경우 '@@'를 사용하여 대상 명령줄에서 입력 파일 이름이 들어가야 할 위치를 표시하십시오. 퍼저가 이를 자동으로 대체합니다:
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@