fuzzing과fuzzer
fuzzing 종류 , fuzzer 차이점 분석
1. Fuzzing이 뭘까?
이번에 새롭게 공부한 것 fuzzing(퍼징)... 단어부터 낯설어서 일단 정의부터 찾아봤는데, 생각보다 직관적이었음.
정의를 한 줄로 정리하면, 프로그램에 의도적으로 "이상한" 입력값을 대량으로, 무작위에 가깝게 집어넣어서 프로그램이 죽거나(crash) 비정상적으로 동작하는 지점을 찾아내는 자동화된 테스트 기법 임.
학교 자료구조 수업 예시로 들면, 내가 짠 연결 리스트 코드가 "잘 동작한다"는 걸 증명하려면 보통 테스트 케이스를 직접 만들어야 함. 노드 1개 넣기, 10개 넣기, 빈 리스트에서 삭제하기 같은 식으로 사람이 떠올리는 케이스를 손으로 짜는 거임.
근데 사람 상상력에는 한계가 있음. "내가 한 번도 떠올린 적 없는 입력"에서 프로그램이 죽으면 그게 그대로 보안 취약점이 됨. 퍼저는 이걸 자동으로 해줌. 초당 수천~수만 개 입력을 만들어서 프로그램에 밀어 넣고, 죽는 순간만 잡아냄.
왜 이게 보안에서 중요할까?
대부분의 메모리 손상 취약점 — buffer overflow, use-after-free, out-of-bounds read 등 — 은 결국 개발자가 예상하지 못한 입력이 들어왔을 때 터짐. 퍼징은 이런 예상 못 한 입력을 기계가 대신 찾아주는 방법임.
Heartbleed(OpenSSL), ImageMagick의 수많은 CVE들, 브라우저 자바스크립트 엔진 버그들 같은 게 대부분 퍼징으로 발견됐다고 함.
2. Fuzzing의 종류
퍼징을 분류하는 기준이 여러 개라서 헷갈렸음. 그래서 제일 자주 언급되는 두 가지 축으로 나눠봤음.
2-1. 입력 생성 방식에 따른 분류
Dumb Fuzzing (멍청한 퍼징)
말 그대로 "아무 생각 없이" 랜덤 바이트를 던지는 방식임. 만들기는 쉬움. 거의 /dev/urandom을 입력으로 쓰는 수준. 근데 대부분의 입력이 첫 단계 파싱에서 거부돼버림. 예를 들어 PNG 파서에 진짜 랜덤 바이트 넣으면 99.99%는 "PNG 시그니처 아님"으로 즉시 reject 돼서 의미 있는 코드까지 도달도 못 함.
Smart Fuzzing (똑똑한 퍼징)
입력 포맷 구조를 어느 정도 알고 변형하는 방식임. 두 갈래로 나뉨.
- Mutation-based: 정상 입력을 들고 와서 일부 바이트를 바꾸거나 잘라내거나 끼워 넣는 식. AFL++가 이 방식의 대표 주자임.
- Generation-based: 문법을 미리 정의해두고 거기 맞는 입력을 새로 만들어내는 식. JavaScript 엔진처럼 입력 자체가 복잡한 언어일 때 자주 씀.
데이터베이스 수업 들으면서 SQL 문법을 BNF로 표현하는 걸 본 적 있었는데, generation-based 퍼저가 딱 그런 문법 정의를 활용하는 거더라.
2-2. 프로그램 내부 정보 활용 여부에 따른 분류
Black-box Fuzzing
프로그램을 그냥 입출력 블랙박스로 보고 입력만 던지는 방식임. 소스코드도 없고, 실행 중에 내부에서 어디를 거치는지도 모르는 상태.
White-box Fuzzing
소스코드랑 실행 경로를 다 알고 있는 상태에서 함. Symbolic execution 같은 기법으로 "이 분기를 타려면 입력값이 뭐여야 하는지"를 수식으로 풀어내려고 하는 건데, 강력하지만 비싸고 느림.
Grey-box Fuzzing
요즘 가장 핫한 방식임. 소스코드를 직접 분석하진 않지만, 컴파일할 때 instrumentation(계측 코드)을 심어두고 실행 중에 "어떤 코드 경로를 새로 탔는지"를 추적함.
새로운 경로를 탄 입력은 "좋은 입력"으로 보고 저장해서 그걸 또 변형함. 이 피드백 루프 덕분에 단순 랜덤보다 압도적으로 효율적임. AFL, AFL++, libFuzzer가 다 이 계열임.
여기서 자료구조 지식이 한 번 더 연결됐음. coverage 정보를 효율적으로 관리하려고 퍼저들이 보통 비트맵 자료구조를 씀. 64KB짜리 공유 메모리에 각 엣지를 해시로 매핑해서 "이 엣지를 밟았다/안 밟았다"를 비트 단위로 기록하는 식임. 빠르고 메모리도 적게 쓰니까 합리적임.
3. 대표적인 Fuzzer 3가지 비교
미션에서 주어진 세 개의 GitHub 저장소를 하나씩 들여다봤음.
3-1. AFL++ (AFLplusplus/AFLplusplus)
AFL++는 현존하는 grey-box 퍼저의 표준이라고 보면 됨. 원조 AFL이 개발 멈춘 뒤 커뮤니티가 포크해서 계속 발전시키고 있는 버전임.
특징을 정리해보면:
- 원본 AFL은 Michał Zalewski가 만들었는데, 개발이 멈춰서 지금은 AFL++가 사실상 후속작 자리에 있음.
- Coverage-guided + Mutation-based 방식이라, 시드 입력을 변형하면서 새 코드 경로 발견하면 그 입력을 큐에 저장해두고 또 변형함.
- 소스코드 있을 때(
afl-cc로 컴파일)랑 없을 때 둘 다 지원함. - MOpt, RedQueen, AFLfast 같은 학계 논문 기법도 적극 반영돼있음.
리눅스 환경에서 소스코드 있는 C/C++ 프로그램 퍼징할 때 제일 먼저 떠올리게 되는 도구임. 예를 들면 오픈소스 이미지 라이브러리, 파서, 네트워크 데몬 같은 것들. 최근 리눅스 공부할 때 익혔던 make, 환경변수, 프로세스 개념이 여기서 그대로 쓰여서 그나마 진입이 부담스럽지 않았음.
3-2. Jackalope (googleprojectzero/Jackalope)
Jackalope는 Windows랑 macOS에서, 소스코드 없이 바이너리만 가지고 퍼징하기 위한 도구임. AFL이 약한 영역을 노린 거임.
특징을 정리해보면:
- 구글의 공격적 보안 연구팀인 Project Zero에서 만듦. 실전에서 0-day 찾으려고 만든 도구라는 뜻임.
- Black-box binary fuzzer로 분류되지만, 완전한 black-box는 아니고 동적 instrumentation(TinyInst 라이브러리)으로 coverage를 측정함. 그래서 사실상 black-box로 출발한 grey-box임.
- Windows랑 macOS 지원이 강점.
- README 보면 "이건 그대로 쓰는 도구가 아니라, 너의 타겟에 맞게 mutator나 sample delivery 코드를 짜는 프레임워크다" 같은 톤이라서 진입 장벽이 좀 있음.
상용 소프트웨어, 특히 Windows 데스크톱 앱이나 macOS 앱을 타겟으로 할 때 쓰는 도구임. Adobe Reader, MS Office, Safari 컴포넌트 같은 닫힌 소스 타겟이 예시임.
3-3. google/fuzzing
처음에 다른 두 개랑 같이 묶여 있길래 "또 다른 퍼저인가?" 하고 들어가봤는데, 도구가 아니라 구글이 정리한 퍼징 가이드/문서/튜토리얼 모음집 이었음.
내용물을 대충 보면:
- Fuzz target 작성법: libFuzzer가 요구하는
LLVMFuzzerTestOneInput함수를 어떻게 짜야 하는지. - 좋은 시드 코퍼스 만드는 법.
- Dictionary 작성 가이드: 파일 포맷의 매직 바이트, 키워드 같은 걸 사전으로 미리 알려주면 퍼저 효율이 크게 올라간다고 함.
- OSS-Fuzz 연동 방법: 구글이 오픈소스 프로젝트를 무료로 퍼징해주는 서비스인데, 거기 등록하는 절차 설명이 있음.
- Glossary: corpus, coverage, sanitizer 같은 용어 정의.
도구는 아니지만 퍼징 시작하는 입문자가 가장 먼저 읽어야 할 문서 같음. AFL++나 libFuzzer를 효과적으로 쓰려면 여기 적힌 베스트 프랙티스를 알아야 함.
4. 세 가지 도구를 한눈에 비교
| 항목 | AFL++ | Jackalope | google/fuzzing |
|---|---|---|---|
| 종류 | Coverage-guided fuzzer | Black-box binary fuzzer | 문서/가이드 |
| 주 타겟 OS | Linux | Windows, macOS | (해당 없음) |
| 소스코드 필요? | 있으면 최적, 없어도 가능(QEMU mode) | 불필요 | - |
| 입력 생성 | Mutation-based | Mutation-based (커스텀 가능) | - |
| 진입 장벽 | 중간 | 높음 (직접 코드 작성 필요) | 낮음 (그냥 읽기) |
| 대표 사용처 | 오픈소스 C/C++ 라이브러리 | 상용 클로즈드 소스 SW | 모든 퍼징 학습자 |
5. 정리하면서 느낀 점
이번 미션 하면서 학교 수업에서 배운 것들이 보안이랑 어떻게 연결되는지 조금씩 보이기 시작했음.
- 자료구조: AFL++의 coverage bitmap, 시드 큐 부분이 그대로 자료구조 응용이었음. 해시·비트 연산·큐가 실전에서 어떻게 쓰이는지 체감됨.
- 컴퓨터네트워크: 네트워크 프로토콜 파서가 퍼징 단골 타겟이라더라. 학기 끝나면 간단한 HTTP 파서에 AFL++ 붙여서 돌려보고 싶음.
- 데이터베이스: SQL 파서나 쿼리 엔진도 generation-based 퍼징의 대표 타겟임. SQLite는 OSS-Fuzz로 매일 자동 퍼징되고 있다고 함.
- 리눅스/git: AFL++ 설치·빌드·실행이 전부 리눅스 위에서 돌아가는데, 최근에 git이랑 linux 공부한 게 바로 도움됨. 저장소 clone 하고 README 보면서 빌드하는 흐름이 자연스러워졌음.
다음 단계로는 직접 작은 C 프로그램에 일부러 buffer overflow를 심어 놓고 AFL++로 잡아보는 실습을 해볼 계획임. 이론으로 본 퍼저가 실제로 크래시를 찾아내는 순간을 한 번은 눈으로 봐야 진짜 이해될 것 같음.