블로그 이전했습니다. https://jeongzero.oopy.io/
AFL fuzzer 설명 및 사용방법
본문 바로가기
보안/Linux

AFL fuzzer 설명 및 사용방법

728x90

1. AFL 퍼저 원리


afl 퍼저는 instrumentation-guided genetic algorithm 과 결합된 퍼저이다. 브푸트포스로 입력을 받지만, 거기서 끝나는게 아니라, 커버리지를 넓혀가며 프로그램 제어 흐름에 대한 변경사항을 기록하고, 이를 로깅하여 유니크한 크래시를 발견해 낼수 있다. 아주 똑똑한 녀석이다. 

 

afl 퍼저의 특징은 다음과 같다 

  • 커버리지 기반 퍼저이기 때문에 매우 효율적인 퍼징이 가능함
  • 코드 커버리지 측정을 위한 코드를 컴파일 타임에 삽입함
  • QEMU를 이용해서 컴파일 타임이 아닌, 런타임시에 코드 삽입도 가능함. 여기서 말하는 코드는 코드가 어디가 실행됬고, 어디가 실행 안됬는지를 측정해주는 코드를 뜻한다.

 

코드삽입 : Instrumentation 이라고 함

 

즉, 코드 삽입을 어디에, 언제할꺼냐가 중요하다. 

⇒ 이건 아직 정확히 의미를 모르겠다. 내가 수동으로 삽입 시점과 위치를 지정할수 있는건가.. 

 

어쨋든 afl 퍼저의 간단한 동작과정은 다음과 같다 

 

1) afl 전체 로직

위 그림은 퍼징을 대략적으로 보여준다.  

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

 

앞서 말한

공유 메모리

를 통해 새로운 입력을 만든다고 했는데 이 부분이 바로 코드 커버리지를 높이기 위한 로직으로, 컴파일시 branch에 다음과 같은 코드가 삽입된다 

  cur_location = <COMPILE_TIME_RANDOM>;
  shared_mem[cur_location ^ prev_location]++; 
  prev_location = cur_location >> 1;

 

공유메모리를 이전 실행과 비교하여 새로운 path 발견시 해당 input data를 저장하고, 이를 다음 루틴의 입력으로 사용한다. 

 

 

2) 성능

afl에서는 퍼징의 성능을 높이기 위해 많은 노력이 들어갔다. 그 중하나가 fork server 이용한다. 타겟 프로그램을 컴파일할때, fork server 관련 코드가 삽입이 되고, 실행이 되면, 해당 프로그램에서 fork server를 만든다. 

 

일반적으로 퍼징 대상 프로그램이 실행되면, 프로그램이 실행되기 위해 execve () , 링커 및 모든 라이브러리 초기화 루틴을 거치게 된다. 우리가 퍼징을 하려는 위치가 프로그램의 특정 기능이라고 가정하면, 위 초기화 과정은 한번만 호출되는것이 제일 합리적이다. 

 

따라서 fork server에서 이러한 초기화 루틴이 한번 실행되고, fork를 통해 필요 리소스를 그대로 child에게 넘겨주고, child에서는 타겟 기능에 대해서만 뮤테이션된 입력으로 퍼징을 수행하는것이 바로 afl에서 성능을 높이기 위한 방법이다.  

 

결론적으로 fork를 통해 중복적으로 수행되는 부분을 제거를 통해 2배정도의 성능 향상이 있다고 한다. ( 추가적으로 fork server를 생성하는 위치를 조정하여 쫌더 성능을 높을수 있다는 글을 봄 ) 

 

만약 종료되지 않는 프로그램을 대상으로 퍼징할때는 Persistent mode를 이용해서 특정 루프로 지정해둔 곳만 퍼징을 돌릴수도 있다. 

New in AFL: persistent mode
Although American Fuzzy Lop comes with a couple of nifty performance optimizations, it still relies on a fairly resource-intensive routine that is common to most general-purpose fuzzers: it continually creates new processes, feeds them a single test case, and then discards them to start over from scratch.
https://lcamtuf.blogspot.com/2015/06/new-in-afl-persistent-mode.html

 

 

 

2. AFL 퍼저 설치 및 사용법


그냥 진짜 디폴트로 퍼징돌리는건 간단하다. 

$ wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz
$ tar -xvf afl-latest.tgz
$ cd afl-2.52b/
$ make
$ sudo make install

 

압축파일을 다운받아서, 압축풀고, 컴파일해주면 위 사진과 같이 짜자잔! 나온다. 그다음 이제 분석하려는 오프소스 프로그램을 하나 다운받는다. 나는 dact라는 압축 프로그램을 다운받았다. 

 

$ wget https://fossies.org/linux/privat/dact-0.8.42.tar.gz
$ tar -xvf dact-0.8.42.tar.gz
$ make clean => 맨처음 실행하면 요건 안해줘도 됨
$ CC=[afl folder]/afl-2.52b/afl-gcc ./configure
$ make

참고로 CC=의 의미는 dact 프로그램을 afl-gcc 컴파일러를 사용하겠다 라는 옵션이다. 이를 넣고 configure로 환경세팅을 하고, 컴파일을 진행하면 된다. (이렇게 afl-gcc로 dact를 컴파일해야, 코드 커버리지 기능을 위한 코드가 dact에 컴파일시 삽입되다.) 

 

요렇게 나옴. dact 사용방법은 옵션보면 된다. 

 

내가 타겟으로하는 dact의 기능은 압축이 아닌 압축해제할때이다. 따라서 -d옵션을 반드시 줘야하면 추가적으로 c,f도 주었다. 그럼이제 testcase를 하나 만들고 input 폴더를 만들자. 

 

$ echo hello > hello.txt
$ ./dact hello.txt => hello.txt.dct 생성됨

mkdir input
mv hello.txt.dct input/

 

1. Fix core file setting
sysctl -w kernel.core_pattern=core
	=>코어파일을 생성을 해놔야 afl이 얘가 뒤졌나 안뒤졌나 알 수 있음

2. Run AFL
[afl folder]/afl-fuzz -i input -o output -- ./dact -dcf
- i는 인풋
- o 는 아우풋 폴더
 -- 이후는 실제로 넣을 인자들
- dcf : 옵션들 

위 명령어대로 실행을 하면 퍼징이 시작된다. 

 

위 표에대한 설명들은 아래의 사이트에서 확인 가능하다 

 

자세히는 더 공부해야하고, 일단 퍼징이 돌아가고, finding in depth 탭을 보면, 현재 total crash가 279개 정도 나왔고, 그 중 우측 상단에 uniq crashes는 18개가 나온것을 확인할 수 있다. 

 

output/crashes 폴더에 가보면, 크래시가 터졌던, 변조된 파일들을 볼수 있다. dact 옵션에 -d를 줬으므로, 압축해제하는 기능을 기준으로 퍼징을 했고, 위 파일들은 변조된 압축 파일들이라고 보면 된다. 보통 sig 11이 세그폴트이므로 요거를 주로해서 파일을 dact로 압축해제 해보자. 

 

세그폴트가 뜬것을 볼수있다. 압축을 해제할때, 뭔가 문제가 있고, 그 문제 때문에 에러가 터졌을 것이다. 그럼 우리는 dact에서 해당 파일이 압축해제될때를 디버깅하면 된다. 

 

또한 afl을 쫌더 공부하다보면, crashes 폴더에 들어있는 크래시난 파일들이 전부다 유효하지 않다는 것을 알것이다. 중복되는 크래시도 있을 것이며, 등등의 이유가 있는데, afl 퍼저 기능을 활용하면 이러한 중복이나 등등.. testcase들을 최적화 시킬수 있다. 이러한 성능과 관련된 설명들은 아래의 사이트에서 확인하면 된다. (나중에 추가로 이것도 정리할 예정) 

AFL
This file provides tips for troubleshooting slow or wasteful fuzzing jobs. This is probably the single most important step to take! Large test cases do not merely take more time and memory to be parsed by the tested binary, but also make the fuzzing process dramatically less efficient in several other ways.
https://afl-1.readthedocs.io/en/latest/tips.html#performance-tips

 

퍼징에 익숙해지고, 어딘가 불편함을 느끼거나, 원하는 동작을 수행시키고 싶을때 저런것들을 참조하면서 퍼징을 하는게 공부에 나는 더 도움이 된다. 그래서 지금은 그냥 기본적인 것들만 설명하... 

 

 

 

3. ASAN 사용


위에서 나온 크래시 파일을 디버깅하면서 익스를 진행하면되는데, 여기서 ASAN이라는 기능을 이용하면 크래시에 대한 정보가 매우 자세하게 나온다. ASAN이 뭔지는 따로 정리를 할것이기 때문에 간단한 사용방법만 정리하겠다. 

 

그냥 afl 사용하려면 다운받고, 컴파일하고 사용하면 된다고 했다. 근데 좀더 성능적으로 속도를 증가시키려는 목적으로 afl안에서 llvm모드로 컴파일을 해주면 좋다. 

 

llvm_mode 디렉토리로 들어와서 여기서 컴파일을 진행해주면, clang-fast 컴파일러를 사용할수 있다. 그 전에 llvm_mode로 컴파일을 하려면, clang을 설치해줘야한다. 

 

  • clang 관련 패키지 설치
# vi /etc/apt/sources.list 에다가 아래 내용 추가
deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main
deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main

# wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -

# apt-get update


# apt-get install clang-4.0 clang-4.0-doc libclang-common-4.0-dev libclang-4.0-dev libclang1-4.0 libclang1-4.0-dbg libllvm-4.0-ocaml-dev libllvm4.0 libllvm4.0-dbg lldb-4.0 llvm-4.0 llvm-4.0-dev llvm-4.0-doc llvm-4.0-examples llvm-4.0-runtime clang-format-4.0 python-clang-4.0 liblldb-4.0-dev lld-4.0 libfuzzer-4.0-dev

 

다시 첨부터 afl 다운을 받는다 치고 설명하면, 

 

$ wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz
$ tar -xvf afl-latest.tgz
$ cd afl-2.52b/
$ make
$ sudo make install
-------------------------위에꺼는 안해줘도 될꺼같은데 일단 저것도 해주자..
$ cd ./llvm_mode
$ sudo ln -s /usr/bin/llvm-config-4.0 /usr/local/bin/llvm-config
$ sudo ln -s /usr/bin/clang-4.0 /usr/local/bin/clang
$ sudo ln -s /usr/bin/clang++-4.0 /usr/local/bin/clang++

$ make
$ make install

export AFL_USE_ASAN=1 // ASAN을 사용하기 위해서 넣어저야함
export PATH=/usr/lib/llvm-4.0/bin:$PATH

 

저렇게 afl-clang-fast++ 를 사용할수 있다. 이걸 왜 사용하냐면 요 fast가 붙은 놈이 바로 바이너리 컴파일 단계에서 원하는 코드를 삽입시킬수 있는 컴파일러이다. (afl-clang은 어셈레벨에서 코드삽입함) 

 

그다음 

 

정리하자면 

  • llvm_mode로 퍼징을 하는 목적

    ⇒ 성능향상 

  • afl-gcc가 아닌 afl-clang-fast++ 로 컴파일러를 사용하는 목적

    ⇒ 컴파일단계에서 코드삽입(ASAN) 

 

그럼이제 ASAN을 사용하기위한 준비는 끝났고, dact를 컴파일 하면 된다 

$ cd dact-0.8.42
$ make clean
$ CC=[afl folder]/afl-clang-fast CXX=[afl folder]/afl-clang-fast++ CFLAGS="-fsanitize=address -g " CXXFLAGS="-fsanitize=address -g" LDFLAGS="-fsanitize=address -g" ./configure
$ make

아까는 CC에 afl-gcc를 사용했는데, AsAN을 이용하기 위해, CC를 afl-clang-fast로 변경했다. CXX는 c++ 컴파일러이다. (afl-clang-fast++도 afl-clang-fast에 링킹되어있어서 동일하다고 보면 될듯) 

 

그럼 이제 아까 크래시 파일을 dact로 압축해제 해보자. 아까는 그냥 이렇게  

 

에러만 나왔다. 근데 다시 해보면? 

 

왓더~~~~~~~~~~~~ 개쩐다. 어느 부분에서 어떠한 에러가 낫는지가 정말 상세하게 나와있다. 해당 크래시는 힙 bof이며, 주소는 뭐고, backstrace를 뭐고, 터지는 시점의 힙 메모리가 어딘지 나와있다. 이거를 가지고 익스를 진행하면 훨씬 편하게 조질수 있다.!!!! 

 

하지만 ASAN을 사용하게 되면 당연히 많은 정보가 나오는 대신 그만큼 퍼징시 성능이 저하가 된다. 그래서 llvm_mode로 빌드한게 바로 저런 성능저하를 어느정도 막기 위함이다. 

 

이러한 새니타이저에 대한 이론은 아직 공부가 완벽하게 된것은 아니다. 추후에 더 공부해서 따로 어떤원리인지 공부할것이다. 뵤비때 남겨줬던 자료가 지금 빛을 발한다.아런아럼ㄴ앎이나럼니알 

 

이제 저 dact를 asan을 이용해서 나온 정보를 토대로 익스를 진행해보자!는 다음에 정리.. 

 

 

4. 참고자료


  • 멘토님 강의자료

 

728x90

'보안 > Linux' 카테고리의 다른 글

fuzzing 이란  (3) 2020.08.26
seccomp 간단 정리  (0) 2020.07.08
main 함수가 호출, 종료되는 과정  (0) 2020.05.14
fclose 분석  (0) 2020.05.01
House of Orange 분석  (0) 2020.04.29