fold/docs/internal/ctf-detector-gaps.md
trvon 95a5ddb462
audit: CTF binary detector gap analysis — 5 gaps, projected 7%→70%
- #1 fortified import _chk strip (+4 CWE-121, S effort)
- #2 binary format-string xref (+3 CWE-134, S effort)
- #3 hardcoded-string re-enable (+2 CWE-798, S effort)
- #4 BB encoder per-instruction granularity (+6 CWE-369/190/476, M)
- #5 binary UAF observer (+2 CWE-416, M)
2026-05-29 22:54:17 -04:00

2.7 KiB

CTF Detector Gap Analysis — Binary Mode

Date: 2026-05-29 | Source: Task #158 | Baseline: 7.4% detection (2/27 ground-truth)

Summary

Metric Value
CTF binaries evaluated 10
Ground-truth vulnerabilities 27 across 7 CWE categories
Current binary findings (real) 2 (gets(), system())
Current false positives ~0 (post-FP fix, over-suppression)
Source-mode baseline 66.7% (18/27)
Total gap 25 undetected vulns across 6 CWEs

Per-CWE Breakdown

Gap #1: CWE-121 — Fortified Import Normalization (S effort, +4 vulns)

Root cause: Mach-O binaries built with -D_FORTIFY_SOURCE import ___strcpy_chk instead of strcpy. normalizedSymbolName() strips _ prefix but not _chk suffix. Fix: strip _chk suffix (~5 lines in r2_backend.cpp:315).

Gap #2: CWE-134 — Binary Format String (S effort, +3 vulns)

Root cause: Binary mode cannot distinguish printf("Hello") (safe) from printf(user_input) (vulnerable). Source mode has full AST-based detection. Fix: use r2 axtj xref to find call sites, then disassemble to check if format arg is constant .rodata string or variable register (~50 lines).

Gap #3: Hardcoded-String Re-Enable (S effort, +2 vulns)

Root cause: Post-Loop-1 FP fix was over-aggressive — killed all credentials. Fix: add bare-secret patterns (supersecret, adminpass), add sk- prefix API key pattern, add build-artifact path filter (~30 lines).

Gap #4: Binary BB Encoder Granularity (M effort, +6 vulns)

Root cause: binary_bb_encoder uses abstract_blocks = true which produces unconstrained Z3 variables. Needs per-instruction pdbj parsing to fire per-instruction hooks (sdiv→div-by-zero, ldr/str→null-deref, add/sub/mul→int-overflow). Pipeline IS wired in scan_runner.cpp:3279, observer logic IS correct — encoder needs per-instruction granularity (~100 lines).

Gap #5: Binary UAF Observer (M effort, +2 vulns)

Root cause: BinaryBBObserver has onPointerDerefIr but doesn't track whether ptr was freed. Fix: add std::unordered_set<unsigned> freed_regs_, track on free() calls, check on derefs (~80 lines).


Detection Ranked by Gain-per-Effort

Rank Gap Effort CWEs Closed +Vulns Cumulative
#1 Fortified import _chk strip S CWE-121 +4 6/27 (22%)
#2 Binary format-string xref S CWE-134 +3 9/27 (33%)
#3 Hardcoded-string re-enable S CWE-798 +2 11/27 (41%)
#4 BB encoder per-instruction M CWE-369,190,476 +6 17/27 (63%)
#5 Binary UAF observer M CWE-416 +2 19/27 (70%)

3 remaining FPs require interprocedural binary analysis or runtime fuzzing.