How 0-days Are Found: Patch Diffing, Coverage-Guided Fuzzing, and Static Analysis in Practice
Behind “we found a zero-day” lies a long, iterative process: patch diffing, harness design, crash triage, root-cause analysis, and exploitability assessment. This is the workflow GOTROOT’s research team uses to discover and register high-impact CVEs (RCE, buffer overflow), stripped of marketing language. One question drives it: can we find the flaw an attacker doesn’t know yet, before they do?
Definitions: 0-day, N-day, and the patch gap

0-day — unknown to vendor and public; no signature exists, hardest to defend.
N-day (1-day) — disclosed/patched but unapplied; attackers reuse public PoCs during the “patch gap.”
Real-world risk comes more from the ubiquity of N-days than the rarity of 0-days — but 0-day skill sharpens N-day intuition.
1) Patch diffing — reading a fresh patch in reverse
When a vendor ships a security patch, the diff between pre/post binaries (or source) is a map of what was vulnerable. Binary diffing isolates changed functions so an exploit path can be reconstructed before a public PoC exists.
Binary compare:
BinDiff,Diaphorato spot changed functionsAdded bounds/sign/integer-overflow checks are the key tells
For source, follow the security commit diff and message ("fix bounds check")
A patch is the answer key. Reading it backwards is your N-day response speed.
2) Coverage-guided fuzzing — driving crashes with unknown input
Many 0-days come from inputs nobody expected. Coverage-guided fuzzers (AFL++, libFuzzer) evolve inputs that reach new code paths, surfacing memory corruption in parsers, decoders, and protocol handlers. The craft is in the harness that calls the target directly.
// 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 fuzzSeed corpus: start from valid samples to reach deep paths fast
Sanitizers: build with ASAN/UBSAN/MSAN to turn silent corruption into crashes
Coverage: if stuck, move to dictionary/grammar-aware fuzzing
3) Crash triage and root-cause analysis
Hundreds of crashes are mostly duplicates or unexploitable NULL derefs. Triage minimizes each crash, classifies the corruption type (OOB write, UAF, type confusion), and traces whether it reaches attacker-controllable memory.
Corruption type | Exploitation view |
|---|---|
NULL deref / DoS | Usually availability (DoS) only |
Stack/heap OOB write | High control-flow potential → RCE candidate |
Use-after-free / type confusion | Weaponizable via heap grooming |
4) Static and taint analysis — source to sink
Where fuzzing is dynamic, static/taint analysis traces, in code, how external input (source) reaches a dangerous function (sink) without validation — efficient for narrowing injection, authz-bypass, and memory-flaw candidates in large codebases.
Sources: network/file/IPC input points
Sinks:
memcpy,system, deserialization, query buildersWrite dataflow queries in CodeQL/Semgrep to auto-extract candidates
5) Exploitability assessment and responsible disclosure
A bug becomes a vulnerability only when impact and controllability are proven. We assess realistic difficulty against modern mitigations (ASLR, DEP/NX, CFI, stack canaries), and once weaponizability is confirmed, coordinate responsible disclosure and CVE assignment with the vendor. Client findings stay confidential.
6) Minimization, dedup, and automated triage
A fuzzer produces dozens to hundreds of crashes for the same bug. To make them analyzable we minimize each input, deduplicate by stack hash, and rank by likely exploitability.
# 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 ...)Wired into CI with a growing corpus, fuzzing becomes a continuous asset that keeps surfacing new bugs rather than a one-off event.
7) Modern mitigations and bypass trends
Between “a bug exists” and “it is exploited” stand ASLR, DEP/NX, stack canaries, CFI/CET, and memory-safe languages. Realistic assessment asks whether primitives can be chained — e.g., an infoleak to defeat ASLR plus an arbitrary write to seize control flow.
Infoleak to recover base addresses → defeat ASLR
Heap grooming to align UAF/overflow with controllable objects
Under CFI, consider data-oriented programming and existing call gates
Why this changes pentest outcomes
A team that finds its own 0-days sees unknown flaws and chaining where scanners report “clean.” Weaponizing N-days in days via patch diffing, catching unknown 0-days via fuzzing, and narrowing risky paths in large code via static analysis all translate directly into depth on client engagements. That is why GOTROOT has registered multiple CVEs — and the essential gap between a scanner report and real penetration testing.
FAQ
Isn’t 0-day research separate from pentesting?
They connect — discovery builds the root-cause and exploitation intuition that finds deeper, chained flaws in client environments.
Do you publish findings?
We follow responsible disclosure with vendors; client findings remain confidential.
Conclusion
A team that has built exploits delivers deeper results. Experience research-driven validation via penetration testing or red teaming.