콘텐츠로 이동

2026년 현대 퍼징: 스냅샷, 프레임워크, 그리고 이전에 할 수 없었던 퍼징

· 13 min read · default
cybersecurityfuzzingvulnerability-researchreverse-engineeringtestingsecurity

퍼징은 평판 문제를 가지고 있습니다: 많은 엔지니어들은 여전히 그것을 프로그램에 무작위 바이트를 던지는 것으로 그림으로 생각합니다. 그 설명은 1990년에 거의 정확했으며 10년 동안 오도되었습니다. 현대 퍼징은 커버리지 기반입니다 — 퍼저는 대상을 계측하고, 각 입력이 도달하는 코드 경로를 관찰하고, 탐사되지 않은 영역으로 돌연변이를 안내합니다. 그 피드백 루프는 파서를 무작위로 찌르는 것과 체계적으로 그 상태 공간을 걷는 것의 차이이며, 퍼징이 이제 전문가에 의해 검토된 소프트웨어에서 매년 수천 개의 실제 CVE를 찾는 이유입니다.

2026 경관은 단일 도구를 훨씬 넘어서 성숙했으며, 흥미로운 발전은 기존 퍼징이 할 수 없는 대상에 도달하는 것에 대합니다: 운영 체제 커널, 비용이 큰 설정 뒤의 코드, 그리고 입력 구조가 바이트 돌연변이를 꺾는 응용 프로그램. 이 가이드는 그 경관을 다룹니다 — 커버리지 기반 고전, 커널용 syzkaller, 스냅샷 퍼징용 Snapchange, 사용자 정의 퍼저 구축용 LibAFL — 더하기 cargo-fuzzhonggfuzz 일상 작업용.

커버리지 안내가 모든 것을 바꾼 이유

메커니즘은 이해할 가치가 있습니다. 왜냐하면 퍼징이 좋은 것과 어디서 막히는지 설명하기 때문입니다. 커버리지 기반 퍼저는 제어 흐름 그래프의 어느 가장자리를 실행이 건드렸는지 기록하는 계측으로 대상을 컴파일합니다. 입력 코퍼스를 유지하고 변경된 입력이 이전 입력이 닿지 않은 가장자리에 도달할 때, 그 입력은 흥미롭다고 판단되고 더 이상 변경되기 위해 코퍼스에 추가됩니다. 수백만 반복에 걸쳐 코퍼스는 집합적으로 깊고 이상한 경로를 운동하는 입력을 축적합니다 — 사람이 쓴 테스트를 포함하여.

이는 거의 지능적으로 보이는 동작을 생산합니다. 유효한 PNG로 시드된 코퍼스가 주어지면, 퍼저는 청크 구조를 발견한 다음 유효한 청크 타입, 각 타입에 대한 파싱 분기, 점진적으로 디코더로 더 깊이 도달하는 입력을 발견할 것입니다. 그것은 절대 PNG를 "이해"하지 않습니다; 그것은 단지 커버리지를 이동한 것을 유지합니다.

따름은 퍼징이 막히는 곳입니다: 어려운 검사가 그것을 추측할 수 없습니다. 매직 상수 비교, 체크섬, 또는 암호화 서명은 벽을 만듭니다 — 무작위 돌연변이는 본질적으로 올바른 8 바이트를 생성하지 않으므로 그 검사 뒤의 모든 것이 탐사되지 않습니다. 실제 응답은 유효한 입력으로 코퍼스를 시드하거나, 매직 값 사전을 공급하거나, 또는 퍼즈 빌드에서 체크섬을 패치하는 것입니다. 커버리지 안정화를 "더 이상 버그가 없다"로 인식하는 것이 아니라 "벽을 쳤다"로 인식하는 것이 이 작업에서 가장 유용한 직관 중 하나입니다.

커널 퍼징: syzkaller

운영 체제 커널은 기존 퍼저에 대한 적대적 목표입니다. 입력은 파일이 아니라 시스템콜 시퀀스이며, 상호 종속적인 인수가 있습니다 — open의 파일 설명자는 read로 흐르고, 대부분의 무작위 시스콜 시퀀스는 EINVAL로 즉시 실패합니다. 충돌은 전체 기계를 가져 내리므로 자동 수집이 아니며, 커버리지는 커널 공간에서 수집되어야 합니다.

syzkaller는 모두 해결합니다. 이는 선언적 언어(syzlang)에서 시스콜을 설명하므로 올바르게 타입화되고 상호 종속적인 인수가 있는 그럴듯한 시퀀스를 생성할 수 있습니다; 이는 일회용 VM 내에서 대상을 실행하므로 충돌은 생존하고 자동으로 수집됩니다; 그리고 커널 커버리지 피드백을 위해 KCOV 및 메모리 오류를 캐치하기 위해 KASAN을 사용합니다. Google의 syzbot은 그것을 계속 Linux에 대해 실행하고 수천 개의 버그를 보고했습니다.

교훈은 커널을 넘어 일반화됩니다: syzkaller는 작동하는 이유는 누군가가 입력 구조에 대한 지식을 설명으로 인코딩했기 때문입니다. 대상의 입력에 문법이 있을 때, 퍼저에게 그 문법을 가르치는 것이 원본 바이트 돌연변이를 비상으로 이깁니다. 대응하는 비용은 실제입니다 — 테스트되지 않은 하위 시스템을 위해 syzlang을 확장하는 것은 진정한 작업이며, 대부분의 사람들이 커널 퍼징에 기여할 수 있는 가장 높은 가치의 것이기도 합니다.

스냅샷 퍼징: 설정을 지나감

두 번째 경계는 흥미로운 코드가 비용이 큰 초기화 뒤의 깊은 곳에 있는 대상입니다. 데이터베이스의 쿼리 파서를 퍼징하는 것을 고려합니다: 각 반복은 서버를 시작하고, 저장소를 초기화하고, 인증하고, 세션을 설정해야 하며, 그 후 단일 쿼리가 파싱됩니다. 아마도 초당 10번의 반복에서 커버리지 기반 퍼징은 절망적입니다 — 기술이 수천을 필요합니다.

스냅샷 퍼징은 이것을 반전시킵니다. 대상을 정확히 관심 순간으로 한 번 실행하고, 전체 기계 상태의 메모리 스냅샷을 가져가고, 그 후 매 후속 반복에 대해 그 스냅샷을 복원합니다. 모든 설정 비용이 한 번 지급됩니다. Snapchange (AWS)는 KVM으로 이것을 구현합니다: QEMU로 스냅샷을 캡처하고, 입력을 주입할 위치와 반복이 끝나는 시간을 설명하는 작은 Rust 하네스를 작성하고, 그것이 매우 높은 속도로 그 상태에서 재생합니다.

이것은 이전에 비실용적인 범주를 잠금 해제합니다: 상태 저장 네트워크 프로토콜이 세션 중간에 퍼징되고, 인증 뒤의 코드, 하이퍼바이저 및 커널 코드, 그리고 무거운 시작이 있는 모든 응용 프로그램. 거래는 노력입니다 — 당신은 명령어를 실행하는 대신 Rust 퍼저를 작성하고, 입력을 올바르게 주입하기 위해 대상의 메모리 레이아웃을 충분히 이해해야 합니다. 그것은 정확히 대안이 대상을 퍼징하지 않는 전문가 기법입니다.

프레임워크: 대상이 필요로 하는 퍼저를 구축합니다

세 번째 발전은 철학적입니다. AFL++과 libFuzzer는 그들이 설계된 것에 우수하고 당신의 대상이 맞지 않을 때 어색합니다 — 사용자 정의 바이너리 프로토콜, 에뮬레이트된 펌웨어 이미지, 버퍼가 아닌 트리인 입력. 역사적으로 당신은 도구를 구부렸으며, 보통 나빴습니다.

AFL++ 팀의 LibAFL은 퍼저를 조합 가능한 부분으로 취급합니다: 데이터를 기록하는 옵저버, 흥미로운 것을 판단하는 피드백, 입력을 변환하는 변경자, 스케줄러, 단계 및 실행기. 당신의 대상이 필요로 하는 조합을 조합하고, 입력이 구조화되면 당신 자신의 입력 타입 및 변경자를 정의하고, 실행기를 선택하고 (프로세스 내, 포크서버, QEMU 에뮬레이션, Frida 계측, 스냅샷), 무료로 멀티코어 스케일링을 얻습니다.

정직한 프레이밍은 이것이 도구를 실행하는 것보다 더 큰 약속이므로 시퀀스가 중요합니다: Rust의 경우 cargo-fuzz로 시작하거나 네이티브 대상의 경우 honggfuzz/AFL++, 그리고 당신이 구체적으로 맞지 않는 이유를 명확하게 표현할 수 있을 때만 LibAFL로 이동합니다. "입력이 프로토콜 상태 머신이고 바이트 돌연변이는 절대 유효한 두 번째 메시지를 생성하지 않습니다"는 그러한 이유입니다; "더 빨랐으면 좋겠습니다"는 보통 그렇지 않습니다.

대부분의 팀이 실제로 지속하는 일상적 퍼징

대부분의 가치는 대부분의 팀에 대해 화려하지 않은 지속적 파서 및 신뢰할 수 없는 입력 핸들러 퍼징에서 나옵니다. cargo-fuzz는 Rust를 거의 마찰 없이 만듭니다: &[u8] 취하는 함수를 작성하거나 (더 나은, arbitrary 상자를 통한 타입화된 값), 그리고 하나의 명령어를 실행합니다. honggfuzz는 네이티브 코드에 유사하게 쉬우며 Intel PT/BTS를 통한 하드웨어 기반 커버리지를 추가합니다. 이것은 재컴파일할 수 없는 바이너리를 퍼징할 수 있습니다 — 폐쇄 소스 종속성을 위해 귀중합니다.

두 가지 관행은 가치를 얻는 팀과 한 주 후에 퍼징을 버리는 팀을 분리합니다. 먼저, 실제 유효한 입력으로 코퍼스를 시드합니다; 빈 코퍼스에서 시작하는 퍼저는 기본 형식 유효성을 재발견하는 데 엄청난 시간을 낭비합니다. 둘째, 계속 실행하고 발견을 테스트로 취급합니다: 최소화된 각 충돌을 회귀 테스트로 변환하여 수정된 상태로 유지하고, 코퍼스를 실행 간 지속하여 진행이 축적되도록 합니다. 퍼징은 일회 감사가 아닙니다; 그것은 코드가 변경될 때 계속 물건을 찾는 배경 프로세스입니다.

살균기는 효과를 곱셈하기 때문에 언급할 가치가 있습니다. AddressSanitizer는 침묵하는 메모리 손상을 즉시 진단 가능한 충돌로 전환하고, UBSan은 나중에 신비한 오류 컴파일로 나타날 수 있는 정의되지 않은 동작을 캐치합니다. 살균기 없이 퍼징은 자신의 부탁으로 충돌하는 경향이 있는 버그만 찾습니다 — 보통 무엇이 실제로 거기 있는지의 작은 분수.

심사: 충돌이 도착할 때 시작되는 작업

충돌을 찾기는 시작이며, 팀은 "퍼저가 중지" 및 "개발자가 이것을 수정할 수 있는" 사이 노력을 일상적으로 과소평가합니다. 네 단계는 유용한 보고서와 무시되는 보고서를 만드는 차이를 만듭니다.

입력을 최소화합니다. 퍼저에서 충돌 입력은 일반적으로 생존한 관련 없는 바이트로 가득 찹니다. 모든 진지한 퍼저는 최소화기를 제공합니다 (cargo fuzz tmin, afl-tmin, syzkaller의 syz-repro), 그리고 이것을 실행하면 4KB 블롭이 실제 트리거를 격리하는 한 줌의 바이트로 변합니다. 이것이 이해해야 하는 개발자에게 어느 정도 중요한지를 거대하게 문제입니다.

중복 제거합니다. 밤새 실행되는 퍼저는 다른 입력을 통해 같은 버그를 수십 번 보고할 것입니다. 충돌 위치 및 스택 서명으로 그룹화하면 200 충돌이 6개 고유 버그로 변합니다. 이 단계 없이 심사는 불가능해 보이고 사람들이 포기합니다.

착취 가능성을 신중하게 평가합니다. 모든 충돌이 취약점은 아닙니다. 파서의 null 포인터 해제는 보통 서비스 거부입니다; 공격자 제어 길이가 있는 힙 버퍼 오버플로우는 잠재적으로 훨씬 더 심합니다. 살균기 출력은 여기서 엄청나게 도움이 됩니다 — ASan은 메모리 오류 타입, 크기, 그리고 할당 및 접근 스택 모두를 보고합니다. 모든 것을 심각으로 레이블 지우는 충동에 저항하십시오. 왜냐하면 부풀려진 심각도를 받는 팀이 보고서를 신뢰하는 것을 중지하기 때문입니다.

회귀 테스트로 변환합니다. 최소화된 입력은 수정과 함께 커밋된 단위 테스트가 됩니다. 이것이 버그가 수정된 상태로 유지되는 것이며, 리팩터 후 같은 문제를 재발견하는 것보다 퍼징이 시간에 따라 복합되도록 만드는 것입니다.

지속적인 퍼징 가치를 얻는 팀은 이 파이프라인을 한 번 구축하는 것이며, 대부분의 충돌을 찾는 것이 아닙니다.

어디서 시작할지 선택

결정은 대상을 따릅니다. Rust 코드의 경우, cargo-fuzz를 사용하고, arbitrary를 사용하여 바이트 파서만이 아닌 타입화된 API를 퍼징합니다. 소스가 있는 네이티브 사용자 공간 바이너리의 경우, honggfuzz 또는 살균기가 있는 AFL++. 소스가 없는 바이너리의 경우, honggfuzz의 하드웨어 피드백 또는 AFL++의 QEMU 모드. OS 커널의 경우, syzkaller, 그리고 관심 있는 하위 시스템에 대해 syzlang 확장을 고려합니다. 비용이 큰 설정 뒤의 코드 또는 상태 저장 세션의 경우, Snapchange 또는 스냅샷 가능 LibAFL 설정. 그리고 실제 구조가 있는 입력의 경우 — 프로토콜, AST, 문법이 있는 파일 형식 — 또는 문법 인식 변경자 또는 이상한 대상과 맞춤 LibAFL 퍼저, 왜냐하면 바이트 돌연변이는 일찍 안정화될 것이기 때문입니다.

이 모든 것에 걸쳐, 같은 규율이 적용됩니다: 잘 시드하고, 살균기로 실행하고, 계속 실행하고, 충돌을 최소화하고, 모든 발견을 테스트로 변환합니다. 도구가 중요하기보다는 루프가 계속 실행하는 것이 중요합니다.

핵심 바닥

2026년 퍼징은 커버리지 피드백 위에서 구축된 체계적 취약점 발견 규율이며, 그것의 경계는 이전에 범위를 벗어난 대상에 도달합니다. syzkaller는 시스콜 구조를 인코딩하고 일회용 VM에서 실행하여 커널을 퍼징합니다; Snapchange는 KVM 스냅샷을 사용하여 비용이 큰 설정 뒤의 코드를 퍼징합니다; LibAFL은 단일 도구를 구부리는 대신 이상한 대상과 맞춘 퍼저를 구축하도록 합니다; 그리고 cargo-fuzzhonggfuzz는 일상적 계속 퍼징을 실제로 지속하기에 충분히 저렴하게 만듭니다. 당신의 언어에 대한 쉬운 도구로 시작하고, 실제 입력으로 코퍼스를 시드하고, 항상 살균기를 활성화하고, 커버리지 안정화를 모든 명확으로 넘어서 벽으로 정의하고, 모든 충돌을 테스트로 변환합니다.

참조 및 리소스

도구

배경 및 분석

관련 1337skills 치트시트