The essence of fuzzing : Create random inputs, and see if they break things.
Synopsis
Fuzzing 아키텍처에 대한 소개
Fuzzer as a base class for fuzzers.
Runner as 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 2 3 4 5 6
deffuzzer(max_length: int = 100, char_start: int = 32, char_range: int = 32) -> str: string_length = random.randrange(0, max_length + 1) out = "" for i inrange(0, string_length): out += chr(random.randrange(char_start, char_start + char_range)) return out
위와 같이 간단하게 랜덤한 문자열을 만들 수 있다.
fuzzer(100, ord(‘a’), 26) 와 같이 사용한다면 최대길이 100인 랜덤한 소문자 알파벳 문자열을 생성한다.
fuzzer(100, ord(‘0’), 10) : 랜덤 숫자 생성
Fuzzing External Programs
퍼징된 입력으로 외부 프로그램 호출을 확인하기 위해 2단계로 진행한다.
퍼징된 데이터를 입력 파일로 만들고, 이 입력 파일을 선택한 프로그램의 input으로 준다.
for i inrange(trials): data = fuzzer() withopen(FILE, "w") as f: f.write(data) result = subprocess.run([program, FILE], stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) runs.append((data, result)) count = sum(1for(data,reuslt) in runs if result.stderr == "") print (count)
errors = [(data,result) for (data,result) in runs if result.stderr != ""] (first_data, first_result) = errors[0]
print(repr(first_data)) print(first_result.stderr) [result.stderr for (data, result) in runs if result.stderr != "" and"illegal character"notin result.stderr and"parse error"notin result.stderr and"syntax error"notin result.stderr]
withopen("program.c", "w") as f: f.write(""" #include <stdlib.h> #include <string.h> int main(int argc, char** argv) { /* Create an array with 100 bytes, initialized with 42 */ char *buf = malloc(100); memset(buf, 42, 100); /* Read the N-th element, with N being the first command-line argument */ int index = atoi(argv[1]); char val = buf[index]; /* Clean up memory so we don't leak */ free(buf); return val; } """)
위와 같이 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한 메모리 접근에 대해서만 발생하는 것이 아닌, 유효한 메모리 내에서도 발생할 수 있다.