Fuzzing이란
퍼징은 프로그램에 무작위 입력을 넣어서 크래시나 버그를 자동으로 찾아내는 소프트웨어 테스팅 기법으로 1980년대부터 있었지만, 최근 보안 연구에서 다시 각광받고 있다.
핵심 구성 요소는 다음과 같다.
1. 입력 생성 (Input Generation)
- 모델 기반: 입력 형식(문법)을 알고 유효한 테스트 케이스 생성
- 뮤테이션 기반: 기존 시드 입력을 무작위로 변형 → AFL, honggfuzz 같은 도구가 사용
2. 실행 엔진 (Execution Engine)
- 생성된 입력으로 프로그램을 실행하고 크래시 감지
- Sanitizer(ASan, UBSan 등)를 사용해 메모리 오류나 정의되지 않은 동작을 더 잘 탐지
- Fork 서버, 퍼시스턴트 퍼징 등으로 실행 속도 최적화

실제 예시 - pdf reader 퍼징
- PDF 사양은 매우 복잡함
- 파싱 코드도 복잡해질 수밖에 없음
- 악성코드 작성자들이 PDF를 공격 벡터로 활용
- 따라서 PDF 리더는 반드시 퍼징 테스트를 거쳐야 함
퍼징은 한계점이 있다. 퍼징은 올바르게 동작하는지는 검증하지 못한다. 잘못 동작하는 지만 탐지한다.
그래서 퍼징 테스트 이전에 정상적으로 동작하는지 확인해야 퍼징 효과가 극대화 된다.
- 런타임 체킹 (assertions)
- 유효하지 않은 상태를 최대한 빨리 감지하도록 코드 작성
Naive Fuzzer vs Intelligent Fuzzer
Naive Fuzzer (단순 퍼저)
while (true) {
input = generate_random_bytes(); // 완전 무작위
feed_to_target(input);
check_crash();
}
단순 퍼저는 무작위로 입력값을 생성하는데 이는 다음과 같은 문제가 있다.
컴파일러 퍼징 예시
무작위 ASCII 문자열 생성:
"x8#kL@9!mZ..." → 파서가 즉시 거부 (문법 오류)
"aB3$qR7..." → 파서가 즉시 거부 (문법 오류)
"zY#2@..." → 파서가 즉시 거부 (문법 오류)
따라서 결과는 다음과 같다.
- 99.99%의 입력이 파서 첫 단계에서 거부됨
- 최적화(optimizer), 코드 생성(codegen) 로직은 전혀 테스트 안 됨
- 컴퓨팅 자원만 낭비
Intelligent Fuzzer (지능형 퍼저)
퍼징은 소프트웨어에 무작위 값을 입력으로 넣어 예상치 못한 에러를 찾아내는 자동화된 테스트 방식이다.
하지만 완전히 무작위로 하면 비효율적이므로 다음과 같은 최적화 기법을 사용한다.
Corpus 활용
유효한 IPv6 패킷들을 시드로 사용
↓
시드를 조금씩 변형 (뮤테이션)
↓
유효한 형식을 유지하면서 다양한 입력 생성
입력 형식 명세
IPv6 패킷 구조는 다음과 같다.

퍼저가 이 구조를 알고 있으면 Version 필드는 항상 6으로 설정하고(version bit는 항상 6이여야함)
나머지 필드만 무작위로 변형하여 기본 검증을 통과하여 더 깊은 코드까지 도달할 수 있다.
피드백 메커니즘
피드백 없는 퍼저는 다음과 같은 문제가 있다.
while (true) {
input = generate_input();
result = run_target(input);
if (result == CRASH) {
report_bug();
}
// 크래시 아니면? → 그냥 버림
// 이 입력이 "좋은" 입력인지 알 방법 없음
}

- Stage 1: 입력의 각 구성요소 길이가 올바른지 검증
- Stage 2: 각 구성요소의 값이 유효한지 검증
Naive Fuzzer의 경우 stage 2까지 가는 경우는 거의 발생하지 않는다.
입력 1000개 생성
↓
900개가 Stage 1에서 실패 (길이 오류)
↓
100개만 Stage 2에 도달
↓
50개만 Stage 2 통과
↓
Stage 2 검증 로직은 거의 테스트 안 됨!
피드백이 있으면 stage 1을 통과한 입력을 기반으로 stage2를 테스트할 수 있다.
입력 생성 → Stage 1 통과 여부 확인
↓
"아, 이 입력은 Stage 1을 통과했구나!"
↓
이 입력을 저장하고, 이걸 기반으로 변형
↓
Stage 1을 통과하는 입력이 점점 많아짐
↓
Stage 2 검증 로직도 충분히 테스트됨
Coverage-guided Fuzzing
피드백을 얻는 방법은 다음과 같다.
| 방법 | 설명 | 단점 |
| 시간 측정 | 처리 시간이 짧으면 → 기본 검증 실패로 추정 | 부정확함, 휴리스틱 |
| 코드 커버리지 | 어떤 코드가 실행됐는지 직접 추적 | 계측(instrumentation) 필요 |
코드 커버리지 방식의 핵심 프로세스는 다음과 같다.
입력 → 실행 → 어떤 코드 라인/블록이 실행됐는지 기록
↓
새로운 코드가 실행됐으면
↓
"이 입력은 가치 있다!"
↓
코퍼스에 추가하고 변형 시도
예시
// 테스트 대상 함수
void process(int cmd, char* data) {
if (cmd == 1) { // 블록 A
handle_type1(data);
} else if (cmd == 2) { // 블록 B
handle_type2(data);
} else if (cmd == 3) { // 블록 C (레어 케이스)
handle_type3(data);
}
}
퍼징 과정
- Round 1: input = {cmd=1, data="abc"}
- → 블록 A 실행 ✓
- → 커버리지: {A}
- → 새로운 커버리지! 코퍼스에 추가
- Round 2: input = {cmd=1, data="xyz"} (Round 1 변형)
- → 블록 A 실행
- → 커버리지: {A}
- → 새로운 커버리지 없음, 버림
- Round 3: input = {cmd=2, data="abc"} (cmd 변형)
- → 블록 B 실행 ✓
- → 커버리지: {A, B}
- → 새로운 커버리지! 코퍼스에 추가
- Round 4: input = {cmd=3, data="xxx"}
- → 블록 C 실행 ✓
- → 커버리지: {A, B, C}
- → 새로운 커버리지! 코퍼스에 추가
대표적인 커버리지 기반 퍼저들은 다음과 같다.
| fuzzer | target | feature |
| AFL | 일반 프로그램 | 가장 유명, 바이너리 계측 |
| libFuzzer | 라이브러리 | LLVM 통합, in-process |
| syzkaller | OS 커널 | 시스템 콜 퍼징, 다중 OS 지원 |
References
- https://nebelwelt.net/blog/2019/0401-FuzzTrain.html
- https://freebsdfoundation.org/wp-content/uploads/2021/01/Kernel-Fuzzing.pdf
'Security > Fuzzing' 카테고리의 다른 글
| syzkaller를 이용한 커널 퍼징 #2 Syzkaller의 동작 구조 (0) | 2025.12.10 |
|---|