0-day는 어떻게 발굴되는가 — 패치 디핑·커버리지 퍼징·정적분석으로 신규 취약점을 찾는 실전 방법론
“제로데이를 발견했다”는 한 줄 뒤에는 패치 디핑, 퍼징 하네스 설계, 크래시 트리아지, 루트코즈 분석, 익스플로잇 가능성 판단이라는 길고 반복적인 과정이 있습니다. 이 글은 GOTROOT 연구팀이 실제로 영향도 상(RCE·버퍼 오버플로우 등)의 취약점을 발굴해 CVE로 등록할 때 사용하는 워크플로를, 마케팅 언어를 걷어내고 연구원 관점에서 정리한 것입니다. 핵심 질문은 하나입니다 — “공격자가 아직 모르는 결함을, 공격자보다 먼저 찾을 수 있는가?”
용어부터: 0-day, N-day, 그리고 “패치 갭”

0-day — 벤더도 공개 패치도 없는 미지의 취약점. 탐지 시그니처가 존재하지 않으므로 방어가 가장 어렵습니다.
N-day(=1-day) — 공개·패치됐지만 운영 환경에 적용되지 않은 취약점. “패치 갭” 동안 공격자는 공개된 PoC를 그대로 사용합니다.
실무 위협은 0-day의 희소성보다 N-day의 보편성에서 더 자주 발생합니다. 그러나 0-day 발굴 역량은 N-day를 더 빨리 무기화/방어하는 직관으로 이어집니다.
1) 패치 디핑(N-day diffing) — 막 나온 패치를 역으로 읽기
벤더가 보안 패치를 배포하면, 패치 전/후 바이너리(또는 소스)의 차이가 곧 “무엇이 취약했는가”의 지도입니다. 바이너리 디핑 도구로 변경된 함수만 추려 분석하면, 공개 PoC가 나오기 전에 익스플로잇 경로를 재구성할 수 있습니다.
바이너리 비교:
BinDiff,Diaphora로 변경 함수 식별경계 검사(길이·부호·정수 오버플로) 추가 여부가 핵심 단서
소스 기반이면 보안 커밋의 diff와 커밋 메시지(“fix bounds check” 등)를 추적
패치는 취약점의 답안지입니다. 답안지를 거꾸로 읽는 훈련이 곧 N-day 대응 속도입니다.
2) 커버리지 기반 퍼징 — 미지의 입력으로 크래시를 유도
0-day의 상당수는 “예상 못 한 입력”에서 나옵니다. 커버리지 기반 퍼저(AFL++, libFuzzer)는 새로운 코드 경로를 밟는 입력을 진화적으로 생성해, 파서·디코더·프로토콜 처리부의 메모리 손상을 드러냅니다. 핵심은 대상 함수를 직접 호출하는 하네스(harness) 설계입니다.
// libFuzzer 하네스 골격 — 대상 파서를 직접 호출
#include <stddef.h>
#include <stdint.h>
extern int parse_packet(const uint8_t *buf, size_t len);
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size == 0) return 0;
parse_packet(data, size); // ASAN으로 빌드 → OOB/UAF 즉시 검출
return 0;
}
// clang -g -O1 -fsanitize=fuzzer,address harness.c target.c -o fuzz시드 코퍼스: 유효 샘플(정상 패킷·파일)로 시작해야 깊은 경로에 빨리 도달
샌: ASAN/UBSAN/MSAN으로 빌드해 무증상 손상까지 크래시로 노출
커버리지: 새 경로를 못 밟으면 사전(dictionary)·구조 인지(grammar) 퍼징으로 전환
3) 크래시 트리아지와 루트코즈 분석
퍼저가 만든 수백 개의 크래시는 대부분 같은 버그의 중복이거나 익스플로잇 불가능한 NULL 역참조입니다. 가치 있는 버그를 가려내려면 크래시를 최소 재현 입력으로 줄이고(minimize), 손상 유형(OOB write, UAF, type confusion)을 분류한 뒤, 제어 가능한 메모리에 도달하는지 추적해야 합니다.
손상 유형 | 익스플로잇 관점 |
|---|---|
NULL 역참조 / DoS | 대개 가용성(DoS)에 그침 |
스택/힙 OOB write | 제어 흐름 장악 가능성 높음 → RCE 후보 |
Use-After-Free / type confusion | 힙 그루밍으로 무기화 가능 |
4) 정적·테인트 분석 — 소스에서 싱크까지
퍼징이 “입력을 던져 보는” 동적 접근이라면, 정적/테인트 분석은 외부 입력(source)이 위험 함수(sink)에 검증 없이 도달하는 경로를 코드에서 직접 추적합니다. 대규모 코드베이스에서 인가 우회·인젝션·메모리 결함의 후보 경로를 빠르게 좁히는 데 효과적입니다.
소스: 네트워크/파일/IPC 입력 지점
싱크:
memcpy,system, 역직렬화, 쿼리 빌더 등CodeQL·Semgrep 같은 도구로 데이터플로 쿼리를 작성해 후보 자동 추출
5) 익스플로잇 가능성 판단과 책임 있는 공개
버그가 “취약점”이 되려면 영향(impact)과 제어 가능성(controllability)이 증명되어야 합니다. 최신 완화책(ASLR, DEP/NX, CFI, 스택 카나리)을 고려해 현실적 익스플로잇 난이도를 평가하고, 무기화 가능성이 확인되면 벤더와 협의해 책임 있는 공개(responsible disclosure) 절차로 CVE를 채번합니다. 고객 시스템에서 발견된 취약점은 비공개로 다룹니다.
6) 크래시 최소화·중복 제거·자동 트리아지
퍼저는 같은 결함에 대해 수십~수백 개의 크래시를 쏟아냅니다. 분석 가능한 형태로 만들려면 입력을 최소 재현 케이스로 줄이고(test-case minimization), 스택 해시로 중복을 묶은 뒤, 익스플로잇 가능성이 높은 순으로 정렬해야 합니다.
# 1) 입력 최소화 (libFuzzer)
./fuzz -minimize_crash=1 -runs=100000 crash-abcd
# 2) 스택 해시로 중복 제거
for c in crash-*; do
./fuzz -runs=1 "$c" 2>&1 | grep -m1 "#0" | md5sum
done | sort | uniq -c
# 3) ASAN 리포트에서 손상 유형 분류 (heap-buffer-overflow / use-after-free ...)이 과정을 CI에 연결해 매 빌드마다 코퍼스를 누적·재실행하면, 퍼징은 1회성 이벤트가 아니라 지속적으로 신규 취약점을 잡아내는 자산이 됩니다.
7) 현대 완화책과 우회 동향
취약점이 “있다”와 “악용된다” 사이에는 ASLR, DEP/NX, 스택 카나리, CFI/CET, 그리고 메모리 안전 언어(Rust 등)라는 방어선이 있습니다. 현실적인 익스플로잇 평가는 이 완화책들을 전제로, 정보 노출(infoleak)로 ASLR을 깨고 임의 쓰기로 제어 흐름을 장악하는 식의 “원시 능력(primitive) 조합”이 가능한지를 따집니다.
정보 노출(infoleak)로 베이스 주소 획득 → ASLR 무력화
힙 그루밍으로 UAF/오버플로를 제어 가능한 객체에 정렬
CFI 환경에선 데이터 지향 공격(DOP)·기존 호출 게이트 활용 검토
왜 이 역량이 모의해킹의 결과를 바꾸는가
신규 취약점을 직접 발굴해 본 팀은, 스캐너가 “정상”이라고 표시한 지점에서 미지의 결함과 체이닝 가능성을 봅니다. 패치 디핑으로 N-day를 며칠 만에 무기화하고, 퍼징으로 미지의 0-day를 잡아내며, 정적분석으로 대규모 코드의 위험 경로를 좁히는 역량은 그대로 고객 환경 점검의 깊이로 이어집니다. GOTROOT가 다수의 CVE를 등록해 온 이유이자, “스캐너 보고서”와 “실전형 모의해킹”을 가르는 본질적 차이입니다.
자주 묻는 질문
0-day 발굴과 모의해킹은 다른 일 아닌가요?
연결됩니다. 발굴에서 쌓인 루트코즈·익스플로잇 직관이 고객 환경의 미지 취약점과 체이닝을 더 깊게 찾아냅니다.
발견한 취약점은 공개하나요?
벤더 협의 후 책임 있는 공개 원칙을 따르며, 고객 시스템 취약점은 비공개입니다.
결론
취약점을 직접 “만들어 본” 팀에게 점검을 맡기면 결과의 깊이가 달라집니다. 모의해킹 상담 또는 레드팀 상담으로 GOTROOT의 연구 기반 검증을 경험해 보세요.