[미래내일 일경험] 테크노트 - 7주차
AFL++ 백엔드 통합 및 1차 제출 준비 | 2026.05.11 ~ 05.17
| 프로젝트명 | AI 기반 퍼징을 활용한 취약점 탐지 및 검증 자동화 시스템 (FuzzGate) | ||
| 팀명 | The First | 작성자 | 이우곤 |
| 담당 역할 | 하네스 개발, 퍼징 파이프라인 구축, 포맷 연동 구현 | ||
📌 1. 금주 학습 목표
- AFL++ 백엔드 통합을 위한 어댑터 구조 설계 및 구현 (libFuzzer와의 결과 데이터 형식 차이 흡수)
- 백엔드 공통 파라미터(워커 수, 타임아웃, 재시작 한계)를 단일 CLI 플래그로 노출
- 단계 라벨 시스템(reproduced / not_reproduced / partial / timeout / flaky) 정식 도입
- High 심각도 4건에 대한 RCE 가능성 정밀 분석 및 1차 제출 후보 리포트 본문 검토
- SafeTensors Python 바인딩 하네스 추가 구현 (numpy/torch 변환 경로)
- GGUF 양자화 메타데이터 뮤테이션 전략 추가 (Q4_K, Q5_K 분기 도달)
📖 2. 학습 및 수행 내용
2-1. AFL++ 백엔드 통합 및 어댑터 구조 설계
지금까지 FuzzGate는 libFuzzer 백엔드만 지원하여 인프로세스 퍼징에 특화된 결과만 다룰 수 있었다. 그러나 AFL++는 fork 기반 실행 모델과 더 강력한 변이 엔진을 제공하므로, 동일 타깃에서도 libFuzzer와 다른 종류의 크래시를 발견하는 경우가 많다. 운영자가 분석 목표에 따라 백엔드를 선택할 수 있도록 만드는 것이 v1.0의 핵심 요구사항이었다.
문제는 두 백엔드의 실행 모델과 결과 데이터 형식이 완전히 다르다는 점이다. libFuzzer는 자체 형식의 크래시 입력을 단일 파일로 출력하는 반면, AFL++는 crashes/, queue/, hangs/ 등 별도 디렉터리 구조에 결과를 저장한다. 후속 단계인 재현 검증과 Triage가 백엔드 차이를 신경 쓰지 않도록, 어댑터 계층에서 결과 파일을 표준화된 위치와 형식으로 정리해 전달하는 구조를 설계하였다.
// crates/fuzzer-bridge/src/backend.rs - 백엔드 공통 인터페이스
pub trait FuzzBackend: Send + Sync {
/// 백엔드 식별자 (libfuzzer / aflpp / local-harness)
fn name(&self) -> &'static str;
/// 퍼징 실행 (공통 파라미터를 환경 변수 템플릿으로 변환)
fn run(&self, cfg: &FuzzConfig) -> Result<BackendHandle>;
/// 백엔드 고유 결과 디렉터리 → 표준 CrashArtifact 목록으로 변환
fn normalize_results(&self, raw_dir: &Path)
-> Result<Vec<CrashArtifact>>;
/// 컨테이너 안전 옵션 프리셋 (read-only, network=none, cap_drop 등)
fn safety_preset(&self) -> &SafetyPreset;
}
// AFL++ 어댑터 구현
impl FuzzBackend for AflPlusPlusBackend {
fn normalize_results(&self, raw_dir: &Path)
-> Result<Vec<CrashArtifact>> {
let mut artifacts = Vec::new();
let crashes_dir = raw_dir.join("default/crashes");
for entry in fs::read_dir(&crashes_dir)? {
let path = entry?.path();
// AFL++ 파일명: id:000042,sig:06,src:000001,op:havoc,...
// → 표준 CrashArtifact로 변환
if let Some(meta) = parse_aflpp_filename(&path) {
artifacts.push(CrashArtifact {
crash_id: format!("aflpp_{:06}", meta.id),
input_path: path.clone(),
input_sha256: sha256_file(&path)?,
backend: "aflpp".into(),
signal: meta.signal,
mutation_op: meta.op,
discovered_at: Utc::now(),
});
}
}
Ok(artifacts)
}
}
공통 파라미터(워커 수, 타임아웃, 재시작 한계)는 단일 CLI 플래그로 노출하였다. 운영자는 --backend aflpp 또는 --backend libfuzzer만 바꿔도 동일한 워크플로우로 퍼징을 실행할 수 있다.
# 동일 타깃에 대해 백엔드만 교체 실행 (CLI 인터페이스 동일)
fuzzgate run \
--target gguf \
--backend aflpp \
--workers 4 \
--timeout 24h \
--seeds ./corpus/gguf \
--output ./results/gguf_aflpp
fuzzgate run \
--target gguf \
--backend libfuzzer \
--workers 4 \
--timeout 24h \
--seeds ./corpus/gguf \
--output ./results/gguf_libfuzzer
✓ 어댑터 구조의 검증
GGUF 타깃에 대해 AFL++ 백엔드로 8시간 시범 퍼징을 수행한 결과, libFuzzer에서는 발견하지 못했던 fork 모델 특유의 환경에서만 재현되는 크래시 3건이 추가로 발견되었다. 어댑터를 통해 이 3건은 자동으로 표준 CrashArtifact 형식으로 변환되어 기존 재현 검증 · Triage · Report Generator 파이프라인을 그대로 통과했다. 백엔드 교체가 후속 단계에 전혀 영향을 주지 않음을 데이터로 확인한 것이다.
2-2. 단계 라벨 시스템 정식 도입
6주차까지는 재현 검증 결과를 "안정 재현 / 부분 재현 / 재현 실패"의 3단계로 비공식 분류해 왔으나, AFL++ 통합으로 백엔드별 비결정성 패턴이 다양해지면서 보다 정밀한 라벨 체계가 필요해졌다. 결과보고서 설계 원칙에 따라 5단계 단계 라벨 시스템을 정식 도입하였다.
| 라벨 | 의미 | 후속 처리 |
|---|---|---|
| reproduced | N회 시도 전체에서 동일 시그니처 일관 발생 | 리포트 생성 대상 |
| not_reproduced | N회 모두 재현 불가 | manual_review 큐 |
| partial | 결과 유형이 시도마다 다르나 일정한 경향성 존재 | manual_review 큐 |
| timeout | 정해진 시간 내 결과 확정 불가 | manual_review 큐 |
| flaky | 일부 시도에서만 크래시 발생 (비결정적) | manual_review 큐 |
하네스 측에서는 단계 라벨 산출에 필요한 정보를 정확히 전달하도록 출력 구조를 개선하였다. 특히 N회 반복 실행 시 각 시도의 종료 신호, 스택 상위 3프레임, 실행 시간을 모두 기록하여 Reproducer가 라벨을 결정론적으로 산출할 수 있도록 한다.
⚠️ 라벨 정의의 일관성 정책
초기에는 라벨 정의를 코드에만 두었더니, 운영 데이터가 누적되면서 분석자마다 라벨 의미를 다르게 해석하는 문제가 발생했다. 이번 주부터는 라벨 정의를 코드와 문서 양쪽에 명시하고, 라벨 산출에 사용된 입력 데이터를 디스크에 함께 보존하도록 정책을 정착시켰다. 동일 입력에 대해 라벨 결과가 시점에 따라 다르게 부여되지 않도록 하기 위한 조치다.
2-3. High 심각도 4건 RCE 가능성 정밀 분석
6주차에 우선 제출 후보로 분류한 High 심각도 4건(GGUF-001, GGUF-003, ONNX-D-001, ONNX-D-004)에 대해 RCE 가능성 정밀 분석을 수행하였다. 분석은 ASan 로그상의 메모리 오염 패턴, 종료 시점의 레지스터 상태, 그리고 크래시 위치의 코드 컨텍스트를 종합적으로 검토하는 방식으로 진행하였다.
| 크래시 ID | 유형 | RCE 가능성 평가 | CVSS 추정 |
|---|---|---|---|
| GGUF-001 | Heap Buffer Overflow | 쓰기 가능 영역, 입력 통제 가능 → RCE 가능성 높음 | 8.8 (High) |
| GGUF-003 | Integer Overflow → OOB Write | 정수 연산 결과로 인접 객체 손상 → 메모리 손상 후속 활용 가능 | 8.1 (High) |
| ONNX-D-001 | Heap UAF (shape inference) | 해제된 객체 재사용 시점 통제 가능 → RCE 가능성 있음 | 7.8 (High) |
| ONNX-D-004 | Type Confusion | 타입 혼동으로 인접 메모리 해석 변경 → 정보 유출 + RCE 가능성 | 8.3 (High) |
각 케이스의 CVSS 추정치는 exploitability signal 분류기가 산출한 권고형 라벨로, 최종 심각도는 외부 보안팀의 평가에 따른다. 본 프로젝트의 분류기는 ASan/UBSan 로그 패턴, 종료 신호, 스택 깊이를 가중치 점수로 결합해 분류하며, 사용자가 분류 결과를 단정적으로 해석하지 않도록 표현 정책을 함께 적용한다.
2-4. 1차 제출 후보 리포트 본문 검토
자동 생성된 리포트 4건의 본문을 인간이 직접 검토하면서, 자동 생성 시스템이 놓친 표현상의 미흡한 부분을 보완하였다. 특히 "Suggested Fix" 섹션은 v1.0 단계에서는 수동 작성을 전제로 두고 있으므로, 자동 생성된 권고형 분류 결과를 참고하여 분석자가 직접 작성하였다.
검토 과정에서 다음과 같은 보완 작업을 진행하였다.
- Title 표준화: 시그니처와 포맷 정보를 결합한 명명 규칙(예:
[GGUF] Heap Buffer Overflow in gguf_init_from_file)으로 통일하여 외부 보고 시 식별성 확보 - Impact 섹션 정제: 자동 생성된 CVSS 추정치는 권고형임을 명시하고, 실제 공격 시나리오를 분석자 관점에서 추가 서술
- Steps to Reproduce 검증: 증거 번들의
reproduce.sh를 클린 환경에서 실제로 실행하여 리포트의 재현 절차가 동작함을 확인 - 책임 있는 공개 절차 명시: HackerOne 정책에 따라 90일 비공개 기간을 본문에 명시하고, 연락처 정보 보강
2-5. SafeTensors Python 바인딩 하네스 구현
6주차 멘토링에서 받은 조언에 따라, SafeTensors의 Python 바인딩 변환 경로를 별도 하네스로 구현하였다. Rust 코어 자체는 메모리 안전성이 보장되지만, Python 바인딩이 numpy/torch 배열로 변환하는 과정에서는 dtype 매핑, 버퍼 복사, 메모리 뷰 처리 등 새로운 공격 표면이 존재한다.
# fuzz/fuzz_safetensors_python.py - atheris 기반 Python 바인딩 하네스
import atheris
import sys
with atheris.instrument_imports():
from safetensors import safe_open
from safetensors.numpy import load as np_load
from safetensors.torch import load as torch_load
import io
def test_one_input(data: bytes):
if len(data) < 8:
return
buf = io.BytesIO(data)
# 1. numpy 변환 경로
try:
np_tensors = np_load(data)
# 모든 텐서에 접근하여 lazy 변환 트리거
for k, v in np_tensors.items():
_ = v.shape, v.dtype
except (ValueError, OSError, MemoryError):
pass # 정상 거부
# 2. torch 변환 경로
try:
torch_tensors = torch_load(data)
for k, v in torch_tensors.items():
_ = v.shape, v.dtype
except (ValueError, RuntimeError, OSError):
pass
atheris.Setup(sys.argv, test_one_input)
atheris.Fuzz()
2시간 시범 퍼징 결과, numpy 변환 경로에서 흥미로운 신호 2건이 포착되었다. 하나는 특정 dtype 조합에서 numpy가 음수 크기를 가진 배열을 생성하려 시도하다 발생하는 OverflowError이고, 다른 하나는 헤더에 선언된 텐서 크기와 실제 데이터 크기가 일치하지 않을 때 발생하는 메모리 뷰 손상이었다. 두 케이스 모두 Rust 코어가 아닌 바인딩 계층의 검증 누락에서 비롯된 것으로, "안전한 코어를 감싸는 외피의 공격 표면"이라는 가설을 데이터로 확인하였다.
2-6. GGUF 양자화 메타데이터 뮤테이션 전략 추가
6주차의 llvm-cov 분석에서 식별된 정체 영역인 GGUF 양자화 메타데이터 처리 분기(Q4_K, Q5_K, Q8_0 등)에 도달하는 신규 뮤테이션 전략을 추가하였다. 기존 cross-field 뮤테이터에 양자화 타입을 명시적으로 변형하는 케이스를 추가하여, 파서가 양자화별 분기 처리 로직을 본격적으로 탐색하도록 유도하였다.
// 양자화 타입 명시 변형 (GGML_TYPE_Q4_K, Q5_K, Q8_0 등을 의도적으로 지정)
size_t mutate_quantization_type(uint8_t *data, size_t size,
size_t max_size, struct gguf_view *view) {
if (view->tensor_count == 0) return size;
// GGML 양자화 타입 enum (일부)
static const uint32_t quant_types[] = {
2, // GGML_TYPE_Q4_0
3, // GGML_TYPE_Q4_1
12, // GGML_TYPE_Q4_K
13, // GGML_TYPE_Q5_K
8, // GGML_TYPE_Q8_0
9, // GGML_TYPE_Q8_1
14, // GGML_TYPE_Q6_K
};
struct tensor_info *t = &view->tensors[0];
uint32_t qtype = quant_types[rand() % (sizeof(quant_types)/sizeof(uint32_t))];
memcpy(data + t->type_offset, &qtype, sizeof(uint32_t));
// 양자화 블록 크기에 맞지 않는 데이터 크기를 함께 유도
// → 양자화별 블록 정렬 검증 분기 도달
return size;
}
신규 뮤테이션 전략을 적용한 4시간 비교 퍼징 결과, 양자화 메타데이터 처리 분기의 진입률이 8% 미만에서 34%까지 상승하였다. 또한 Q4_K 양자화 블록의 super-block 크기 계산 분기에서 새로운 예비 크래시 2건이 발견되어, 신규 분기 도달이 곧 신규 결함 발견으로 이어졌다.
🛠️ 3. 수행 활동
3-1. 개인 수행 활동
- AFL++ 백엔드 어댑터 구현 (
FuzzBackendtrait 기반, 결과 디렉터리 정규화 로직 포함) - 백엔드 공통 파라미터를 단일 CLI 플래그로 노출 (
--backend,--workers,--timeout) - 단계 라벨 시스템(reproduced/not_reproduced/partial/timeout/flaky) 산출에 필요한 하네스 출력 보강
- High 심각도 4건 RCE 가능성 정밀 분석 및 CVSS 추정치 산출
- 1차 제출 후보 리포트 4건의 본문 검토 및 표준 명명 규칙 적용
- SafeTensors Python 바인딩 하네스 신규 구현 (
atheris기반, numpy/torch 변환 경로 커버) - GGUF 양자화 메타데이터 뮤테이션 전략 추가 및 비교 퍼징 수행 (정체 분기 진입률 8% → 34%)
3-2. 팀 활동
- 온라인 정기 미팅 (05.17, Discord): AFL++ 백엔드 통합 결과와 High 4건 분석 결과를 공유, 1차 제출 일정을 다음 주 초로 확정. 단계 라벨 정식 도입에 대한 팀 합의
- 전선정 팀원과 페어 작업 (05.13~05.14): 단계 라벨 시스템 도입을 위해 Reproducer의 라벨 산출 로직을 함께 설계. N회 시도 결과 분포에 따른 분기 트리(reproduced → flaky → partial → not_reproduced)를 코드와 문서 양쪽에 명시
- 신숙우 팀장·황희주 팀원과 협업 (05.15): 1차 제출 후보 리포트의 본문을 함께 검토. 특히 Title 표준화와 Suggested Fix 작성 가이드라인을 합의하여, 향후 리포트 품질이 작성자에 따라 달라지지 않도록 정책화
- 멘토링 세션 (05.16): 멘토로부터 AFL++ 어댑터 구조가 "백엔드 교체가 후속 단계에 영향을 주지 않는 설계"라는 점에서 v1.0의 핵심 기여라는 평가를 받음. 또한 Python 바인딩 하네스에서 발견된 dtype 매핑 이슈는 numpy 생태계 전반에 영향을 줄 수 있어 별도 카테고리로 분류하라는 조언 수령
- 책임 있는 공개 절차 사전 협의: 1차 제출에 앞서 llama.cpp·onnxruntime 프로젝트 메인테이너에게 사전 연락 채널을 확보. 제출 후 90일 비공개 기간 동안의 커뮤니케이션 책임을 신숙우 팀장이 담당하기로 분담
📋 4. 다음 주 계획 (8주차)
계획서상 8주차 키워드: "FuzzGate v1.0 통합 검증 및 최종 산출물 정리"
- High 심각도 4건의 1차 제출 진행 (llama.cpp 2건, onnxruntime 2건)
- FuzzGate v1.0 통합 테스트 수행 (3개 백엔드 × 3개 타깃 어댑터 매트릭스 검증)
- 단일 ZIP 번들 일괄 출력 기능 통합 및 번들 무결성 해시 이중 기록 정착
- CLI 사용자 매뉴얼 작성 및 컨테이너 안전 옵션 표준 프리셋 문서화
- SafeTensors numpy 변환 경로의 dtype 매핑 이슈 추가 검증 및 리포트 작성
- 최종 결과보고서 작성 (정량 지표 정리, 산출물 목록, 향후 확장 항목 명시)
💬 5. 느낀점 및 회고
7주차는 "FuzzGate가 단일 백엔드 도구에서 진정한 의미의 플랫폼으로 확장된 한 주"였다. AFL++ 어댑터를 구현하면서 가장 크게 느낀 것은, 6주에 걸쳐 다듬어 온 표준 인터페이스(CrashArtifact, ReproducedCrash)의 가치였다. 만약 이 인터페이스가 없었다면 AFL++ 통합은 단순히 어댑터 하나 추가가 아니라 후속 단계 전체를 다시 작성해야 하는 작업이 되었을 것이다. "느슨한 결합과 표준 인터페이스"라는 1주차의 설계 원칙이, 7주차에 와서야 그 효과를 온전히 드러냈다는 점이 가장 인상적이었다.
단계 라벨 시스템을 정식 도입하면서, "분류는 단순한 라벨 붙이기가 아니라 후속 의사결정의 출발점"이라는 것을 다시 한 번 체감했다. 초기에는 라벨 정의를 코드에만 두었더니 분석자마다 해석이 달라지는 문제가 있었고, 이를 코드와 문서 양쪽에 명시하고 라벨 산출 입력 데이터를 함께 보존하는 정책으로 정착시켰다. 이는 단순한 엔지니어링 결정이 아니라 "도구가 일관된 결과를 산출하기 위한 거버넌스"에 가깝다는 생각이 들었다. 보안 도구는 사용자의 신뢰 위에서만 작동하므로, 같은 입력에 다른 결과가 나오지 않게 만드는 것이 가장 근본적인 요구사항이었다.
High 심각도 4건의 RCE 가능성 분석은 이번 프로젝트에서 가장 긴장된 작업이었다. 자동 분류기가 산출한 "High"라는 라벨을 그대로 받아들이지 않고, ASan 로그 한 줄씩 검토하면서 실제로 공격자가 통제 가능한 메모리 영역인지를 판단하는 과정은, 6주 동안 만들어 온 자동화 도구에 "분석자의 판단"이라는 마지막 검증 단계가 왜 필요한지를 깨닫게 해 주었다. 자동화는 반복 작업을 줄여 주지만, 최종 판단의 책임까지 도구에 넘길 수는 없다는 결과보고서의 권고형 라벨 정책이 실제 작업 현장에서 어떻게 적용되는지를 직접 경험한 셈이다.
SafeTensors Python 바인딩 하네스에서 변환 경로 이슈를 발견한 것은 흥미로운 경험이었다. 6주차에 "안전한 코어를 감싸는 외피에 또 다른 공격 표면이 있을 수 있다"고 가설로만 적었던 부분이, 7주차에 실제 데이터로 확인되었다. 같은 SafeTensors 라이브러리라도 Rust 코어에서는 1시간 동안 메모리 오류 0건이었던 반면, Python 바인딩에서는 2시간 만에 dtype 매핑 이슈와 메모리 뷰 손상이 발견되었다. "보안은 가장 약한 고리에서 결정된다"는 격언이 단순한 수사가 아니라는 것을 데이터로 본 순간이었다.
다음 주는 드디어 1차 제출과 v1.0 통합 검증이 있는 마지막 주다. 8주 전에 "퍼징 도구를 만들어 보자"로 시작한 프로젝트가, 실제로 외부 오픈소스 프로젝트에 책임 있는 공개 절차를 통해 취약점을 제출하는 단계까지 도달했다는 것이 아직도 실감이 잘 나지 않는다. 동시에 v1.0이라는 정식 버전이 붙은 산출물을 정리하면서, 이 도구가 8주의 일경험을 넘어 후속 단계에서도 계속 발전할 수 있는 토대가 되기를 바라는 마음이 크다. 마지막 주에 통합 매트릭스 검증을 무사히 완료하고, 1차 제출이 외부 프로젝트의 보안 강화에 작은 기여가 되기를 기대한다.
📚 6. 참고 자료
'ABC 미래내일 프로젝트' 카테고리의 다른 글
| [2026 ABC 프로젝트 멘토링 3기] 프로젝트 8주차 (0) | 2026.05.26 |
|---|---|
| [2026 ABC 프로젝트 멘토링 3기] 프로젝트 6주차 (0) | 2026.05.11 |
| [2026 ABC 프로젝트 멘토링 3기] 프로젝트 5주차 (0) | 2026.05.03 |
| [2026 ABC 프로젝트 멘토링 3기] 프로젝트 4주차 (1) | 2026.04.26 |
| [2026 ABC 프로젝트 멘토링 3기] 프로젝트 3주차 (0) | 2026.04.20 |