Open an obfuscated signature SDK and you get a certain illusion: this thing is arcane, every line is computing something you can't follow.
Once you've run the whole reverse-engineering flow, the truth is usually the reverse — 90% of the code is standard algorithms. The hash it uses is a public hash, the encoding is a public encoding, the stream cipher is a public stream cipher. You don't need to "understand" those parts at all; recognize which algorithm it is, copy it from the RFC or a reference implementation, and not a single byte will be off.
The deadly part is the remaining 10%: the places where a standard algorithm has been quietly changed by a hair. A round count that should be a constant has become a variable; a base constant has been swapped for a neighboring value; the word indices in one round have been transposed; the key has been hidden inside the ciphertext. Each change is small, but miss even one and your rewrite won't match the target — and you won't know where the fault is, because the other 90% is correct.
So the real work of reversing an obfuscated signature isn't reading the 90% — it's locating that 10% of deviation points. This piece is about getting a large model to do that hunting for you, and why this is the step where the model is least replaceable.
(The prerequisite — algorithm-family identification, recognizing "this is ChaCha," "this is FNV" from the constants and structure — is covered in the previous piece. Here I'll assume you've already recognized the skeleton and go straight to deviation hunting.)
Why deviation points are exactly where humans look past
The human brain has a strong tendency when recognizing patterns: once it recognizes the gist, it stops looking closely.
You see a chunk of code, the constants match ChaCha's fingerprint, the structure looks right, and your brain ticks a box — "this is ChaCha20" — and moves on. It does not go byte by byte through every round count, every index, the low bit of every constant, because the satisfaction of "already recognized it" switches your attention off.
Obfuscation authors bank on precisely this. They don't rewrite the algorithm (too expensive, too bug-prone); they make the smallest possible change to a standard one — flip a number, swap an index, add a step. Those changes are small enough that your pattern recognition skips over them, but large enough to make a by-the-standard rewrite fail wholesale.
This is an asymmetric war of attention. The author only has to hide one deviation; you have to find all of them. And humans are congenitally bad at "staying suspicious of something that looks correct."
The model has a counterintuitive edge here: it has no "already recognized it" satisfaction to switch off. As long as your prompt explicitly asks it to hunt for deviations, it will check item by item without slacking off halfway — provided you use the right prompt, which is Section 5's business. First, what these four kinds of deviation look like.
Four kinds of deviation
For each kind below I'll use a public standard algorithm to show what "standard looks like," then describe what "the deviation looks like." In a real project these four routinely stack up inside the same signer.
1. Constant-to-variable: a parameter that should be fixed becomes runtime-derived
A standard stream cipher has a fixed round count. ChaCha20 is 20 rounds, immovable; the number is hardcoded in every standard implementation — RFC 8439 says as much in its introduction, noting that it only describes the 20-round ChaCha (the 8- and 12-round variants are defined elsewhere). The round count has always been a constant printed in the standard.
The deviation: make that fixed round count something computed dynamically from the key. Same quarter-round logic, but how many rounds it runs depends on certain bytes of the key. Change the key and the round count changes with it.
Why humans miss it: you recognized the quarter-round structure, recognized the σ constants, your brain ruled "ChaCha20," and you copied it as 20 rounds. You never even looked at whether the variable controlling the loop count is a constant or an expression — because in every ChaCha you've ever seen, it's a constant.
The tell for this kind of deviation: a place you expect a constant, and there's an input-dependent expression instead. A well-steered counter-evidence pass will go check specifically for "the standard implementation hardcodes a value here, but here it's computed."
How differential testing confirms it: deliberately run two keys that differ by a single byte. If the output difference is far larger than one byte's worth of influence, that byte is feeding some global parameter (the round count, say) rather than just XORing into the keystream.
2. Constant perturbation: a base constant swapped for a neighboring value
The FNV-1a hash has two public magic numbers: the offset basis and the prime. Every correct FNV-1a implementation uses these two exact values, and they're published in the standard — the FNV reference page maintained by co-author Landon Curt Noll gives the 32-bit offset basis as 2166136261 and the prime as 16777619, down to the digit.
The deviation: swap one of those base constants for a neighboring value that differs from the standard by a tiny amount. The overall structure of the hash is identical — the XOR, the multiply, the loop are all correct — only that initial base has had a nearly invisible change made to it.
Why humans miss it: this is the nastiest of the four. You see FNV's structure, see a big constant that looks just like the offset basis, and rule "standard FNV-1a." You don't compare that constant bit for bit against the standard value — who double-checks a magic number they've "already recognized"?
This kind of deviation is almost only catchable by differential testing, because eyeballing large numbers is deeply unreliable. The method: take a known input, run it through the standard implementation and through the target black box, and compare. If the structure is identical but the outputs differ, the problem is almost certainly in a base constant. Then hand the suspect constant to the model and have it diff against the standard value — a machine is far more reliable at this than you.
The model's value here is very concrete: it remembers the standard FNV-1a offset basis to the last bit, and you don't. You just ask "is this constant exactly equal to the standard FNV-1a offset basis," and it points out the difference immediately.
3. Structural tweak: one step of a standard algorithm is locally rewritten
Each ChaCha double round is made of 8 quarter rounds — the first 4 act on columns, the next 4 on diagonals. Which words each quarter round operates on is a fixed set of indices laid down by the standard (RFC 8439 §2.3 spells out the block function and exactly which state words each round touches).
The deviation: quietly swap one or two of the word indices in one of the rounds. The vast majority of rounds stay standard; only somewhere in the middle does a word that should be operated on get replaced by another. It still looks like ChaCha, still runs without error, but the keystream it produces is completely different from standard ChaCha.
Why humans miss it: the quarter-round indices are a long string of numbers — eight groups like (0,4,8,12)(1,5,9,13)…. Your eye skims across and only confirms "yep, column rounds plus diagonal rounds," without checking that each group's four numbers are all in their standard positions. A change hidden inside a string of already eye-glazing indices is a perfect place to hide.
This one isn't a glance for the model either; you have to steer it to list the indices round by round and diff them against standard ChaCha. It's a mechanical checking task — precisely the kind of thing where the model doesn't drift and a human does. Have it output a "standard index vs. actual index" table and the deviation falls out on its own.
Differential testing confirms it: if the first two kinds are ruled out (round count right, constants right) and the output still disagrees, the problem is structural. Dump the intermediate state round by round; the first round that diverges from standard ChaCha is the one that was changed.
4. Material embedding: key data hidden inside the output
The first three all change the algorithm; this one changes the way the data is organized.
One common technique: the encryption key isn't transmitted over a separate channel — it's broken up and inserted into the ciphertext itself, and the receiver pulls it back out by the same rule. The slyer version makes the insertion position not fixed but computed from the data content itself — the ciphertext changes, and where the key is hidden changes with it. The outermost layer then wraps everything in a custom-alphabet Base64 and a marker prefix byte.
Why humans miss it: you're wrestling with the encryption algorithm itself and never realize the key doesn't need to be broken at all — it's right there in the ciphertext in your hand, you just don't know which slice it's hiding in. Beginners routinely get stuck here, trying to "crack" something whose plaintext is sitting in front of them.
The tell for this kind of deviation: a region within a block of data whose statistical characteristics differ from its surroundings (a key is usually high-entropy random bytes, and inserted into the middle of ciphertext it forms a recognizable "foreign segment"). The model can help you analyze "which interval of this output has a byte distribution unlike the rest," which locates the boundaries of the embedded material.
Confirming it: if you can find the insertion rule (some modulus over a byte sum, say), you verify by taking a few known input/output pairs, back-solving the formula for the insertion position, then verifying it forward. The model can help you induce the rule from a handful of samples, but whether it's ultimately right is still assert's call.
The counter-evidence pass is the reasoning tier's only real battleground
Across those four kinds, you may have noticed a common thread: recognizing the algorithm family (the candidate) is the easy step; finding the deviation (the counter-evidence) is the hard one.
The candidate step almost any model can do — ChaCha's σ constants, FNV's structure, any model that has seen the training data recognizes them. What actually separates models is the counter-evidence pass: whether the model is willing and able to keep picking at an algorithm it just recognized and say "but this part doesn't match the standard."
A weaker model loses its nerve here. Having recognized "this is ChaCha20," its counter-evidence pass tends to degrade into restating the candidate in other words — "the implementation follows the standard ChaCha20 structure, employing the classic quarter round…" — all a rehash of the candidate, not a single word actually checking for deviation. A counter-evidence pass like that can't guide your next step.
A strong reasoning model's counter-evidence pass is a different animal. It writes "the candidate is ChaCha20, but there are three departures from the standard implementation: first, the round count is controlled by a key-dependent expression, whereas standard ChaCha20 is fixed at 20 rounds; second, the word indices in round N don't match the standard diagonal round; third…" — each pointing at a specific, verifiable deviation point. A counter-evidence pass like that is the differential-test checklist you'll write in Stage 3.
This is why algorithm-family identification is worth putting on the reasoning tier, and worth testing yourself before you commit. The difference in counter-evidence quality across models directly decides how many useless tests you write and how many detours you take.
The four tiers I run for this workflow:
Stage | Capability it needs | Pick | model id |
|---|---|---|---|
Post-split structural mapping | Long context, reads a whole module in one pass | Kimi K3 |
|
Algorithm-family ID and counter-evidence | Strong reasoning; dares to argue against itself | Claude Opus 5 |
|
Bulk symbol renaming | Cheap, high concurrency | Claude Sonnet 5 |
|
Difference attribution | Mid reasoning, explains against specific bytes | GPT-5.6 Sol |
|
The second tier is the heart of this article. Its selection shouldn't be taken on my word — test it. The protocol is simple:
Pick 2–3 leaf functions from your own obfuscated bundle, at least one you already know the answer to (as a control).
Using the "candidate / evidence / counter-evidence" three-part prompt from the previous piece, feed the same input to
claude-opus-5andgpt-5.6-solseparately.Look only at the counter-evidence pass: is it actually checking deviations one by one, or restating the candidate in different words? On the control, how many of the deviations did each model catch?
The number and quality of the deviations caught is your selection criterion.
You'll see the difference in a single run — more directly than any benchmark leaderboard.
The switching cost is the real obstacle
Four models across three vendors. The naive way to use different models at different stages is to wire up three SDKs, three auth schemes, three error handlers — and most people do the math, decide it's not worth it, and end up running one model for everything, using a weak-counter-evidence tier for algorithm-family ID and taking a pile of detours without knowing why.
AIReiter flattens that layer: one key, one OpenAI-compatible interface, all four models behind it, and switching is just changing the model field in the request body.
# Algorithm-family ID + counter-evidence: the reasoning tier
curl https://aireiter.com/api/v1/chat/completions \
-H "Authorization: Bearer $AIREITER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5",
"messages": [{"role": "user", "content": "<three-part prompt + leaf function + constants>"}]
}'
# Bulk symbol renaming: change one field
# "model": "claude-sonnet-5"
# Difference attribution:
# "model": "gpt-5.6-sol"
Already on the OpenAI SDK? Point base_url at https://aireiter.com/api/v1 and change nothing else. On the Anthropic SDK? Hit POST /api/v1/messages with the same key.
On price, Claude models run at 30% off list and GPT models at half price. For this workflow the discount lands right on the sharp end: the algorithm-family step is where you iterate the prompt over and over, asking the same function across many rounds, and it's the most call-dense part of the flow; bulk symbol renaming starts in the hundreds of calls. Those two are the bulk of the cost.
Try it without signing up — run a few rounds by hand, see the two models' counter-evidence passes side by side with your own eyes, then decide.
In closing
The truth about reversing obfuscated signatures: most of the code is standard algorithms you copy verbatim; the real work is hunting the places where a standard algorithm was quietly changed.
The four kinds of deviation — constant-to-variable, constant perturbation, structural tweak, material embedding — share one trait: small enough that human pattern recognition skips them, large enough to make a rewrite fail wholesale. Humans are bad at staying suspicious of something that looks correct, and that is exactly the strength of a model (well-prompted).
But the model only generates suspicion; it doesn't confirm. Every deviation-point hypothesis ultimately has to become a differential test — the subject of the differential-testing piece. The model hands you a list of "places that might have been changed"; assert tells you which ones actually were.
