Skip to main content

Week 02 · Block I Representation

What is obfuscation?

Five tools, one program, and a definition strong enough to be useful — which means a definition that tells you what evidence would show whether anything happened.

Block I · RepresentationStudio 3 March 2027Shape: Lane audit

An obfuscator ran and exited zero. What has it actually done, and what would show that?

Week 1 gave you four ways to describe one function. This week is about the people who change one of them on purpose.

A working definition, and then five hours of trouble with it.

Obfuscation is a transformation applied to a program that is intended to preserve its observable behaviour while making some specific analysis of it more expensive.

Everything interesting is in the two vague words. Which analysis, and preserve according to whom. This week is about turning both into questions with answers you can check.

Which analysis, and at which representation

An obfuscator does not make a program “harder”. It makes one particular way of looking at the program harder, and it does that at one particular point in the pipeline. Week 1 gave you the four points. Here is what each choice commits the tool to:

Table 01

Where does obfuscation happen?

The transformation stage determines which optimizations come afterwards.

LayerExamplesWhat changes?What follows?
C sourceTigressExpressions and control flow in generated CThe native compiler can reshape the result.
LLVM IROLLVM / Hikari / PolarisIR operations, blocks and edgesLLVM optimization and instruction selection.
X86 backendPolaris backend-obfuMachine instructions after IRNative changes may be invisible in IR metrics.
Linked binaryBinProtectInstructions in an existing PENo later compiler optimization in these tests.

Compare the final machine code, not just the representation where obfuscation was added.

Polaris has separate IR and backend paths. BinProtect outputs were semantically invalid in the retained tests.Open the original lecture figure (SVG)

The consequence is the reason week 5 exists. A transformation applied to C source has to get past the entire native compiler. One applied to LLVM IR has to get past instruction selection. One applied to a linked binary has nothing after it — no optimiser will undo it, and no optimiser will fix it either.

The five lanes this course uses

Four compilers and one post-link rewriter were tested. Three words first, because the evidence below is full of them.

Their exact identities are recorded too, because “OLLVM” is a family of implementations spanning a decade and results do not transfer between them for free:

compiler_inventory.json — the toolchains behind every number on this site

identity, not marketing
  "polaris": {
    "path": "E:\\Workspace\\seeing_through_obfuscation\\builds\\polaris\\bin\\clang.exe",
    "version": "clang version 16.0.6\nTarget: x86_64-pc-windows-msvc\nThread model: posix\nInstalledDir: E:\\Workspace\\seeing_through_obfuscation\\builds\\polaris\\bin",
    "sha256": "88DC08535F71EFD84B3BFACE00080CF5BCA56DC47ADF6DD50FC4293568E50D10",
    "identity": "Polaris LLVM16 / Clang 16.0.6"
  },
lines 8–13 of 26open compiler_inventory.json(2 kB)
Table 02

Five obfuscators, five useful lessons

A compact comparison of the tested implementations, not a universal ranking.

ObfuscatorBest teaching contrastOriginal PE statusMain qualification
OLLVM-16Classic SUB; working BCF and FLA rewritesPASSSelected originalsGlobal pass selection in the retained matrix.
HikariPartial SUB coverage; FLA transferPASSSelected originalsSUB/BCF target effects disappear by O1.
PolarisStrong MBA; BCF transfer; wrong FLA rewritePASSSelected originalsOriginal PASS does not validate a later rewrite.
TigressSource-level effects survive native optimizationPASSMatched environmentPreserve exact generated C and initialization.
BinProtectPost-link arithmetic and state dispatchINVALID0 / 8 valid outputsCrash/hang: static observations only.

An obfuscator's original output and a deobfuscator's rewritten output need separate validation.

PASS means the retained checks passed. It is not a guarantee for arbitrary programs or inputs.Open the original lecture figure (SVG)

Sixty-eight planned build conditions were produced from the frozen source. All sixty-eight linked as Windows x64 executables, ran, printed the six expected values and exited zero. That matters for a reason that is easy to skip past: it means every specimen you will read this semester is a program that worked. When something goes wrong later in the course, the obfuscator is not the suspect.

The lane that does not run

This is the first of several places where the course’s habit of separating outcomes earns its keep. “The tool exited zero and its transformations are plainly visible in the output” and “the tool produced a working protected program” are different claims, and here the first is true and the second is false eight times out of eight.

It is also a warning about reading a tool’s own description of itself. Upstream capability descriptions do not override a local runtime failure, and a local runtime failure on one program does not prove the tool never works. Both halves of that are needed.

Targeting: what the tool was told to transform

You would like to say “the obfuscator transformed this function”. For two of the four compilers tested, that turned out not to be available.

Annotation-only pilots were run first: mark one function with __attribute__((annotate("sub"))), build, and check that only it changed. On the tested OLLVM-16 and Hikari builds, the annotated target compiled and was not transformed. In OLLVM-16’s case its annotation reader was written against an older way of spelling pointers in LLVM IR, and did not recognise what this front end emits. The retained matrix therefore uses global switches — -mllvm -sub, -enable-subobf and their siblings — which transform everything eligible in the module.

Polaris’s selective targeting did work, with suffix annotations paired to its pass pipelines.

The measurement consequence is precise and small. Target-function measurements stay isolated, because they are taken from the named function’s own bytes. But other functions in the same binary may also have been transformed, so nothing here supports a claim about function-isolated treatment, and nothing here proves annotation support is impossible in some other build of these compilers.

planned build conditions that linked, ran and printed SHOWCASE PASS
68 / 68planned build conditions that linked, ran and printed SHOWCASE PASSshowcase_matrix_v1.md
BinProtect protected outputs valid at runtime
0 / 8BinProtect protected outputs valid at runtimebinprotect_validation.md
compilers with recorded hash and revision behind every claim
4compilers with recorded hash and revision behind every claimcompiler_inventory.json
compilers where annotation-only targeting silently did nothing
2 of 4compilers where annotation-only targeting silently did nothingshowcase_matrix_v1.md

What “more complicated” looks like

Two function graphs side by side in a disassembler, each a single tall basic block. The right-hand block is several times taller than the left. Individual instructions are not legible at this zoom.

The same function before and after one arithmetic transformation, at a zoom where no instruction is readable. Both are a single basic block — the control flow is identical and all the growth is inside one straight line of code. Nothing here ranks one tool against another, and nothing here is a measurement: the retained instruction counts come from the disassembly, not from the height of a rectangle.

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

Keep this image in mind for two reasons. It is the honest reason obfuscation works — someone has to read that — and it is a trap, because block count and expression size are independent, and in week 6 you will meet a transformation that explodes the graph while barely touching the arithmetic.

Studio: audit the five lanes

In pairs, build a five-row table of your own. One row per tool, and these columns: the representation it transforms, what it takes as input, what it emits, and what the retained evidence establishes about its output’s validity.

Then answer two questions in writing:

  1. Which of the five lanes cannot be used for a runnable exercise in this course, and what exactly is the evidence for that? Quote the number.
  2. Pick any row and write the sentence that would be an overclaim. Then write the version that is supported. This is the skill A1 marks.

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