What a proof is worth#
SLOP8445 · Spine lecture 3 · Block III
An injected gate#
140001640: mov eax, dword ptr [rip + 0x879d6]
140001646: mov ecx, dword ptr [rip + 0x879cc]
...
140001667: jne 0x140001672Two writable globals. A predicate over them. A conditional jump.
Nothing about this says which way it goes.
The obligation, written down#
Path prefix P, branch condition C:
P ∧ C is this direction reachable?
P ∧ ¬C is the other one?Two queries. Three possible answers each: SAT, UNSAT, UNKNOWN.
What the solver returned#
"branch_rva": "0x1667",
"queries": {
"true": { "status": "SAT",
"model": { "g_140089018_4": 0,
"g_14008901c_4": 0 } },
"false": { "status": "UNSAT", "model": {} }
}One direction has a model. The other has none.
Read the qualifier twice#
Under path prefix
P, UNSAT forP ∧ ¬Crules out that direction for states covered byPand by the model.
Not: “this branch is opaque.”
38 gates. Two different obfuscator implementations. One generic harness.#
The transfer result#
| Family | Injected gates | Proved | Genuine branches |
|---|---|---|---|
| implementation A | 22 | 22 | 4, all two-sided |
| implementation B | 16 | 16 | 4, all two-sided |
No pattern for either implementation was written down.
The genuine branches staying two-sided is the control.
Same technique, different assumption#
| Entry state | Gates proved |
|---|---|
| arbitrary symbolic globals | 0 / 3 |
| one captured initialised state | 3 / 3 |
Same solver. Same branches. Same day.
From proof to bytes#
before 0F 85 05 00 00 00 jne 0x140001672
after E9 06 00 00 00 90 jmp 0x140001672 ; nopSix bytes. Four of them differ. The destination is unchanged — a shorter opcode needs a longer displacement.
22 gates × 4 differing bytes = 88 bytes changed in a 571,904-byte file.
What the rewrite did and did not do#
- reachable blocks: 70 → 37
- physical blocks: 70
- 33 original blocks now unreachable: 11 clones, 22 stubs
- predicate arithmetic: still there
- headers, sections, exports, unwind ranges: identical
Not a clean binary. A working partial deobfuscation.
And then it was tested#
- 4,591 clean / original / rewrite triples
- 0 mismatches
- canonical run:
SHOWCASE PASS, exit 0
Plus 4,598 more on the second implementation.
Four achievements, kept apart#
- a solver returned UNSAT, under a stated prefix and model
- separate code turned that into changed bytes
- the file still loads and exports the same symbols
- the program answered correctly on the inputs that were run
1 does not imply 4.
Block III#
- Week 7 — the solver, and what a model is a model of
- Week 8 — six bytes, and the audit that reconciles 70 with 37
- Week 9 — flattening: hiding the successor, not the value
A2 is due at the end of week 9.
A proof is worth exactly its assumptions#
Write them down or you have not got one.