Reverse Engineering an APK: What the Tools Actually Show You (And What's Legal)
An APK is a zip file. Unpacking it is standard static analysis, not magic. The four-tool chain — apksigner, aapt2, apktool, jadx — and exactly what each step tells you about a package you downloaded.
“Reverse engineering an APK” sounds like it needs a special setup. It doesn’t. An APK is a zip archive with a compiled manifest, a Dalvik bytecode file, and some resources. Everything you need ships in four command-line tools, three of which install in one brewline. The real skill is knowing which tool answers which question — and stopping as soon as you have your answer.
Core idea: Reverse engineering an APK is a read-first workflow, not an edit-first workflow. Most real-world use cases are answered by signature verification and the permission manifest, before you ever decompile a line of code.
What’s inside an APK
Rename .apk to .zip and open it:
app.apk
├── AndroidManifest.xml # compiled binary XML — not readable as text
├── classes.dex # Dalvik bytecode (may continue as classes2.dex, ...)
├── resources.arsc # compiled resource index
├── res/ # compiled layouts, drawables, values
├── lib/
│ ├── arm64-v8a/*.so # native libraries per ABI
│ └── armeabi-v7a/*.so
├── assets/ # raw files, not compiled
└── META-INF/ # signing materialTwo things surprise people. First, AndroidManifest.xml and everything under res/ is binary XML, which is why unzipshows garbage and why apktool exists. Second, there is no Java source inside — only Dalvik bytecode, which maps to something Java-like but is not the original source.
The four-tool chain
apksigner — “is this the real app?”
apksigner verify --print-certs app.apkThe SHA-256 digest is stable across every versionthe same developer publishes. Compare it against a known-good install — pull the installed APK with adb shell pm path com.example.app then adb pull— and you have a definitive answer in ten seconds. A mismatch means the package was rebuilt and re-signed by someone else. This single step eliminates most malicious repackages.
aapt2 — “what does it claim to need?”
aapt2 dump badging app.apk | head -30
aapt2 dump permissions app.apk
aapt2 dump permissions suspect.apk > suspect.txt
aapt2 dump permissions official.apk > official.txt
diff official.txt suspect.txtExtra permissions that matter:
| Permission | What it enables |
|---|---|
READ_SMS / RECEIVE_SMS | Steals 2FA codes — no legitimate reason for most apps |
READ_CONTACTS | Bulk contact exfiltration |
BIND_ACCESSIBILITY_SERVICE | Read screen content, simulate taps, keylog — the most abused permission on Android |
BIND_DEVICE_ADMIN | Prevent uninstall, lock the device |
SYSTEM_ALERT_WINDOW | Overlay attacks — draw a fake login UI over a real app |
REQUEST_INSTALL_PACKAGES | Silently drop and install a second payload |
A downloaded “mod” showing three of those is not a mod. It is a dropper.
apktool — “what’s configured and what resources ship?”
apktool d app.apk -o app_src
grep -rn "https://" app_src/res/values/strings.xml
grep -n 'android:exported="true"' app_src/AndroidManifest.xml
grep -rn "loadLibrary\|DexClassLoader" app_src/smaliYou get human-readable AndroidManifest.xml, res/ XML, and a smali/ tree. An android:exported="true" component you did not expect is worth understanding: exported components can be triggered by any other app on the device, which is real attack surface.
jadx — “how does it actually work?”
jadx -d out_java app.apk
jadx-gui app.apk # far more usable: global search, jump to definitionjadx decompiles dex into readable Java. Not perfect Java — control flow is reconstructed and some methods fail — but good enough to follow business logic. Search by string constant to find where a URL or error message is used, then jump to definition to walk the call graph.
Case study 1: is this download tampered with?
- Signature.
apksigner verify --print-certsand compare the digest. A mismatch means stop. - Permissions. Diff against official. New SMS, accessibility, or install_packages means stop.
- Injected libraries. List
lib/arm64-v8a/for.sofiles that do not belong. Native payloads hide here because they are harder to inspect than dex. - Assets. Anything
filecannot identify is worth a look. Encrypted payload blobs typically appear as.dator.binfiles in the tens-to-hundreds of KB range reported as plaindata, decrypted at runtime by a matching.so.
Case study 2: how does feature X work?
Search jadx for the API method name, or for a class that appears in logcatwhen you trigger the feature. “Find usages” walks you to the implementation: thresholds, debounce strategies, retry logic — the parts that usually are not in the docs.
The line here is real: learning the approach and writing your own implementation is fine. Copying decompiled code into your project is copyright infringement, and if the app ships obfuscated you are probably also violating its terms of service. Understand, then build.
Case study 3: recovering your own lost source
apktool d myapp.apk -o recovered| Recoverable | Not recoverable |
|---|---|
| Layout XML, menus, animations | Original Java/Kotlin source (only decompiled approximations) |
| Drawables — icons, colors, styles | Comments |
strings.xml— all UI copy | Original identifiers (obfuscated to a, b, c) |
| Manifest: permissions, components, intent filters | Third-party library sources |
Everything under assets/ | Build config, Gradle files |
For an unobfuscated app, jadx output is often readable enough to port business logic by hand. Budget more time than you expect: decompiled code loses type information in generic-heavy sections and needs manual cleanup.
Obfuscation vs. hardening
Obfuscation (R8 / ProGuard)
Classes and methods are renamed to short identifiers, but structure survives. Lots of single-letter packages in the output is the tell — jadx still shows full control flow, you just infer intent from strings, API calls, and data flow. It slows reading down significantly but does not stop it.
Hardening (packers)
The real dex is encrypted and decrypted in memory at runtime. Symptoms: classes.dex is suspiciously small (a few hundred KB) while the APK is 30+ MB, jadx shows only a loader stub, and native libraries are huge with names like libjiagu.so. Getting past this requires dynamic analysis — Frida hooks, a debugger attached to the running process. For most purposes the correct answer at this point is to stop and treat the app as opaque.
Legal boundaries
| Action | Status |
|---|---|
| Decompiling your own app | Fine |
| Studying an implementation, then writing your own | Fine |
| Analyzing a suspicious APK before installing | Fine |
| Analyzing malware | Fine, and useful to everyone |
| Removing a paywall or licence check for your own use | Violates ToS; illegal in many jurisdictions |
| Modifying and redistributing someone’s app | Copyright infringement |
| Publishing a modified app to an app store | Clearly illegal |
The tools are neutral. The verification workflow above is the same one a security researcher uses on malware — you are just pointing it at a package you downloaded.
Common questions
Can decompiled output be recompiled into a working app?
Not really. Decompilation is lossy: control flow survives, but type information, generics, and resource references do not round-trip. For modifications, patch smali and rebuild:
apktool b app_src -o modified.apk
keytool -genkey -v -keystore test.keystore -alias test -keyalg RSA -keysize 2048 -validity 10000
apksigner sign --ks test.keystore --ks-pass pass:123456 modified.apkThe resigned APK cannot install over the original because the certificate differs — that is the OS enforcing integrity, not a bug.
The short version
| Question | Tool | Time |
|---|---|---|
| Is this the genuine app? | apksigner --print-certs | 10 sec |
| What does it ask for? | aapt2 dump permissions | 5 sec |
| What’s configured? | apktool d | 1 min |
| How does the code work? | jadx-gui | 10+ min |
Start at the top. Most questions never reach the fourth row — and the first row alone would prevent the majority of malicious sideloads. If you just need a Play-distributed APK without trusting a mirror site, use gptoapk’s download tool, which resolves through Google Play’s own distribution path.