SReverseby Simpa Labs

Shrinking and keep rules

How ProGuard affects APK API analysis

The effect ProGuard has on an analysis comes down to its keep rules. They decide which classes, methods and fields still have usable names in the shipped dex.

Where ProGuard sits in the build

ProGuard processes Java bytecode. In a build that still uses it, compiled classes pass through shrinking, optimization, obfuscation and preverification, and the result is dexed for the APK. The output is a smaller program with short names. R8 reads ProGuard configuration syntax and performs the same broad classes of work, so a ProGuard configuration file recovered from a repository is often the most informative single artifact an analysis can find.

Keep rules decide what survives

ProGuard's default behavior removes everything it believes is unused, which would break an Android app immediately. Keep rules prevent that. The standard Android configuration preserves classes the platform resolves by name, and an app adds its own rules for anything it looks up dynamically. The effect on analysis is direct: a class named in a keep rule keeps its name, and everything reachable from it tends to keep enough structure to read.

The practical consequence is that the parts of an app which use reflection or name based lookups are often the easiest parts to read, which is the opposite of what a reader expects from an obfuscated build.

Debug attributes and annotations

ProGuard can strip attributes that carry debugging metadata. When source file names and line numbers are removed, stack traces stop pointing at the original source, which costs little on its own because a trace from the shipped build still maps renamed frames to each other. When those attributes are kept, an exception from a crash report can point straight at the method that produced it, and that is often the quickest entry into a flow.

Annotations are a separate attribute. Frameworks that inspect them at run time need them preserved, so an app built on such a framework usually keeps them, and the annotations then survive with their string values intact. Checking for them takes a moment and can hand you the endpoint list directly.

What an API analysis can still use

  • Retrofit annotations on interface methods, which carry the HTTP method and path as string values even when the method name is a single letter.
  • URLs, host names, header names and content types stored as constants.
  • JSON field names in model classes, because a reflective serializer resolves them by string.
  • Manifest components and their intent filters, which describe entry points into the flow.
  • Resource files and layout names, which still link screens to the code that inflates them.
  • Native method declarations, whose names and signatures are fixed by the JNI API specification.

Distinguishing ProGuard from R8 output

Both tools produce renamed symbols, so the presence of single letter classes proves only that one of them ran. Several signals help. R8 can merge classes outright, so a build where interfaces have vanished and methods have been folded into unrelated types points at R8 with optimization enabled. ProGuard leaves more type boundaries in place. A mapping file, when present, names the tool and the options in its header.

The distinction matters less than it appears. The reading strategy is the same either way: find the network layer, trace values, and use behavior to name the code.

Reconstructing the request path

Begin with the client. Find the code that creates it, since that is where base URLs, interceptors and converters are configured. The interfaces attached to that client carry the endpoint definitions, and from each definition you can follow the call into the repository or service class that prepares its arguments. Renamed or not, the argument list tells you what the endpoint needs.

Then confirm the path with one live call. A request that reaches the server with the values you predicted closes the loop on that endpoint. Where a prediction is wrong, compare the sent bytes against your notes and look for a step you missed, such as an interceptor that rewrites a header or a converter that changes the body encoding.

Serialization is worth checking early, because it decides how much of the payload you can read statically. A reflective JSON library resolves field names by string, so the names on the wire usually match something present in the dex. A generated serializer compiles the same mapping into code, which means the field names appear as constants in generated classes rather than in the model. Finding which of the two the app uses tells you where the payload shape is defined.

Reviewed 28 September 2026 · SReverse research desk

Start your full APK reconstruction

Send the full APK

Projects start at $120. Choose WhatsApp or email, then attach the APK in the app that opens. We reply within one hour with the next step and send the fixed quote after review.

Want us to contact you?