[AFL fuzzer] AFL fuzzer 원리
Ref. https://wogh8732.tistory.com/272
# AFL 퍼저 원리
- afl 퍼저는 instrumentation-guided genetic algorithm과 결합한 퍼저
- 브루트포스로 입력을 받지만, 거기서 끝나는 것이 아닌 커버리지를 넓혀가며 프로그램 제어 흐름에 대한 변경사항을 기록하고, 이를 로깅하여 유니크한 크래시를 발견해 낼 수 있음
- 특징
- 커버리지 기반 퍼저이기 때문에 매우 효율적인 퍼징이 가능함
- 코드 커버리지 측정을 위한 코드를 컴파일 타임에 삽입함
- QEMU를 이용해서 컴파일 타임이 아닌, 런타임 시에 코드 삽입도 가능함
- 코드: 어디가 실행됐고, 어디가 실행 안됐는지를 측정해주는 코드
- 코드 삽입: Instrumentation
- 코드 삽입을 어디에, 언제할 것인지가 중요
# AFL 전체 로직
- afl-fuzz에서 우리가 퍼징하려는 프로그램을 실행
- 해당 프로그램이 처음으로 실행되면서 afl-fuzz와 pipe로 통신을 하면서 fork server를 만들고, 새로운 타겟 인스턴스를 fork call()로 실행함
- 표준 입력 or file로 들어온 입력이 fuzz 대상 프로그램으로 전달됨
- 실행된 결과를 공유 메모리에 기록하여 실행을 완료
- afl-fuzz는 공유 메모리에서 fuzz 대상이 남긴 기록을 읽고 이전 항목을 변경하여 새로운 입력을 만듦
- 새롭게 만든 입력은 다시 프로그램에 들어가서 실행됨
💡 공유 메모리
- 공유 메모리를 통해 새로운 입력을 만듦
- 아래 코드는 코드 커버리지를 높이기 위한 로직으로, 컴파일 시 branch에 다음과 같은 코드가 삽입됨
- 공유 메모리를 이전 실행과 비교하여 새로운 path 발견 시 해당 input data를 저장하고, 이를 다음 루틴의 입력으로 사용
cur_location = <COMPILE_TIME_RANDOM>;
shared_mem[cur_location ^ prev_location]++;
prev_location = cur_location >> 1;
# 성능
- afl에서는 퍼징의 성능을 높이기 위해 많은 노력이 필요
- 그 중 하나가 fork server를 사용하는 것
- 타겟 프로그램을 컴파일 할 때, fork server 관련 코드가 삽입이 되고, 실행이 되면, 해당 프로그램에서 fork server를 만듦
- 일반적으로 퍼징 대상 프로그램이 실행되면, 프로그램이 실행되기 위해 execve(), 링커 및 라이브러리 초기화 루틴을 거치게 됨
- 우리가 퍼징을 하려는 위치가 프로그램의 특정 기능이라고 가정하면, 위 초기화 과정을 한 번만 호출되는 것이 제일 합리적임
- 따라서 fork server에서 이러한 초기화 루틴이 한 번 실행되고, fork를 통해 필요 리소스를 그대로 child에게 넘겨주고, child에서는 타겟 기능에 대해서만 뮤테이션된 입력으로 퍼징을 수행하는 것이 바로 afl에서 성능을 높이기 위한 방법임
- 결론적으로 fork를 통해 중복적으로 수행되는 부분 제거를 통해 2배정도의 성능 향상이 있다고 함
- 만약 종료되지 않는 프로그램을 대상으로 퍼징할 때는 persistent mode를 이용해서 특정 루프로 지정해둔 곳만 퍼징을 돌릴 수 있음
'Hacking Tech > Fuzzer' 카테고리의 다른 글
[Fuzzer] What the Fuzz (0) | 2022.11.08 |
---|---|
[firmadyne/Firm-AFL] firmadyne & FirmAFL 사용을 위한 지식 (0) | 2022.11.07 |
[Firm-AFL] FirmAFL 환경 셋팅 및 오류 해결 (3) | 2022.11.05 |