‘제로데이를 발견했다’는 한 문장은 화려하지만, 그 뒤에는 패치 분석부터 퍼징, 변종 추적, 크래시 선별과 근본 원인 분석, 그리고 실제 악용 가능성 판단까지 길고 반복적인 과정이 자리합니다. 규모를 가늠해 보면, 구글 위협 인텔리전스 그룹(GTIG)은 2024년 한 해에만 실제 공격에 사용된 제로데이 75건을 집계했습니다(2023년 98건, 2022년 63건). 전체 건수는 줄었지만, 표적은 점차 기업용 보안 솔루션과 네트워크 장비 쪽으로 옮겨가고 있습니다.
이 글은 GOTROOT 연구팀이 영향도가 높은 취약점(원격 코드 실행, 메모리 손상 등)을 직접 발굴해 CVE로 등록하기까지 거치는 과정을, 공개된 연구와 표준에 근거해 정리한 것입니다. 모든 작업의 출발점은 하나의 질문입니다. 공격자가 아직 알지 못하는 결함을, 공격자보다 먼저 찾아낼 수 있는가?
먼저 용어부터: 0-day, N-day, 그리고 패치 공백
0-day — 벤더도, 공개된 패치도 아직 존재하지 않는 미지의 취약점입니다. 탐지 규칙이 없어 방어가 가장 까다롭습니다.
N-day(또는 1-day) — 이미 공개·패치됐지만 운영 환경에는 아직 적용되지 않은 취약점입니다. 패치가 나온 뒤 실제 적용까지의 공백 기간 동안, 공격자는 공개된 PoC를 그대로 사용할 수 있습니다.
한 가지 짚어둘 점이 있습니다. 2024년의 regreSSHion(CVE-2024-6387)은 사실 2006년에 한 번 수정됐던 결함(CVE-2006-5051)이 다시 되살아난 사례였습니다. 이미 고쳐진 줄 알았던 결함의 변형이 새로운 제로데이로 돌아오는 일은 생각보다 드물지 않습니다.
큰 그림: 결함의 다수는 메모리 안전에서 비롯된다
발굴의 우선순위를 정하려면 통계를 봐야 합니다. 업계의 오랜 데이터는 한 방향을 가리킵니다. 심각도가 높은 취약점의 상당수가 메모리 안전 문제에서 비롯된다는 사실입니다.
출처 | 핵심 수치 |
|---|---|
크로미움 | 고위험·치명 결함의 약 70%가 메모리 안전 문제 (2015년 이후 912건 분석) |
마이크로소프트 | 지난 12년간 보안 패치의 약 70%가 메모리 안전 결함 |
안드로이드(구글) | Rust 도입으로 메모리 안전 취약점 비중 76%(2019) → 20% 미만(2025) |
GTIG(2024) | 실제 악용 제로데이 75건, 주요 유형: 해제 후 사용·명령 주입·XSS |
그래서 제로데이 탐색은 파서, 디코더, 메모리 관리 코드처럼 손상이 일어나기 쉬운 지점에 집중됩니다. 동시에 Rust 같은 메모리 안전 언어로의 전환(미국 CISA도 강하게 권고)은 이 공격면 자체를 구조적으로 줄여가고 있습니다. 방어와 공격이 같은 데이터를 보고 움직이는 셈입니다.
1) 패치 분석 — 패치를 거꾸로 읽기
벤더가 보안 패치를 배포하면, 패치 전후의 차이가 곧 ‘무엇이 취약했는가’를 알려주는 지도가 됩니다. BinDiff나 Diaphora로 변경된 함수만 추려 분석하면, 공개 PoC가 나오기 전에 악용 경로를 재구성할 수 있습니다. 특히 길이·부호·정수 오버플로에 대한 검사가 새로 추가됐다면, 바로 그 지점이 취약점이었다는 뜻입니다.
이것이 왜 위험한지는 실제 사례가 잘 보여줍니다. Log4Shell(CVE-2021-44228)은 2013년부터 코드 안에 잠들어 있다가 뒤늦게 발견됐고, 수억 대의 기기에 영향을 줄 수 있었습니다. 패치가 공개되는 순간부터, 그것을 거꾸로 읽어내는 속도가 곧 방어자와 공격자의 시간 싸움이 됩니다.
2) 변종 분석 — 가장 과소평가된 기법
여기서부터가 연구의 깊이를 가릅니다. 구글 프로젝트 제로의 관찰에 따르면, 실제 공격에 쓰이는 제로데이의 상당수는 완전히 새로운 결함이 아니라 이미 패치된 결함의 변형입니다. 같은 실수가 코드베이스 곳곳에 반복돼 있는데, 퍼징은 이런 변형을 자주 놓칩니다. 그래서 결함 하나를 고쳤다고 끝이 아니라, ‘같은 패턴이 또 어디에 있는가’를 코드 전체에서 다시 찾아봐야 합니다.
# 코드베이스 전체에서 '같은 패턴'을 질의 (CodeQL 개념 예시)
from FunctionCall memcpy, Expr len
where memcpy.getTarget().getName() = "memcpy"
and not exists(BoundsCheck bc | bc.guards(len)) // 경계 검사 없음
select memcpy, "검증되지 않은 길이의 memcpy — 변종 후보"
프로젝트 제로는 최근 이 변종 분석에 LLM 에이전트를 결합해 SQLite 같은 실제 코드베이스를 검토하는 실험까지 진행했습니다. 핵심은 도구가 아니라 관점입니다. ‘이 결함은 혼자가 아닐 것이다’라는 의심이죠.
3) 커버리지 기반 퍼징 — 예상 못 한 입력으로 결함을 끌어내기
제로데이의 상당수는 예상하지 못한 입력에서 나옵니다. 커버리지 기반 퍼저(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으로 빌드하면 메모리 오류를 즉시 검출
return 0;
}
// clang -g -O1 -fsanitize=fuzzer,address harness.c target.c -o fuzz
상시 퍼징: OSS-Fuzz는 수백 개 오픈소스 프로젝트를 쉬지 않고 퍼징하며 수만 건의 결함을 찾아왔습니다. 일회성 작업이 아니라 상시 공정으로 돌릴 때 진가가 드러납니다.
커널: 리눅스 커널은 syzkaller와 KASAN이 함께 끊임없이 결함을 찾아냅니다.
막혔을 때: 사전 기반, 문법 인지, 구조 인지 퍼징으로 더 깊은 경로에 도달합니다.
4) 정적·테인트·심볼릭 분석 — 입력에서 위험 지점까지
퍼징이 입력을 직접 던져보는 동적 분석이라면, 정적·테인트 분석은 외부 입력이 위험한 함수까지 검증 없이 도달하는 경로를 코드에서 직접 추적합니다. CodeQL이나 Semgrep으로 데이터 흐름을 질의하고, 조건이 복잡한 경우에는 심볼릭 실행(angr 등)으로 실제 도달 가능 여부를 따집니다. 대규모 코드베이스에서 의심 지점을 빠르게 좁히는 데 효과적입니다.
5) 크래시 선별과 근본 원인 분석
퍼저가 쏟아내는 수백 건의 크래시는 대부분 같은 결함의 중복이거나, 악용이 불가능한 단순 NULL 참조입니다. 가치 있는 결함을 가려내려면 입력을 최소 재현 형태로 줄이고, 스택 해시로 중복을 묶은 다음, 악용 가능성이 높은 순서로 정렬해야 합니다.
# 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-overflow / use-after-free 등)
손상 유형 | 악용 관점 |
|---|---|
NULL 참조 / 단순 다운 | 대개 서비스 거부(가용성)에 그침 |
스택·힙 경계 밖 쓰기 | 실행 흐름 장악 가능 → 원격 코드 실행 후보 |
해제 후 사용 / 타입 혼동 | 힙 정렬로 무기화 가능 (2024년 실제 악용 1위 유형) |
6) 악용 가능성과 보호 기술의 군비 경쟁
결함이 ‘취약점’이 되려면 실제 영향과 제어 가능성이 증명돼야 합니다. 현실의 익스플로잇은 여러 보호 기술을 전제로, 작은 공격 요소들을 차례로 엮어 완성됩니다.
메모리 주소가 한 번 노출되면 ASLR이 무력화되고, 여기에 임의 쓰기가 더해지면 실행 흐름을 장악할 수 있습니다.
DEP/NX를 우회하기 위한 ROP·JOP, 그리고 해제 후 사용 결함을 제어 가능한 객체에 맞춰 배치하는 힙 정렬(heap grooming)이 동원됩니다.
모바일·최신 환경에서는 CFI(안드로이드), 포인터 인증(PAC, iOS 12 이상), 메모리 태깅(MTE), 커널의 SMEP·SMAP·KPTI 같은 보호 기술이 더해집니다.
이런 보호 기술은 분명히 익스플로잇을 어렵게 만듭니다. 그러나 ‘불가능’과는 다릅니다. 2024년 연구진은 투기적 실행을 이용해 ARM의 메모리 태깅(MTE)을 우회하는 TikTag 기법을 발표하며, 실제 크롬과 리눅스 커널 환경에서 성공률을 거의 100%까지 끌어올릴 수 있음을 보였습니다. 방어가 한 걸음 나아가면 공격도 한 걸음 따라옵니다. 그래서 ‘현실적인 난이도’를 정확히 평가하는 일이 연구자의 몫입니다.
7) 새로운 전선 — 공급망과 신뢰의 취약점
2024년 가장 충격적인 사건은 메모리 결함이 아니라 XZ Utils 백도어(CVE-2024-3094)였습니다. 한 인물이 약 2년에 걸쳐 오픈소스 프로젝트에 기여하며 신뢰를 쌓아 메인테이너 권한을 얻은 뒤, 압축 라이브러리에 백도어를 심어 OpenSSH 원격 코드 실행을 노렸습니다. 거의 성공할 뻔한 이 공격은, 한 엔지니어가 SSH 로그인이 0.5초가량 느려진 것을 이상하게 여겨 파고든 끝에 드러났습니다.
교훈은 분명합니다. 취약점 발굴은 코드만이 아니라, 그 코드가 기대고 있는 구성 요소와 그것을 둘러싼 신뢰의 사슬까지 살펴야 한다는 것입니다. 저희가 모의해킹에서 강조하는 공급망 관점과 정확히 맞닿아 있습니다. (관련: 공급망·오픈소스 취약점 관리)
8) 책임 있는 공개 — 발굴 다음의 윤리
악용 가능성이 확인되면, 벤더와 협의해 패치 기간을 확보한 뒤 CVE를 등록하는 책임 있는 공개 절차를 따릅니다. 고객 시스템에서 발견한 취약점은 철저히 비공개로 다룹니다. 발굴 역량이 강할수록, 그 역량을 다루는 윤리와 절차가 더 중요해집니다.
이 역량이 모의해킹의 결과를 바꾸는 이유
신규 취약점을 직접 발굴해 본 팀은, 스캐너가 ‘이상 없음’이라 표시한 지점에서도 아직 드러나지 않은 결함과 연계 가능성을 봅니다. 패치 분석으로 N-day를 며칠 만에 재현하고, 변종 분석으로 같은 실수가 숨어 있는 다른 자리를 찾고, 퍼징으로 미지의 결함을 잡아내는 역량은 그대로 고객 환경을 점검하는 깊이로 이어집니다. 특히 고객이 기대고 있는 구성 요소와 라이브러리의 N-day까지 살피는 APT 관점 점검, 그리고 LLM 레드팀에서도 같은 사고방식이 작동합니다. GOTROOT가 여러 건의 CVE를 등록해 온 배경이자, 단순한 ‘스캐너 보고서’와 실전형 모의해킹을 가르는 본질적인 차이입니다.
현장에서 가장 자주 마주치는 사실은 ‘결함은 혼자 있지 않다’는 것입니다. 하나를 찾으면 비슷한 것이 그 근처에 또 있습니다. 그래서 좋은 연구는 한 건을 터뜨리는 일이라기보다, 같은 실수가 어디에 반복되는지 지도를 그리는 일에 가깝다고 느낍니다.
자주 묻는 질문
취약점 발굴과 모의해킹은 별개의 일 아닌가요?
이어집니다. 발굴에서 쌓인 근본 원인·변종·악용에 대한 직관이, 고객 환경의 미지 취약점과 연계 경로를 더 깊이 찾아냅니다.
발견한 취약점은 공개하나요?
벤더와 협의한 책임 있는 공개 원칙을 따르며, 고객 시스템의 취약점은 공개하지 않습니다.
Rust가 널리 쓰이면 제로데이 연구도 끝나나요?
메모리 손상은 줄어들지만, 로직·인가·공급망·설계상의 결함은 그대로 남습니다. 공격면이 이동할 뿐, 연구는 그 이동을 따라갑니다.
마치며
취약점을 직접 만들어 본 팀에게 점검을 맡기면 결과의 깊이가 달라집니다. 우리 서비스와 그 기반 구성 요소가 실제 공격자의 시선에서 어디까지 버틸 수 있는지 확인하고 싶으시면 모의해킹 상담 또는 레드팀 상담으로 편하게 연락 주세요.