Information Security 11 min read

Common Pitfalls and Solutions When Building an APK Protection Tool

This article enumerates the typical traps encountered while developing an Android APK protection solution—such as signature verification, JNI library stripping, smali injection limits, magic‑number manipulation, and post‑obfuscation safeguards—and offers practical mitigation strategies for each.

Hujiang Technology
Hujiang Technology
Hujiang Technology
Common Pitfalls and Solutions When Building an APK Protection Tool

Protecting Android APKs has become a routine requirement, and many companies, including ours, have built custom anti‑repackaging tools beyond standard products like ProGuard or DexGuard.

Pitfall 1: Signature verification – Simple string comparison is insecure; Java‑level checks can be altered after decompilation, and JNI checks may return a signature that differs by one byte. The recommended fix is to compare specific byte values rather than the whole signature string.

Pitfall 2: Still signature verification – Storing the full signature in memory is unsafe. Use language features to keep only one or two characters for verification, for example:

class Sig {
    private:
    string c0;
    string c1;
    string c2;
    ...
}

This reduces memory exposure and speeds up checks.

Pitfall 3: JNI library protection – Attackers can strip the .so file and delete the corresponding System.loadLibrary() call. To counter this, randomize the loading code and scatter many uniquely named checks throughout the app, making reverse‑engineering tedious. An example of obfuscated loading code:

var a = "l", b = "o", c = "a", d = "d", e = "i", f = "b", g = "r", h = "y", i = "n";
var aa = "j", bb = "a", cc = "v", dd = "n", ee = "g", ff = "s", gg = "t", hh = "e", ii = "m";
var aaa = ".";
var x = "$a$b$c$d${a.toUpperCase()}$e$f$r$c$g$h";
var s = Class.forName("$aa$bb$cc$bb$aaa$a$c$dd$ee$aaa${ff.toupperCase()}$h$ff$gg$hh$ii");
var ss = Class.forName("$aa$bb$cc$bb$aaa$a$c$dd$ee$aaa${ff.toUpperCase()}$gg$rr$e$i$ee");
var yy = "$ff$hh";
var v = s.getMethod(x, ss);
v.invoke(null, yy);

After compilation, the resulting smali is extremely hard to read, especially when the variables are defined across many locations.

Pitfall 4: Smali code injection – Injecting large numbers of classes and methods can exceed the 65,535‑method limit of a single dex file. The solution is to assess the current method count, add the injection count, and if it exceeds the limit, move part of the injected code to a new dex (e.g., smali_classes2 ). When targeting Android 5.0+ (API 21+), MultiDex may be unnecessary, but for lower targets you must manually split dex files and ensure the Application class and launcher Activity remain in the primary dex. Example of adding MultiDex support:

protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
}

Pitfall 5: Magic Number – Modifying the ZIP header (magic number) can break standard extraction tools while still being loadable by Android. Because the writable offsets vary across Android versions, a single static magic number is unreliable. Instead, generate multiple magic numbers algorithmically and randomly select one for each build, with the JNI layer verifying the chosen pattern.

Pitfall 6: Additional protection on top of obfuscation – Further protection after ProGuard‑style obfuscation can cause class‑name collisions, especially on case‑insensitive file systems (e.g., macOS). Dynamically generate unique class names by scanning existing packages, and be aware of platform‑specific file‑system behavior. If the generated class is a JNI loader, you must recompile the native library to match the new name.

Extra notes: When injecting a full Kotlin runtime, reserve about 8,000 extra method slots (the Kotlin framework adds roughly 6,800 methods) to stay under the 65,535 limit. Avoid relying on a single magic number; use a set of generated patterns to increase uncertainty for attackers.

In summary, any protection technique merely raises the effort required to repackage an APK; continuous updates and layered defenses are essential for effective security.

JNIAndroid securitySignature VerificationAPK protectionsmali injection
Hujiang Technology
Written by

Hujiang Technology

We focus on the real-world challenges developers face, delivering authentic, practical content and a direct platform for technical networking among developers.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.