Obfuscation, assembly, and recovering program semantics
Here are two listings of the same function. One is four times longer than the other, and they compute the same value. Here are two more: one is smaller and more readable than the other, and it computes the wrong answer. Telling those two situations apart is the entire subject.
Over twelve weeks this course follows one 483-line C program through obfuscation, compilation, disassembly, graph recovery, symbolic execution, binary rewriting and lifting — and asks, at every step, what survived and how you would know. The evidence is real: sixty-eight builds, twenty-one retained executables, frozen solver queries, and two results that look like successes and are not.
The question, for twelve weeks: How much program meaning survives a change of representation, and which deobfuscation methods generalise beyond one implementation?
New to reverse engineering?
You do not need prior reverse-engineering experience for this course. You should already be comfortable with C, hexadecimal numbers, registers, memory and branches.
Week 1 teaches the remaining representations from the beginning, and the glossary carries every term with the qualification that matters later.
The thing you will keep meeting
An off-the-shelf unflattener — a tool that undoes one particular trick, where code is rearranged until no block any longer says which block runs next — was pointed at a function that had been through it. The tool exited zero, wrote a valid executable, and took the function's control-flow graph from 56 blocks down to 10: the largest simplification anywhere in this corpus. (Both figures under the same Miasm counting convention, which matters more than it sounds and is week 9's problem.)
what the smaller, cleaner, valid executable actually prints
expected DBEFFCE7SUB = 08080000
BCF = 6665BBBC
FLA = F9CD8332
CALLS = 12345F05
DATA = 57977293
COMBINED = EEBB6D71
SHOWCASE FAIL: 1 mismatchesFour legitimate switch alternatives from the original C were grouped together and emitted one after another, so all four now run, every time. Nothing about the graph says so. Nothing about the file says so. One test vector says so immediately.
What you will be able to do
By week 12 you should be able to take an unfamiliar obfuscated function, choose an approach and say why, work across more than one representation, validate whatever you conclude at the level you claimed it, and state the boundary of your own result without being asked. Concretely, you will be able to explain:
- why source, IR and machine code can tell different stories about one function;
- how compiler optimisation can weaken, erase or preserve an obfuscation;
- how mixed Boolean-arithmetic is simplified, and what a simplification proves;
- what makes an opaque predicate different from an ordinary path-dependent branch;
- how symbolic execution and an SMT solver prove a branch direction impossible;
- how such a proof becomes six changed bytes in a working executable;
- why control-flow flattening hides successor relationships rather than values;
- why an unflattener succeeds on one implementation and fails on its close relative;
- why valid-looking deobfuscation output still has to be validated semantically;
- and where generic methods succeed, against where a representation-specific assumption is still doing the work.
- 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.
The argument, in four blocks
Representation
One computation has several truthful descriptions, and obfuscation is a change of description before it is anything else.
Opens with Same program, four stories.
Simplification
Something usually simplifies the obfuscation for you — the algebra, the compiler backend, or your own reading — and each of the three proves a different thing.
- 04MBA and semantic simplification
- 05Optimization versus obfuscation
- 06Bogus control flow and opaque predicates
Opens with Something always simplifies it for you.
Proof
A solver can prove a branch impossible and that proof can be turned into changed bytes; neither step covers what flattening hides.
Opens with What a proof is worth.
Limits
The methods that generalise are the ones that carry their assumptions with them, and every output still needs validating at the level of its claim.
- 10Unflattening and hidden assumptions
- 11Lifting machine code back to LLVM IR
- 12How generic can deobfuscation be?
Opens with The smaller graph problem.
What it is built on
- build conditions from one frozen source that linked, ran and passed their own checks
- 68 / 68build conditions from one frozen source that linked, ran and passed their own checksshowcase_matrix_v1.md
- injected opaque gates proved one-sided across two different obfuscator implementations
- 38injected opaque gates proved one-sided across two different obfuscator implementationsfamily_results.json
- differential test triples on the rewritten binaries, with zero mismatches
- 9,189differential test triples on the rewritten binaries, with zero mismatchesvalidation.json
- of 4,118 inputs miscomputed by a lift whose LLVM IR verifies cleanly
- 3,917of 4,118 inputs miscomputed by a lift whose LLVM IR verifies cleanlysemantic_result.json
Where to go next
The twelve weeks
The teaching programme, in four blocks, with the claim each block establishes.
Assessment
Three pieces, 25 / 30 / 45, no exam. A quarter of each is what you decline to conclude.
Evidence and provenance
Every artefact the course cites, byte-identical, with its hash — and the two images we will not publish.
Four spine lectures
Weeks 1, 4, 7 and 10, each with the deck it was delivered from.
Tools
What each tool produces, and whether it ever produced a working rewritten binary here.
Course information
Prerequisites, the lab rules, the read-only route, and the assessment summary.