rea-reverse

Android APK Analysis with REA: JADX, Apktool, ADB and Flutter

Written for REA 6.4.0Updated 8 min read
On this page

REA, the open-source project by morluto, gives your terminal or coding agent a set of Android tools that return structured, artifact-bound Evidence instead of a folder of loose decompiler output. This guide walks through a complete static investigation of an open-source APK, then shows how to acquire an installed package from a device you control and how to triage a Flutter app.

Who this is for: developers and security researchers who want answers like “where is this value computed?” or “which Dart SDK built this app?” without clicking through a GUI decompiler.

What you will learn:

  • Inspect an APK’s manifest, permissions and class inventory with JADX through REA
  • Search classes, read decompiled methods and trace callers
  • Decode resources and localized strings with Apktool
  • List devices and pull an installed split APK set over ADB
  • Identify a Flutter build and its Dart AOT snapshot

Targets used: Aegis Authenticator (GPL-3.0, also distributed on F-Droid) and the open-source (BSD-3-Clause) Flutter Gallery app, whose v2.9.2 release page provides flutter_gallery_Android.apk. Both are open source, so you can compare every finding with the original code.

Prerequisites

  1. REA 6.4.0 or newer: npm install --global rea-agents, or prefix commands with npx rea-agents@latest. Connect it to your agent with npx rea-agents setup (/en/getting-started-rea-mcp/).
  2. A full JDK 17 or newer, including the jdk.compiler module. A JRE is not enough: REA compiles a small metadata bridge at startup. Check with java --list-modules | grep jdk.compiler.
  3. jadx-headless-mcp 0.7.1 (the -all.jar from its GitHub release). REA does not install it.
  4. Apktool 2.7.0 or newer for resource decoding.
  5. Android platform-tools adb if you want the device section, plus an emulator or a phone you own.

REA installs none of these. The Android provider is verified on macOS and Linux; on Windows the REA Aegis showcase recommends WSL.

Step 1: Get and pin the APK

Download Aegis v3.4.3 from its official GitHub release and check its digest:

shasum -a 256 aegis-v3.4.3.apk

The REA showcase recorded fa091c91912c0d56ec3553394fac4158f9a9d178e93bcbb35419601d788cb528 for this file (6,881,897 bytes).

Why pin the digest: every REA result embeds the APK’s SHA-256. If your hash matches, your results are comparable to the published case. The F-Droid build of Aegis is built and signed by F-Droid, so its bytes and digest differ; it is equally valid to analyze, just not byte-identical to the showcase.

Step 2: Point REA at JADX and inspect the package

export REA_JADX_MCP_JAR=/absolute/path/jadx-headless-mcp-0.7.1-all.jar
# Optional: choose a specific JDK instead of java on PATH
export JAVA_HOME=/absolute/path/existing-jdk

rea inspect-android-package /absolute/path/aegis-v3.4.3.apk --json

Why: package inspection is the map. It returns the decoded manifest XML, package and SDK declarations, permissions, class and resource counts, the engine identity, and the APK and engine JAR digests. For Aegis the showcase reports package com.beemdevelopment.aegis, version 3.4.3 (version code 82) and 3,077 classes.

Two things this step does not do: verify the APK signature (reported as not_performed) or execute the app.

Over MCP, add REA_JADX_MCP_JAR to the REA server’s environment and reconnect, then call inspect_android_package with {"path": "/absolute/path/aegis-v3.4.3.apk"}. In npm 6.4.0 this tool takes only path and returns the complete result. Repository main adds an optional detail: "summary" plus a paged manifest view through inspect_analysis_view; those are not in 6.4.0, so check your connected server’s tool schema before using them (android-analysis.md).

Step 3: Search, read and trace code

The showcase question was “how is the six-digit login code computed?”. The same pattern works for any feature.

Search class names:

rea search-android-classes /absolute/path/aegis-v3.4.3.apk Totp

The showcase got three matches for the case-sensitive query Totp: otp.TotpInfo, ui.views.TotpProgressBar and importers.TotpAuthenticatorImporter (package prefix shortened). Only the first looks like a calculation.

List the class’s members and overloads:

rea inspect-android-class /absolute/path/aegis-v3.4.3.apk com.beemdevelopment.aegis.otp.TotpInfo

This returns fields, methods and overload indices without generating source. For TotpInfo, getOtp() is overload 0 and getOtp(long) is overload 1.

Read one method:

rea inspect-android-method /absolute/path/aegis-v3.4.3.apk \
  com.beemdevelopment.aegis.otp.TotpInfo getOtp --overload-index 1

Why pass --overload-index: REA refuses an ambiguous method request instead of silently picking the first match. Overload indices belong to this artifact and engine; do not reuse them across builds.

Find callers:

rea trace-android-references /absolute/path/aegis-v3.4.3.apk \
  com.beemdevelopment.aegis.otp.OtpInfo --method-name getOtp

The showcase found ui.views.EntryHolder.getOtp and ui.MainActivity.copyEntryCode as callers: one displays the code, one copies it.

How to read these results:

  • Decompiled Java is a derived representation. A complete text result is not proof of complete semantics.
  • References are static incoming relationships, not observed runtime calls. Reflection and dynamic loading stay unverified.
  • Static initializers (<clinit>) come back as smali, because Java decompilation can scatter their instructions into field declarations.
  • Release builds are often shrunk and renamed. In Aegis, the OTP generator surfaced under kotlin.ExceptionsKt, so follow calls from a known entry point rather than trusting class names.

Step 4: Decode resources with Apktool

JADX answers code questions; resource tables answer “what does the UI say?” and “which locales and versions ship?”.

export REA_APKTOOL_COMMAND=/absolute/path/apktool   # optional; otherwise PATH
rea inspect-apktool-client
rea decode-android-resources /absolute/path/aegis-v3.4.3.apk
rea decode-android-resources /absolute/path/aegis-v3.4.3.apk --locale de
rea decode-android-resources /absolute/path/aegis-v3.4.3.apk --no-strings

Why each one:

  • inspect-apktool-client runs apktool --version and reports which launcher was selected. Run it first so a missing tool fails fast.
  • The default decode returns apktool.yml metadata as Apktool printed it, the decoded manifest (bounded at 512 KiB) and the default strings table (up to 2000 entries), plus a locales list.
  • --locale de projects res/values-de/strings.xml instead. Regional locales such as pt-BR map to Android’s values-pt-rBR.
  • --no-strings skips string projection when you only need metadata and the manifest.

REA runs Apktool with --no-src, so no smali is produced here; code belongs to the JADX tools. Strings are projected verbatim: escaped entities stay escaped, and entries with nested markup are counted as unparsed rather than flattened.

Step 5: Acquire an installed app with ADB

Sometimes the APK you need is the one installed on a device: for example, the F-Droid build of Aegis on your own test phone, including any split APKs.

Authorization first. Only connect devices you own or are explicitly authorized to examine, and only pull apps and data you have the right to analyze. Several ADB operations change device state. Read REA’s DISCLAIMER.md before you start.

export REA_ADB_PATH=/absolute/path/to/adb   # optional; otherwise PATH
rea inspect-adb-client
rea list-adb-devices
rea inspect-adb-device emulator-5554

inspect-adb-client only probes the binary and never contacts the adb server. list-adb-devices returns each serial with its state, transport and an inferred emulator/physical kind together with the basis for that inference. Use the serial exactly as listed; emulator-5554 is just the common default emulator name.

Find and pull the package:

rea resolve-adb-packages emulator-5554 aegis
rea inspect-adb-package emulator-5554 com.beemdevelopment.aegis
rea pull-adb-package emulator-5554 com.beemdevelopment.aegis --output-directory /tmp/acq
rea inspect-android-package /tmp/acq/com.beemdevelopment.aegis/base.apk

Why this order: resolve-adb-packages does a case-insensitive substring search so you get the exact package name; inspect-adb-package shows versions, installer and flags so you know which build you are about to pull; pull-adb-package collects the complete split set from pm path and digests every file. --output-directory may already exist, but the package-named subdirectory REA creates inside it (here /tmp/acq/com.beemdevelopment.aegis) must not, so a completed pull is never overwritten.

Read-only vs. device-mutating. Commands such as list-adb-processes, read-adb-logcat, capture-adb-screen and list-adb-directory only read. These change the device and are never run implicitly:

rea install-adb-package emulator-5554 /tmp/app.apk --replace
rea start-adb-app emulator-5554 com.example.app
rea stop-adb-app emulator-5554 com.example.app
rea uninstall-adb-package emulator-5554 com.example.app

uninstall-adb-package removes the package and its data for user 0. Prefer an emulator for anything in this group.

REA builds every adb call from a fixed argument vector; no tool accepts an arbitrary shell command, and UI input (input, monkey) is deliberately not exposed.

Step 6: Triage a Flutter app

Flutter apps keep most logic in Dart, compiled ahead of time into lib/<abi>/libapp.so. JADX shows you little more than the Android embedding. REA’s Flutter tools parse the APK directly, with no engine, no execution and no network lookups.

Version note. identify-flutter-build and inspect-dart-aot (MCP identify_flutter_build / inspect_dart_aot) are on repository main but are not in the npm 6.4.0 release: its command list does not include them. Until a later release ships them, you need a source build of main; check npm view rea-agents dist-tags.latest and the released package and main section first.

rea identify-flutter-build /absolute/path/flutter-gallery.apk
rea inspect-dart-aot /absolute/path/flutter-gallery.apk
rea inspect-dart-aot /absolute/path/flutter-gallery.apk --abi armeabi-v7a

What you get:

  • flutter_detected, per-ABI libflutter.so digests, GNU build IDs and any labeled version strings
  • the Dart snapshot hash, read 20 bytes after the f5 f5 dc dc snapshot magic in libapp.so
  • from inspect-dart-aot: the four _kDart*Snapshot* sections and a string-pool projection listing package: URIs (a dependency inventory) and dart: SDK libraries

inspect-dart-aot defaults to arm64-v8a, then x86_64, x86 and armeabi-v7a. REA’s own verification record for the Flutter Gallery 2.9.2 release APK reports the agreeing snapshot hash 65817c30a78bb44c3dc3771876b6010a on every ABI, and no labeled Dart version, which is expected for a release engine.

Why the snapshot hash matters: it is the key that maps to a Dart SDK release through an external dataset such as the one reFlutter maintains. REA deliberately performs no hash-to-SDK lookup. Once you know the SDK version, you can pick a snapshot parser for that layout, or continue with native analysis of libapp.so (/en/rea-ghidra-native-binary/).

The string pool is a byte-pattern projection, not deserialized classes. Treat class_like_tokens as hints that include incidental matches.

Troubleshooting

Java starts but the bridge fails. You are probably on a JRE. Point JAVA_HOME at a full JDK and confirm jdk.compiler appears in java --list-modules.

“Ambiguous method” error. Run inspect-android-class to list overloads, then add --overload-index. For overloaded methods, trace-android-references cannot select a specific overload and reports that boundary as unsupported.

Large APK times out or runs out of heap. Operations have a 120-second deadline and decompilation 90 seconds. Raise the heap with export REA_JADX_HEAP_MIB=8192 or JAVA_TOOL_OPTIONS=-Xmx8g.

You have an .aab. An Android App Bundle is not an APK, and these tools do not treat it as one. Work from the APK the project publishes, or install the app on a test device through its normal channel and pull the installed split set with pull-adb-package.

Apktool reports capability_unavailable. Set REA_APKTOOL_COMMAND to the launcher’s absolute path and rerun rea inspect-apktool-client.

pull-adb-package refuses to run. The package subdirectory inside --output-directory already exists from an earlier pull. Choose a new output directory; REA does not overwrite earlier evidence.

No snapshot hash. If flutter_detected is false, coverage is complete and the app is not Flutter. If it is true but the hash is null, check snapshot_hash_candidates: disagreeing records are kept rather than resolved by guessing.

Next steps