When to use / when not to use
- Use: when you have an APK / Android app package and need to inspect the manifest, Java code, resources, permissions, and component structure
- Use: when you need to identify hardening / obfuscation (packers, resource obfuscation) to prepare for unpacking or patching
- Don't use: when you need runtime hooking / decryption / bypassing (use [[re-frida]])
- Don't use: when the target is the logic of a native library .so (use [[re-format-elf]] under [[re-binary-core]])
- Don't use: when you need to extract DEX from runtime memory (use [[re-memdump]])
Tool preparation
Pure static analysis (unpacking / decompilation) can skip the sandbox ([[re-analyze/platform-tips]] highest principle); anything involving dynamic analysis / unpacking goes to [[re-frida]] / the unpacking domain. Verify all tools before use.
jadx — the main workhorse for Java decompilation (including JADX GUI)
- Official/GitHub release:
https://github.com/skylot/jadx/releasesDownloadjadx-<版本>.zip, after extracting runbin/jadx(Linux/macOS) orbin\jadx.bat(Windows); the GUI isbin/jadx-gui - macOS:
brew install jadx;Arch:pacman -S jadx - Requires Java 11+ (officially requires 11+ and 64-bit): Linux
apt install openjdk-17-jre/dnf install java-17-openjdk/pacman -S jre17-openjdk;macOSbrew install openjdk - Windows/WSL: use the Linux version zip inside WSL
- Verify:
jadx --version; version note — the release zip and the brew/pacman packages are updated in sync; rely on the version actually tested
apktool — unpacking / rebuilding (official/GitHub release)
- Requires Java 8+ (the minimum requirement on the official install page; the jar is compiled targeting Java 8). Official wrapper: download
https://github.com/iBotPeaches/Apktool/releasesDownloadapktool_<版本>.jarand the wrapper script (Linux/macOSapktool、Windowsapktool.bat), and put the script and jar in the same directory - Version model: 3.x is the current mainline, 2.x is the maintenance line— the two lines are not fully command-compatible, and the official documentation is also split between 3.x / 2.x tracks. After downloading, first
apktool --versionto confirm the major version; commands and tutorials from the 2.x era used directly on 3.x will reportUnrecognized option(see [[gotchas]] version differences for the list of changes) - Debian/Ubuntu:
apt install apktool(the repo version is older; command behavior just needs to be compatible) - macOS:
brew install apktool - Windows/WSL: Linux version inside WSL
- Verify:
apktool --version
aapt2 — resource dumping / restoring obfuscated resources
- Bundled with Android SDK build-tools. After installing cmdline-tools:
sdkmanager "build-tools;34.0.0", path$ANDROID_HOME/build-tools/34.0.0/aapt2 - macOS:
brew install --cask android-commandlinetoolsthensdkmanager "build-tools;34.0.0" - Windows: Android Studio → SDK Manager → check build-tools
- Verify:
aapt2 version; version note — the build-tools version updates with the SDK (example 34.0.0; newer SDKs bundle higher versions); the command interface is stable, so just use the installed version
dex2jar (optional) — dex → jar
- GitHub release:
https://github.com/pxb1988/dex2jar/releasesDownload the zip, after extractingd2j-dex2jar.sh classes.dexto get the jar, then browse with jd-gui - Requires Java
- Verify:
d2j-dex2jar.sh --version
apksigner / keytool — repackaging and signing (companion to smali patching)
- apksigner is in Android SDK build-tools (same path as aapt2); keytool comes with Java
- Verify:
apksigner --version;keytool -help
adb — device installation / repackaging closed-loop verification
- Bundled with Android SDK platform-tools (
sdkmanager "platform-tools") - Linux:
apt install adb(Debian/Ubuntu);macOS:brew install --cask android-platform-tools;Arch:pacman -S android-tools - Windows: official download of the platform-tools zip
- Verify:
adb version
Procedure
Execute in order, record the result of each step (evidence path + sha256, see [[re-triage]]).
-
Unpack (apktool d):
apktool d app.apk -o out/Decode out
AndroidManifest.xml、smali/(recompilable bytecode),res/、assets/、lib/。-s(--no-src) without decoding source (resources only),-r(--no-res) without decoding resources (smali only), combine as needed when patching. The output is more suitable for recompilation after modification than jadx. -
AndroidManifest entry/permissions/components:
grep -E 'application|activity|service|receiver|provider' out/AndroidManifest.xml | head -40Record: main entry (
application/ the firstactivityofandroid:name)、uses-permission(SMS / call log / device admin permissions are malicious or sensitive signals), exported components,android:debuggable="true"(debuggable apps can be directly attached via [[re-frida]]). After hardening, the entry is often replaced with a shell class (see Pitfall 1). -
jadx decompiles Java:
jadx -d java-out app.apk # 批量反编译全部 dex 类 jadx app.apk # 或 GUI 模式逐类浏览First look at the entry classes (Application / MainActivity) and the algorithm / verification classes; sensitive strings (keys, URLs, verification logic) are located by
grep -rE 'key|secret|sign|license' java-out/. dex2jar equivalent alternative:d2j-dex2jar.sh app.apkget the jar and then use jd-gui; the jar can also be converted to the [[re-java]] workflow (CFR/JD-GUI browsing, Java hardening identification). If business code cannot be decompiled → hardening identification (step 5). -
smali patching approach:
# 改 smali 后回编译、对齐、签名、安装(用 apksigner 时 zipalign 必须在签名之前,签名后不得再改包) apktool b out/ -o patched.apk keytool -genkey -v -keystore ks.jks -alias r -keyalg RSA -validity 3650 -storepass 123456 zipalign -P 16 -f -v 4 patched.apk aligned.apk apksigner sign --ks ks.jks --out signed.apk aligned.apk adb install signed.apkCommon modifications: invert a conditional jump (
if-eqz↔if-nez), changeconst/4 v0, 0x0to return a constant, change the verification method directly toreturn-void. Firstjadxlocate the logic and then modify it in the corresponding smali. If the target contains signature self-verification, the patch may be blocked (see Pitfall 2). -
Hardening/obfuscation identification:
- Shell characteristics: jadx only sees shell classes (
com.stub.StubApp=ijiami,com.secneo.apkwrapper/com.bangcle.*=Bangcle, Legu, etc.);lib/has an extra shell so (libjiagu.so=360 hardening,libDexHelper.so=ijiami…);classes.dexsize is abnormally small (the real dex is decrypted at runtime) - Resource obfuscation characteristics:
res/resource paths are randomly renamed,resources.arscstructure is abnormal - If identified as hardened → switch to the unpacking domain ([[re-anti-analysis]] / [[re-mobile-pack]]) or dynamically obtain the in-memory DEX ([[re-frida]] / [[re-memdump]]); for fine-grained fingerprint comparison of shell names/sections/entropy, go to [[re-packer-id]]; for resource obfuscation, restore with aapt2:
aapt2 dump badging app.apk # 包名 / 入口 / 权限速览 aapt2 dump resources app.apk # 混淆后的资源映射
- Shell characteristics: jadx only sees shell classes (
-
AAB and split APK (distribution form boundaries):
.aab(Android App Bundle): essentially a zip containing dex and resources—jadx can open it directly to read the code; installing on a real device requires bundletool to generate an APKS, and once you have the .aab, just follow steps 1-3- split APK(
base.apk+config.*.apk): the main dex and code are in base, and language/density configurations are in split—for static analysis, focus on base, and compare resource differences in split; apktool unpacks base and split separately
Cross-domain combination
- [[re-mobile]]: workflow step 2 (APK static branch) fixedly invokes this skill
- Needs runtime (decryption / hook / bypass) → [[re-frida]]; obtain DEX from runtime memory → [[re-memdump]]
- Hardened / packed → [[re-anti-analysis]] (unpacking domain) and [[re-mobile-pack]] (hardening and unpacking special topic); native .so → [[re-binary-core]] ([[re-format-elf]] / [[re-ghidra]])
- Java-layer deep dive after dex→jar (obfuscation / Java hardening) → [[re-java]]
- This skill is invoked by the triage "Mobile App Analysis" path of [[re-analyze]] (re-mobile → re-apk)
Common pitfalls and traps
-
Hardened sample: jadx only shows the packer: Symptom — decompilation yields only packer classes like StubApp, with no business code at all; Cause — the real dex is encrypted and stored in assets/ or only decrypted at runtime; Countermeasure — identify the packer per step 5, switch to [[re-anti-analysis]] for unpacking, or use [[re-frida]] / [[re-memdump]] to dump the in-memory DEX at runtime
-
Signature verification blocks patching: Symptom — after repackaging and installing, it crashes or reports "signature mismatch / unsigned"; Cause — in-app self-verification of the signature (comparing against PackageManager's signature info); Countermeasure — locate the verification point and patch around it (modify the return value / jump in smali), or hook it with [[re-frida]]
PackageManager.getPackageInfocall chain -
Resources cannot be viewed directly after resource obfuscation: Symptom —
res/paths do not match resource IDs, and strings cannot locate the target resource; Cause — resources are obfuscated with randomized renaming; Countermeasure — use aapt2 dump to restore the mapping (step 5), and if necessary combine with dynamic analysis for comparison -
Native .so analyzed as Java: Symptom — core logic (algorithm / anti-debugging) cannot be found in the Java layer; Cause — sensitive logic is written in the JNI .so; Countermeasure —
lib/pull the so and switch to [[re-format-elf]] + [[re-ghidra]] ([[re-binary-core]]), use the export table /Java_<包名>_<类名>_<方法名>style symbols to match JNI functions -
apktool recompilation fails: Symptom —
apktool breports a resource compilation error; Cause — resources were decoded during unpacking, and some resource formats are incompatible with recompilation; Countermeasure —apktool d -rkeep the original resources undecoded, and only modify smali before recompiling -
Analyzing only base in a split APK misses resources: Symptom — the target string/resource cannot be found in base; Cause — language/density configurations are split across
config.*.apksplits; Countermeasure — use apktool to unpack base and each split separately, and compare resource differences by split (step 6) -
Four details of writing your own dex parser: opcode is the low byte of u16(the high byte is the register bits; reading a full u16 as the opcode misaligns everything);
fill-array-datais 0x26(31t), goto is 0x28(0x24 is filled-new-array, and going by memory is guaranteed to be wrong); string_data_item has a uleb128 length prefix(failing to skip it treats the length as a character); code_item'sinsns_sizeis at +12(+4 is outs_size) — before writing a parser by hand, check the layout against the dex specification -
Only the startup script remains in the jadx directory: Symptom —
jadxreports ClassNotFound; Cause — incomplete installation (jar missing/cleaned up); Countermeasure — re-download the complete zip from the GitHub release (codeload.github.com is more reliable than github.com), or directly use a single tool such as baksmali -
Analyzing container/dual-app (VirtualApp-like) samples: Symptom — the sample behaves abnormally when running inside a dual-app container, the target process cannot be hooked, or the container itself needs to be analyzed; Cause — VA/Blackbox containers simulate the system via "proxy stubs + dynamic registration": launching an Activity goes through a fake AMS inside the container (Binder dynamic proxy interception, restoring the VAPP request parameters before forwarding to the real system); IO redirection redirects hardcoded absolute paths to the in-container installation path; ContentProvider's
onCreateserves as the container initialization entry point (handleBindApplicationactive invocation); the principle is the same as the old Android version (VA only supports old versions, Blackbox is a modern reference implementation); countermeasures—when analyzing apps inside a container, pay attention to the true process ownership (container process vs host process), and choose hook points in the container framework layer (fake AMS/IO redirection functions) rather than the application layer; to identify dual-app environments (dual-app detection), look at/proc/self/mapscontainer so and fake package name path characteristics; this kind of principle is also generally applicable to install-free execution/plugin-based analysis -
after repackaging
install -rdoes not take effect (repackaging trap): symptom—after installing the repackaged signature, the behavior does not change at all, leading to the misjudgment that the "patch is ineffective"; cause—when the package name and signature are the same,-rwill replace the existing app and retain data; a versionCode that is not incremented does not prevent content updates (it mainly affects downgrade installation rules); when "the patch did not take effect," you should investigate: the signature verification chain (see the next item), split APK inconsistency, the installation target of multiple users/profiles, build artifact caching (apktool recompilation build cache not cleared), and the target process not being restarted; countermeasure—after installation, perform closed-loop verification:adb shell pm path <包名>retrieve the installed APK and perform a byte-level comparison of the patch address; after confirming the bytes are in place, then investigate the logic layer (consumption point errors, etc.) (source: reverse-skills (inliver233), MIT) -
Cross-layer signature verification chain and system version drift: symptom—after repackaging, prompts such as "signature mismatch" still appear, or after reusing an old-style signature adaptation hook, tampering is falsely reported on a new system; cause—signature verification is often a cross-layer chain (Java layer retrieves signature information → digest calculation → native layer compares with a hardcoded baseline; if any layer mismatches, it is judged a failure), so neutralizing only a single point is not enough; the app's built-in PackageManager signature adaptation hook can be reused (intercept the getPackageInfo series, replace both signatures and signingInfo, and affect only its own package), but the constructor signature of the internal signature object it constructs via reflection (such as SigningDetails) changes with the system version; after an exception returns null, it triggers a misjudgment; countermeasure—prefer reusing the app's built-in signature adaptation hook; when the version drifts, use Frida to probe the live constructor and switch to rebuilding via the public constructor; for packed targets, prefer no repackaging (runtime signature adaptation), and only consider repackaging if unpacked; on the static side, find the native comparison function to neutralize the failure branch (source: reverse-skills (inliver233), MIT)