Skip to main content
Two columns of hexadecimal machine-code bytes in gold on near-black. The left column is a short regular sequence; the right is three times longer and irregular. One six-byte row is highlighted in both.

SLOP8445: Seeing Through Obfuscated Code

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 DBEFFCE7
SUB      = 08080000
BCF      = 6665BBBC
FLA      = F9CD8332
CALLS    = 12345F05
DATA     = 57977293
COMBINED = EEBB6D71
SHOWCASE FAIL: 1 mismatches
7 linesopen output_runtime_stdout.txt(155 B)

Four 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.

Week 10 takes it apart.

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:

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

Block I

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.

Block II

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.

Opens with Something always simplifies it for you.

Block III

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.

Block IV

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.

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

Enlarged evidence

Open the file