Fascination
article thumbnail

[AFL fuzzer] AFL fuzzer 원리

Ref. https://wogh8732.tistory.com/272


# AFL 퍼저 원리

  • afl 퍼저는 instrumentation-guided genetic algorithm과 결합한 퍼저
  • 브루트포스로 입력을 받지만, 거기서 끝나는 것이 아닌 커버리지를 넓혀가며 프로그램 제어 흐름에 대한 변경사항을 기록하고, 이를 로깅하여 유니크한 크래시를 발견해 낼 수 있음
  • 특징
    • 커버리지 기반 퍼저이기 때문에 매우 효율적인 퍼징이 가능함
    • 코드 커버리지 측정을 위한 코드를 컴파일 타임에 삽입함
    • QEMU를 이용해서 컴파일 타임이 아닌, 런타임 시에 코드 삽입도 가능함
    • 코드: 어디가 실행됐고, 어디가 실행 안됐는지를 측정해주는 코드
  • 코드 삽입: Instrumentation
    • 코드 삽입을 어디에, 언제할 것인지가 중요

 

 

# AFL 전체 로직

  1. afl-fuzz에서 우리가 퍼징하려는 프로그램을 실행
  2. 해당 프로그램이 처음으로 실행되면서 afl-fuzz와 pipe로 통신을 하면서 fork server를 만들고, 새로운 타겟 인스턴스를 fork call()로 실행함
  3. 표준 입력 or file로 들어온 입력이 fuzz 대상 프로그램으로 전달됨
  4. 실행된 결과를 공유 메모리에 기록하여 실행을 완료
  5. afl-fuzz는 공유 메모리에서 fuzz 대상이 남긴 기록을 읽고 이전 항목을 변경하여 새로운 입력을 만듦
  6. 새롭게 만든 입력은 다시 프로그램에 들어가서 실행됨

 

💡 공유 메모리

  • 공유 메모리를 통해 새로운 입력을 만듦
  • 아래 코드는 코드 커버리지를 높이기 위한 로직으로, 컴파일 시 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를 이용해서 특정 루프로 지정해둔 곳만 퍼징을 돌릴 수 있음

 

profile

Fascination

@euna-319

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!