AFL fuzzer에 대해서 조금씩 더 알아나가 보자.
블로그에서 파편화된 지식들로 배우다 보니, 스스로 깊이가 부족한 것 같아서 돌고 돌아 다시 공식 문서다.
두번째 읽으니까 다시 보이는 것이 있어서 정리해둔다.
저번 AFL fuzzer 포스팅에서는 간단한 동작 원리를 살펴보았는데,
타겟 프로그램에 무수히 많은 mutated inputs을 테스팅해봄으로써 취약점을 찾아내는 것이 fuzzing 이다.
하지만 생각해보면, 우리가 사용하는 유명한 프로그램은 독자적인 포맷, 확장자를 제공한다.
예를 들어, PNG, JPEG, PDF, PPTX 등이 있을 것이다.
이러한 확장자들을 살펴보면, 저마다 magic number 혹은 file signature 라는 것이 있는데,
PNG 같은 경우, file signature는 "\x89PNG\x0d\x0a\x1a\x0a" 이다.
컴퓨터에서 캡쳐 도구로 아무거나 캡쳐해서 저장하고, xxd 로 hexdump를 찍어보면, 맨 앞에 위와 같은 file signature를 확인할 수 있다.
이것이 왜 중요하냐면, 퍼징의 성능을 높이는 데에 크게 기여할 수 있는 부분이기 때문이다.
PNG 를 입력으로 받는 프로그램에 bug가 있다고 가정해보자.
이러한 프로그램에게 아무런 의미가 없는 입력을 주게 되면, 프로그램을 탐색하지도 못하고 나올 것이다.
이러한 프로그램은 PNG를 입력으로 받기 때문에 첫 관문이라도 넘길려면, 적어도 PNG 포맷으로 인식될 수 있는 파일을 입력으로 주어야 CFG에서 더 깊이 경로를 탐색해서 bug를 찾아낼 수 있는 확률이 높아질 것이다.
안타깝게도, AFL fuzzer 의 가장 큰 한계점은 input을 변이시키는 mutation engine이 syntax-blind 라는 점이고, mutation engine이 보다 "간편하고 간결한" 데이터 포멧에 최적화 되어 있다는 것이다. (e.g., archives, multimedia / 공식 문서의 말을 빌리자면, terse human-readable languages like RTF, shell-scripts)
이 한계점은 performance와 직결될 수 있는 부분이다.
syntax-blind한 mutation을 한다는 것은 타겟 프로그램의 문법을 고려하지 않은 mutation을 진행한다는 것과 동일하다.
앞서 말한 것처럼, PNG를 입력으로 받는 프로그램을 퍼징할 때는 입력으로 PNG와 관련된 syntax를 갖는 입력을 주어야 더 효율적으로 퍼징하고, 그 입력으로부터 explore 하여 퍼징 퍼포먼스가 올라갈 수 있다.
하지만, 이를 고려하지 않고, bit flips, arithmetic operations 등 syntax-blind mutation 을 하게 되면, execution path의 시작 노드에 진입하기에도 엄청 오랜 시간이 걸릴 수 있다. (PNG 포맷이 아니면, 바로 exit() 해버린다든가)
또한, AFL fuzzer는 간편하고 간결한 데이터 포맷에 최적화되어 있어서, 장황한 syntax를 가진 SQL or HTTP 는 퍼징이 상당히 힘들 수 있다.
하지만, AFL fuzzer는 이러한 문제를 놀라울정도로 좋은 결과를 내도록 해결할 수 있는 방안을 제공해주는데, 그것이 바로 dictionaries의 활용이다.
Dictionaries
우리가 아는 사전 맞다.
PS를 하시던 분들이라면, key=value 생각하시면 된다.
우리가 퍼징하려는 타겟 프로그램의 키워드들을 미리 정의해놓으면, 나중에 syntax-blind mutation을 하더라도 유의미한 입력을 가지도록 도움을 줄 수 있다.
사용자로 하여금 해당 타겟의 syntax를 모조리 작성하라고 하는 것은 사실 퍼징에 큰 도움이 될 수는 있지만, 앵간한 고수가 아니라면 불가능할 것이다. 공식 문서에서는 이러한 키워드들을 제공하는 것만으로도 엄청난 퍼징 성능 향상이 된다라고 나와있다.
위에서 볼 수 있듯이, PNG의 헤더 키워드를 미리 정의해두는 것이다.
사진에서 볼 수 있듯이, magic number 말고도 IHDR과 같은 청크들도 저장해두었다.
나중에 mutation algorithm이 전 포스팅의 pseudo-code에서 언급한 deterministic, sequential bit flips를 하게 될 때, 이웃한 region을 bit flip을 하는 것과 affected 되는 부분을 bit flip 하는 것이 전혀 다른 execution path를 trigger할 수 있다는 것을 확인하여 보다 유의미한 입력을 만들어낼 수 있다. (e.g., IHDR 바이트를 bit flips했을 때와 그 주변을 했을 때 전혀 다른 execution path를 보여준다든지 -> IHDR 부분이 뭔가 타겟 프로그램에서 중요한 byte 구나)
이러한 현상이 발견되면, 이러한 데이터가 dictionary에 등록되고, 나중에 다른 dictionary tokens와 결합되어 더 깊이 탐색할 수 있는, distinct 한 execution path를 generate 할 수 있다.
Reference:
https://lcamtuf.blogspot.com/2015/01/afl-fuzz-making-up-grammar-with.html
https://afl-1.readthedocs.io/en/latest/fuzzing.html#fuzzing-binaries
https://github.com/google/AFL/blob/master/dictionaries/png.dict
※ Any feedbacks and corrections are welcome. Feel free to comment down below.