https://www.fuzzingbook.org/html/Fuzzer.html
The essence of fuzzing : Create random inputs, and see if they break things.
Synopsis
Fuzzing 아키텍처에 대한 소개
Fuzzeras a base class for fuzzers.Runneras a base class for programs under test.(PUT)
Fuzzers

Fuzzer는 fuzzer들에 대한 기본 클래스로 RandomFuzzer와 같이 확장해서 쓸 쑤 있다.
fuzz() 함수는 input을 생성하여 반환한다.
run() 함수는 fuzz()에서 생성된 input을 put에 넣어 fuzzing을 실행한다.
runs() 함수는 반복적으로 run()을 실행한다.
Runners

- Runner는 Fuzzer에서 생성된 fuzzed string을 input으로 받는다.
- 3가지 상태로 결과를 나타낸다. (PASS, FAIL or UNRESOLVED)
- ProgramRunner는 external program을 input으로 주어 fuzzed input과 함께 fuzzing한다.
- PrintRunner는 단순히 입력을 출력하고 PASS결과를 반환한다.
A Simple Fuzzer
1 | def fuzzer(max_length: int = 100, char_start: int = 32, char_range: int = 32) -> str: |
- 위와 같이 간단하게 랜덤한 문자열을 만들 수 있다.
- fuzzer(100, ord(‘a’), 26) 와 같이 사용한다면 최대길이 100인 랜덤한 소문자 알파벳 문자열을 생성한다.
- fuzzer(100, ord(‘0’), 10) : 랜덤 숫자 생성
Fuzzing External Programs
- 퍼징된 입력으로 외부 프로그램 호출을 확인하기 위해 2단계로 진행한다.
- 퍼징된 데이터를 입력 파일로 만들고, 이 입력 파일을 선택한 프로그램의 input으로 준다.
Creating Input Files
1 | basename = "input.txt" |
- 임시 파일을 만든 후 fuzzer() 결과를 파일에 쓴다.
- assert를 통해 제대로 동작했는지 확인한다.
Invoking External Programs
1 | program = "bc" |
- 호출할 외부 프로그램은 계산기이며, Input으로 줄 파일에 “2+2”를 쓴다.
- subprocess를 통해 계산기를 Input파일을 주고 실행시킨다.
- result.stdout, result.returncode, result.stderr를 통해 subprocess로 실행시킨 bc의 결과를 알 수 있다.

- 만약 remove 프로그램에 대해 퍼징을 진행하고, 이때 모든 파일을 다 지울 수 있는 경우가 나올 확률은?
- 무작위로 생성될 문자열의 길이가 1 이면서, 그 값이 / 일 경우 (rm -rf /)
- 1/100 * 1/32 가 된다.
- 즉 remove 프로그램에 대해 퍼징을 한다면 위의 확률로 모든 파일을 지울 수 있다.
Long-Running Fuzzing
1 | trials = 100 |
- 반복문을 통해 여러번 퍼징을 할 수 있다.
- 정상적인 호출이 일어난 횟수와 에러가 발생한 횟수를 count할 수 있다.
- 발생한 에러중에서 위와 같은 에러가 아니라면 아마도 새로운 종류의 crash일 것이다.
Bugs Fuzzers Find
Buffer Overflows
1 | def crash_if_too_long(s): |
- 입력값이 buffer에 있는 문자열 보다 길면 Overflow임을 의미한다.
1 | trials = 100 |
- fuzzer()함수로 fuzzed string을 만든 후 crash_if_too_long 함수에 input으로 주어 실행시킨다.

- 여기서 ExpectedError 메시지가 뜨지만, Fuzzing을 통해 UnexpectedError를 발견할 수 있다.
Missing Error Checks
- 대부분 프로그래밍 언어는 예외처리 대신에 특정한 오류 코드를 사용한다.
- C언어의 getchar()은 더이상 input이 가능하지 않으면 EOF를 반환한다.
1 | while (getchar() != ' '); |
- 프로그램이 사용자가 공백문자를 이력할 때까지 읽어들이는 상황에서
- 입력이 조기에 종료되었다면, getchar()은 계속 EOF을 반환할 것이며,
- 이는 무한 루프로 들어가게 된다.
1 | def hang_if_no_space(s): |
- 공백문자를 입력받으면 break가 된다.
1 | trials = 100 |
- ExpectTimeout(2)로 지정하고 코드를 돌리면, 2초 뒤에 TImeout Error 메시지가 뜬다.

Rogue Numbers
- 퍼징으로 쉽게 정상적이지 않은 값을 만들 수 있고, 이는 흥미로운 행동을 야기한다.
1
2
3
4
5
6char *read_input() {
size_t size = read_buffer_size();
char *buffer = (char *)malloc(size);
// fill buffer
return (buffer);
} - 만약 size가 프로그램 메모리보다 크면 어떻게 될 것인가.
- 아니면 size가 문자 수 보다 작으면, 혹은 음수이면 어떻게 될 것인가.
- 랜덤으로 생성된 숫자를 생성하여 퍼징은 모든 종류의 에러를 발생시킬 수 있다.
1
2
3def collapse_if_too_large(s):
if int(s) > 1000:
raise ValueError - s의 크기가 1000보다 크면 ValueError를 발생한다.
1 | long_number = fuzzer(100, ord('0'), 10) |
- fuzzer()함수를 통해 무작위 숫자를 만들고, collapse_if_too_large()함수의 인자로 준다.

- 위와 같은 Bug를 찾을 수 있다.
- 하지만 누군가는 나쁜 프로그래밍이나, 언어의 문제라고 주장하지만
- 매일 수천명의 사람들이 프로그램을 시작하고 같은 실수를 반복한다.
Catching Errors
- 처음에 crash와 hang으로 오류를 식별할 수 있었다.
- 다만 오류를 감지하기 어렵다면, 우리는 추가적인 확인이 필요하다.
Generic Checkers
- C나 C++같은 언어에서 프로그램은 메모리에 대해 접근할 수 있다.
- 취약점이 있다면 초기화 되지 않은 메모리나, 이미 free된 메모리에도 접근할 수 있다.
- 런타임에 이러한 문제를 포착하는데 도움이 되는 도구가 있으며 퍼징과 결합하면 좋다.
Checking Memory Acccess
- 테스트 중에 문제가 있는 메모리 접근을 포착하기 위해, 특별한 메모리 검사 환경에서 C프로그램을 실행할 수 있다.
- Address Sanitizer : memory corruption bug를 감지하는 도구.
1 | with open("program.c", "w") as f: |
- 위와 같이 C코드를 생성하는 스크립트를 작성한다.
- malloc으로 동적 할당을 한 후 42값을 채워준다.
- 프로그램 실행 시 인자로 받은 값을 인덱스로 하여 동적할당한 메모리에 접근한다.
- 이때 인덱스 값이 메모리를 넘어간다면 OOB 취약점이 발생한다.
1
clang -fsanitize=address -g -o program program.c
- 컴파일 시 -fsanitize 옵션을 활성화한다.

- 할당된 메모리보다 작은 인덱스 값을 주면 정상적으로 출력이 된다.

- 하지만 할당된 메모리를 초과하는 인덱스 값을 주면 Out of bound 에러가 발생한다.
- Address Sanitizer는 어느 주소에서 취약점이 발생했는지 알려준다.

- gdb로 해당 주소를 확인하면 초기화가 된 공간 말고 0x15e 위치를 접근하려 한 것을 확인할 수 있다.


- C프로그램에서 오류를 찾고 싶다면, 퍼징과 함께 사용하면 좋다.
- 메모리를 더 많이 소모하지만, 버그를 찾는 데 필요한 인간의 노력에 비해 매우 저렴하다.
- Out of bound는 공격자가 의도하지 않은 메모리에 엑세스, 수정이 가능 하기에 보안 위험이 있다.
- 대표적인 OOB 취약점으로 HeartBleed가 있다.
HeartBleed
- OpenSSL 라이브러리의 보안 버그, SSL heartbeat 서비스에서 발견된 취약점



- OpenSSL에서 이러한 메모리에는 암호화 인증서, 개인 키 등이 포함 될 수 있으며
- 메모리에 엑세스한 사실을 아무도 알아채지 못했다.
- 하지만 Address Sanitizer로 OpenSSL 라이브러리를 컴파일한 후에
- Out of bound가 발생했음을 알아차렸다.
Information Leaks
- Memory Leak은 illegal한 메모리 접근에 대해서만 발생하는 것이 아닌, 유효한 메모리 내에서도 발생할 수 있다.
1 | secrets = ("<space for reply>" + fuzzer(100) + |
- secrets 문자열을 만든다.
1 | def heartbeat(reply: str, length: int, memory: str) -> str: |
- heartbeat 기능을 위와 같이 구현한다.
1
2
3heartbeat("potato", 6, memory=secrets)
heartbeat("bird", 4, memory=secrets)
heartbeat("hat", 500, memory=secrets)

- 순서대로 함수를 호출하면 두 번째 인자에 따라 뒤에 출력되는 데이터가 다른 것을 확인할 수 있다.
- 어떻게 하면 탐지 할 수 있을까?
- 출력하기전에 secret과 uninitialized_memory_marker가 문자열에 있으면 assert.

- 퍼징 중에는 항상 가능한 많은 automatic checker를 켜야 한다.
- 오류를 탐지할 수 있는 옵션 없이 프로그램만 실행하면 많은 기회를 놓치기 때문이다.
A Fuzzing Architecture
Runner Classes
- Runner는 일반적으로 program or function under test이다.
1 | class Runner: |
1 | class PrintRunner(Runner): |
1 | class ProgramRunner(Runner): |
1 | class BinaryProgramRunner(ProgramRunner): |
Fuzzer Classes
1 | class Fuzzer: |
1 | class RandomFuzzer(Fuzzer): |