fired-not-fed
AI-for-security benchmarks that score models on compiled code can report a number nobody can recompute. We took one apart: how well obfuscation resists AI decompilers.
An AI-for-security benchmark scores a model on compiled code and reports one success rate, and a rate is only as good as the population it was averaged over. Mix functions a transform changed together with functions it never touched, and the untouched ones carry their old rate into the total, unmarked.
The models here are code models: they read a disassembler's rendering of a compiled binary and write source back. Where the population behind the reported rate goes unpublished, the rate cannot be recomputed by anyone who was not in the room, and that is a measurement-validity problem wherever it occurs.
Ship software to a customer and they own the machine it runs on, so they can take it apart at leisure. Obfuscation is the defence: a compiler pass that leaves a program's behaviour alone while making its compiled form harder to read back. Buyers choose one on the strength of published resistance numbers, because those numbers are the only evidence anyone has.
The number is a drop. Run an AI decompiler over the clean binary, run it over the obfuscated binary, report how much less code came back. That is a reasonable thing to measure and the arithmetic behind it is sound. The problem is the denominator. A transform does not fire on every function, because some have no control flow worth flattening and no strings worth encrypting, and the functions it skipped stay easy and get averaged into the obfuscated result anyway. So the headline can move because the defence worked or because the transform covered less ground, and the published number cannot tell you which.
Three more effects hide in the same scalar. Functions longer than the model's context window are cut off and scored as failures, so a reading limit reads as resistance. Clean and obfuscated binaries are often built by different compilers, so part of the drop is a dialect the model never trained on. And the result depends on what the attacker had already seen: the same binaries that stop an attacker who has never met the defence are read straight through by one that has.
This series takes the number apart in that order — the binary, then the build environment, then the attacker — using a census of what the field reports and a controlled grid of our own. Each part shows that the previous measurement was conditioned on something nobody stated. Two of the results that dissolve along the way are ours. What the census measured is one subfield. The arithmetic under it is general: any eval that folds inputs a transform never touched into the same average, without reporting how often it fired, inherits this exact bug.
last substantively updated: July 30, 2026
- 01
- 02
The compiler did it, and my obfuscation took the credit
Two arms of the same 149 functions, neither arm obfuscated — stock LLM4Decompile v1.5, gcc-lenient scorer, native 4096-token window: GCC-built binaries recover at 27.5%, clang-built at 15.4%, paired McNemar p = 0.0029. The confound is the toolchain, and the law generalizes well past binary analysis. The three-term decomposition built to separate those effects then fails to add up, and the ~5.6-point residual is printed open. - 03
Bigger didn't break it. Knowing did.
Obfuscation dropped this decompiler's recovery from 18.8% of clean functions (28/149) to 0.0-0.9% of the functions each transform fired on (0/97, 0/63, 1/106) — gcc-lenient scorer, native 4096-token window, paired and placebo-controlled. Tripling its parameters bought no monotonic gain in the largest obfuscated cell: three points at n = 151, intervals overlapping. Fine-tuning it on the same transform family bought 12-16% back, and the headline moved from hardening to brittleness — against one modest 6.7B attacker, where every number is a floor.