Skip to main content

Week 12 · Block IV Limits

How generic can deobfuscation be?

The one week with no new evidence. What transferred between implementations, what did not, and the protocol for choosing a method when nobody tells you what was done to the binary.

Block IV · LimitsStudio 26 May 2027Shape: Method selection protocol

Given an unfamiliar obfuscated function, what would you do first, and what would you have to check?

No new specimens this week. Everything below has already appeared; the work is putting it in one place and drawing the conclusion it supports.

Everything, on one table

Table 07

From obfuscated code to a working rewrite

All eight inputs below are runnable originals; the output claims differ sharply.

CaseRecovered / demonstratedRewritten original PEQualification
OLLVM SUBSelected local expressionsNOT PRODUCEDmsynth recovery is not binary rewriting.
Polaris MBALocal recovery; passing Mergen liftNOT PRODUCEDSubstantial MBA remains in lifted IR.
OLLVM BCF O022 proved gates patchedPASSPredicates and dead clone bytes remain.
Polaris BCF O016 proved gates patchedPASSSame generic proof workflow transfers.
OLLVM FLA O025/25 successor pairsPASSGraph need not match clean exactly.
Hikari FLA O028/28 successor pairsPASSO0 success does not extend to O2 here.
Polaris FLA O014/22 true pairs + 1 false pairWRONG SEMANTICSLegitimate switch cases collapse.
Tigress Flatten O0Stock discovery incompleteNO OUTPUTFails before symbolic successor analysis.

A semantic result is not automatically a working executable, and a valid PE is not automatically correct.

PASS refers to retained validation. Successor-pair scores are not universal proofs of program equivalence.Open the original lecture figure (SVG)

Eight cases. All eight inputs were working programs. The output column is where they diverge, and it has four distinct values in it — produced, not produced, wrong, and nothing at all.

What transferred

Three things crossed an implementation boundary in this corpus.

The branch proof plus a separate patcher. A generic symbolic harness proved thirty-eight injected gates one-sided across OLLVM-16 and Polaris, while leaving sixteen genuine conditions two-sided, with no false positives. A separate program turned those proofs into changed bytes. 9,189 differential triples, zero mismatches.

Local arithmetic recovery. Ten of twelve isolated sites recovered across three different implementations, every accepted candidate carrying an independent 32-bit equivalence proof, and the two that could not be proved recorded as UNKNOWN rather than accepted.

Stock unflattening, within a lineage. From one LLVM-family implementation to a closely related one: 25/25 and 28/28 successor pairs, two working executables.

Look at what those three share. Each one carries an explicit model — of memory and path prefix, of bit width, of dispatcher and state layout — and each one has a separate validation step that could have said no. In the case of the third, on a fourth specimen, it did.

What did not

The pattern is uncomfortable and it is the course’s thesis: the methods that travelled were the ones that modelled semantics and validated separately. The methods that did not travel were the ones that recognised a shape.

Table 11

A seed is not always a reconstruction key

Archive what actually ran, not only the command that was meant to reproduce it.

Tested laneRepeat observationWhat to retain
OLLVM / Hikari SUBREPEATED IDENTICALLYSeeded IR in the retained probeExact flags, compiler identity, IR and PE hashes.
Polaris selected IR passesREPEATED IDENTICALLYSUB/BCF/FLA/indirection/gvenc smoke variantsExact artifacts; not a universal seed guarantee.
Polaris MBA / backendVARIEDSame requested RNG seed was insufficientThe exact generated artifact is the specimen.
TigressWHOLE FILES VARIEDIdentifier salts / ordering were not pinnedExact generated C, then reuse it across optimization levels.
BinProtectNO EXPOSED SEEDOutputs variedExact PE for static analysis; validity failed here.

Reproducibility has several levels: same source, same generated representation and same executable bytes.

Tigress target bodies were more stable than whole generated files; whole-file variation is not proof of semantic variation.Open the original lecture figure (SVG)

Four traps, with the specimen that sets each one off

Table 10

Four tempting conclusions to avoid

Keep visual appearance, representation validity and semantic correctness separate.

Tempting conclusionRetained counterexampleWhat the evidence says
Smaller CFG = correctPolaris FLA: 56 to 10 blocksWRONGF9CD8332 instead of DBEFFCE7; exit 1.
Valid IR = faithful liftMergen on valid OLLVM SUB inputWRONG3,917 / 4,118 mismatches in every lifted stage.
Six discovered blocks = six actual blocksTigress FlattenINCOMPLETEThe jump-table-aware reference has 38 blocks.
Matching PDB ID = valid instruction mapRewritten BCF / FLA PEsSTALE METADATAOld RSDS references survive. Use exports / RVAs.

Validate the output at the level of the claim: expression, CFG, lifted function or complete executable.

Original OLLVM SUB passes native checks. FLA whole-function rewriting moves internal instructions.Open the original lecture figure (SVG)

Two function graphs. The left is a wide state-dispatch network; the right is a compact graph with a loop and a few branches.

A working rewrite: the dispatcher network becomes a compact branching graph, and the retained execution checks pass.

analysis view — not correctness evidencedemo_flattening4_ollvm_to_rewritten_fla_1.jpg — open for full resolution
Two function graphs. The left is a wide state-dispatch network; the right is a small graph dominated by one long straight-line region.

An incorrect rewrite: the dispatcher network becomes an even smaller graph, and the program returns the wrong value.

analysis view — not correctness evidencedemo_flattening5_polaris_to_rewritten_1.jpg — open for full resolution

Those two images are the final exam of the course, in the sense that matters. Neither one, on its own, tells you which is which. You need the runtime record, or the successor score, or the region map. The picture is not the evidence.

A protocol, not a checklist

Given a target function and no information about what was done to it, the order below is what this corpus supports. It is a protocol because each step names what it assumes, and you get to say so out loud.

  1. Get a matched comparator. Same compiler family, same optimisation level. Without it, every count you take is unanchored. Most of the bad claims in this field die here.
  2. Decide what is being hidden. Expression complexity, branch outcome, or successor relation? Those need different methods and produce different outputs. Look at block count against expression size: a one-block function with 678 instructions is not the same problem as a 60-block dispatcher.
  3. Establish ground truth where you can. A clean build, a source file, a canonical output, a test vector. If ground truth exists, use it, and say that you had it — a result checked against known answers is worth more than one that could not be.
  4. Choose the method by the representation, not the vendor. Expression recovery for arithmetic, symbolic branch proof for gates, successor recovery for dispatchers, lifting when you want the whole function in one representation. Naming the obfuscator buys nothing; in this corpus one filename was wrong about which binary it even showed.
  5. Write the assumption down before you run anything. Which values are symbolic. What the memory model covers. What bit width. Which path prefix. If you cannot write it, you will not be able to report the result.
  6. Validate at the level of the claim. An expression claim needs an equivalence proof at the right width. A CFG claim needs a convention and a comparator in the same convention. A lifted-function claim needs test vectors. An executable claim needs the executable run.
  7. Write down what you could not establish. Not as a disclaimer. As the part of the result that tells the next person where to start.
Analysis success
A tool ran, produced output, and did not error.
Semantic proof
A property holds — under a stated model, state and path prefix.
Binary rewriting
Bytes changed and a file still loads.
Runtime correctness
The program answered correctly, on the inputs that were actually run.

Two places this course stops

The lane that never ran. One post-link tool applied four transformations to two inputs and returned exit code 0 for all eight invocations. Seven of the eight protected executables terminate with an access violation and the eighth hangs. Its transformations are visible and real; its outputs support static observations and nothing else. There is no end-to-end deobfuscation lane there because there was never a working protected program to deobfuscate.

Virtualization. Translating protected behaviour into custom bytecode run by an embedded interpreter is where a lot of commercial protection actually lives, and this corpus contains no virtualization or devirtualization experiment. It was intentionally not tested. So the course can tell you what the problem is and cannot tell you anything measured about it, and week 12 is not going to pretend otherwise by reasoning from the results above. If you want to work on it, that is a research question with an empty results section, which is the best kind.

post-link protected outputs valid at runtime — seven crashes, one hang
0 / 8post-link protected outputs valid at runtime — seven crashes, one hangbinprotect_validation.md
virtualization experiments in this corpus
0virtualization experiments in this corpusEXPERIMENTAL_CORPUS.md
screenshots withheld because their contents contradict their filenames
2screenshots withheld because their contents contradict their filenamesVISUAL_INDEX.md
483-line program behind every result on this site
1483-line program behind every result on this siteSHOWCASE_VERSION.txt

That last number is the honest boundary of the whole course. Everything here is about one small C program, compiled sixty-eight ways. The reasoning generalises. The measurements do not, and a page that blurred those two would have failed its own standard.

Studio: your capstone protocol, out loud

Bring your sample and whatever you have. Twenty minutes each, in pairs, then a round of the whole room.

  1. State what you think is being hidden in your function, and the evidence for that. One minute.
  2. Name the two representations you will work in and why those two.
  3. For each method you plan to use, state the assumption it depends on. Your partner’s job is to find the assumption you left out.
  4. For each conclusion you expect to reach, name the level you will validate it at and how.
  5. Read your limitations section aloud. This is the part people skip and the part markers read first, and hearing your own draft is the fastest way to notice that it says nothing.

Then one question to the room, which is the question the course has been building towards for twelve weeks: what would make you abandon your current conclusion? If there is no answer, there is no conclusion yet.

Come to the studio with

The studio assumes you have done these. Each is written so you can tell whether you have, without asking.

Enlarged evidence

Open the file