I Reopened My Own APK in JADX After Hardening It
I develop Android applications that include authorization and payment functionality. Before every release, I have a habit of opening the release APK in JADX and searching through the package names. It started as a way to check whether obfuscation had missed anything. Eventually, it became a reason to worry about my own code.
In the unprotected version, searching for an authorization class name immediately found it. I could expand a payment callback and read its implementation. String constants were still in plaintext.
Obfuscation had changed some local variable names, but that did little to stop someone who understood the business logic.
It took me less than twenty minutes to locate the activation code validation logic and identify the class handling order results. This was my own code, so I knew exactly what I was looking at. Anyone who obtained the APK would start from the same position.
That afternoon, I used the Windows desktop version of XopProtector to build a protected APK. I downloaded the package from Releases, extracted it, and launched XopProtector.exe. There was no need to configure the NDK first.
The result was still an installable APK. The package name and application icon remained unchanged.
That evening, I opened the protected APK with the same apktool and JADX workflow.
The manifest was readable. The DEX could be decompiled. But I could no longer find those business classes.
My Business Code Was No Longer in the Visible DEX
The entry point in classes.dex had been replaced with the shell's ProxyApplication. The code I could inspect in JADX consisted mainly of shell startup logic and bridge code. Some strings had also been transformed using rolling XOR.
Searching for the original class names returned no results.
An additional DEX contained filler code. The business DEX had been moved into assets/protector/, under the filename dexes.zip.
I double-clicked it, expecting a regular archive. But its file header no longer identified it as a normal ZIP file. The accompanying code.bin could not be opened as ordinary DEX code in JADX either.
The configuration file was still readable text, and I could see its switches. The decryption key was not stored directly inside it. At runtime, key material was derived using the master key, signing certificate, and package name. The configuration also had integrity verification.
I considered modifying a detection switch, but the altered configuration would no longer pass the integrity check. The shell would not simply accept the modified settings.
I also inspected the native libraries. Critical sections of the shell library were encrypted. When protection was enabled for business SO libraries, their protected code sections were no longer the original plaintext machine code.
System libraries and a small number of engines that might crash when encrypted could be excluded. That trade-off made sense: protecting the application is pointless if it can no longer start.
Two Questions I Still Had
1. Would the Original DEX Reappear in Memory at Runtime?
Partially, yes.
The default approach encrypts the application package, but ordinary methods may still be restored into DEX form for a short period during execution. The exposure window is reduced, rather than assumed to disappear completely.
Payment-related classes follow a different path by default. Instead of restoring the complete method body into the DEX, the protected method retains a bridge stub leading to VmBridge. Execution takes place inside a native interpreter, without writing the original method body back into the DEX.
For industry-specific applications, authorization, activation, and cryptographic methods can also be included in this protection path, preferably limiting the scope to the application's own packages.
The custom instruction encoding can also change between builds.
So even if I managed to capture the runtime DEX that evening, the protected payment and authorization methods could still be represented by bridge stubs rather than their original implementations.
That was an important distinction. Capturing DEX does not automatically mean recovering every protected method.
2. Could Someone Bypass the Protection with a Different Debugger or Signing Certificate?
XopProtector includes detection for common Frida artifacts, debuggers, and Xposed environments. It also checks the integrity of its own native libraries.
Root and emulator detection are not enabled by default in the configuration I used. That helps reduce the risk of false positives on industrial devices and vendor-customized Android hardware.
The signing certificate can also be checked at runtime. When signature verification is enabled, re-signing the APK with a different certificate causes verification to fail, preventing the protected business logic from following its normal loading path.
There are some important configuration details, though.
Resource encryption, resource path obfuscation, proxy detection, and certificate pinning require separate configuration. I used the default profile for this test, so those additional features were not enabled in my build.
Why I'd Mention This in My Release Notes
Before hardening, anyone who knew how to use JADX could see essentially the same business code I saw.
After hardening, the same tools primarily exposed the shell. The application entry point had changed, the business DEX was stored as encrypted data rather than a directly readable resource, selected methods could remain native-interpreted bridge stubs even when runtime DEX was captured, and signing verification could reject an APK signed with an unauthorized certificate.
What this buys me is time, not a guarantee that the application can never be reverse-engineered.
The project's own position is sensible: hardening increases the cost of reverse engineering; it does not make cracking impossible. Native interpreter protection applies to selected methods, not automatically to every line of code. Going further requires a deeper understanding of native shells and runtime behavior.
For an application like mine, that difference is meaningful.
My authorization and payment logic no longer sits on the first page of JADX.
XopProtector is open source under the Apache 2.0 license.
Repository: https://github.com/xopJack/XopProtector
Downloads: XopProtector Releases
Top comments (0)