Skip to main content

Deobfuscation strategy

Deobfuscation is not one operation. Name minification, optimization, string encryption, reflection, control-flow transformation, and packing remove different kinds of information and need different evidence.

Identify what changed​

SymptomLikely transformationResponse
classes and methods named a, b, cProGuard/R8 name minificationuse mapping if owned; otherwise infer roles structurally
methods apparently missingoptimization/inlininginspect call sites and smali; compare the unoptimized build if available
unreadable constants wrapped by one methodstring encryptionisolate/reimplement the decoder or observe authorized runtime returns
large switch dispatchers and opaque branchescontrol-flow obfuscationcompare engines; simplify cautiously; read smali
reflection around reconstructed namesreflection obfuscationtrace string construction and reflective calls
tiny loader DEX, real classes absentpacking/virtualizationmove to loader/native/runtime analysis

APKiD can identify known signatures, but manual structure still matters.

Use an owned mapping.txt​

A mapping generated by the same release build is the strongest source of original names:

com.example.network.ApiClient -> a.b:
java.lang.String baseUrl -> a
42:57:void execute() -> b

Use Android's R8 retrace for obfuscated stack traces:

retrace mapping.txt obfuscated-stacktrace.txt

Important constraints:

  • the mapping must match the exact build/version;
  • retrace is primarily for stack traces, not wholesale source-tree rewriting;
  • line mappings can be absent or altered by optimization;
  • never seek or use a third party's private mapping without authorization.

For an owned decompiled tree, custom renaming can improve navigation. Parse exact class/member mappings, rename classes before members, and keep a reversible map. Blind text replacement can corrupt unrelated short names.

Work without a mapping​

Recover semantic labels, not imaginary originals.

  1. Anchor classes named in the manifest.
  2. Use framework superclasses and implemented interfaces.
  3. Search constants: URLs, preference keys, intent actions, log tags, database tables.
  4. Follow call sites and data types.
  5. Rename in notes or a working copy (a.b → probable NetworkClient) with confidence markers.

A useful naming ledger:

Obfuscated symbolWorking labelEvidenceConfidence
a.c.eCheckoutActivitymanifest + extends Activity + layout stringshigh
x.y.a()decryptStringcalled around constants; returns UTF-8 stringsmedium

Do not present working labels as recovered source names.

Handle encrypted strings​

Find a repeated static method around opaque constants, especially in static initializers. Then choose the least invasive route:

Reimplement the routine​

When the routine is self-contained, translate it into a small script, validate against one known value, and batch only the constants relevant to the task.

Record:

  • encoding and byte order;
  • key derivation and per-call parameters;
  • integer overflow/rotation semantics;
  • expected output charset;
  • validation cases.

Observe authorized runtime values​

If keys come from device state or native code, instrument the decoder's return value in a controlled lab. This is narrower than dumping every class and often answers the question faster. Follow the dynamic fallback authorization boundary.

Decrypted data may be more sensitive

Runtime strings can contain endpoints, tokens, user data, or anti-abuse signals. Minimize collection and redact reports.

Approach control-flow transformations​

Before applying an automatic simplifier:

  1. preserve the original DEX and its hash;
  2. isolate one problematic method;
  3. compare jadx with CFR/Procyon or another engine;
  4. inspect the original smali;
  5. run the transformed output in a separate path;
  6. compare semantics, not only readability.

Tools such as simplify can symbolically execute methods and remove dead branches, but unsupported instructions, reflection, native calls, and environment-dependent predicates can limit or alter results. Never replace original evidence with transformed output.

Recognize the stopping point​

Some information no longer exists after compilation and optimization: comments, exact formatting, many local variable names, generic abstractions, and inlined boundaries. A responsible result may be:

The branch can be explained at bytecode level, but the original method boundary and identifier are not recoverable from this build.

That is better than a confident but fabricated reconstruction.