Files
rea/docs/adb-device-analysis.md
T
fef6f73064 feat(adb): complete ADB device support for Android reverse engineering (#1552)
* feat(adb): acquire installed APK sets from connected Android devices

Add a read-only ADB device-acquisition family over the caller's adb binary:
inspect_adb_client, list_adb_devices, inspect_adb_device, list_adb_packages,
and pull_adb_package, exposed through the CLI and MCP with shared admission,
Evidence provenance (binary, version, serial, transport), and fixed argv
vectors that never reach a shell.

pull_adb_package acquires the complete split set reported by pm path into a
package-named directory that must not exist, digesting every file with
SHA-256 and keeping per-file failure reasons and partial coverage. Device
kind is reported as an inference with its basis; getprop whitelisting keeps
build identity bounded. REA installs nothing and never manages devices or
emulators; the first device query may start the caller's local adb server,
recorded when observed.

Verified against adb 34.0.5 on Linux with an Android 14 (API 34) x86_64
emulator: full 224-package inventory, a real two-APK split set pulled with
byte-exact digests, and a 73.9 MB system-package pull. npm run verify:adb
records the lane; parser goldens come from the same device. READMEs carry
the new row in every supported language.

* feat(adb): complete the adb surface with observation, transfer, and lifecycle tools

Extend the ADB family from five to twenty-four operations, all through
fixed adb argument vectors with no shell exposure:

Observation: read_adb_logcat (bounded dump, count/buffer/pid), inspect_adb_package
(dumpsys package projection), list_adb_processes (ps -A), list_adb_directory
(ls -la with kinds and symlink targets), list_adb_features (hex GL versions),
list_adb_services (AIDL slash-suffixed names), inspect_adb_display (wm size and
density), inspect_adb_window (focus declarations), read_adb_setting (device-side
nulls reported as such), capture_adb_screen (exec-out screencap with PNG
dimensions), collect_adb_bugreport (digested device archive).

Transfer and search: pull_adb_file and push_adb_file (device-side sha256sum
verification, explicit overwrite authority), resolve_adb_packages (substring
package search with exact-match flag — the find step of an easy get).

Lifecycle for dynamic analysis, each declaring mutatesTarget:
install_adb_package and uninstall_adb_package preserving the device's
Success/Failure verdict, start_adb_app (resolve-activity then am start -W),
start_adb_activity (typed extras as --es/--ez/--ei/--el/--ef), and stop_adb_app
(am force-stop).

Verified end-to-end against adb 34.0.5 on Linux with an Android 14 (API 34)
x86_64 emulator: 287 services, 92 features, split-set pulls with byte-exact
digests, verified push/pull roundtrip, sized screen capture, and the full
install → resolve → start → observe-in-ps → force-stop → uninstall lifecycle.
Fixed a v-mode regex incompatibility in the settings key pattern and added
argument descriptions required by the CLI catalog audit.

* fix(adb): quote remote shell operands and protect captured output

* test(adb): cover bugreport destinations containing spaces

---------

Co-authored-by: REA Contributor <[email protected]>
Co-authored-by: morluto <[email protected]>
2026-10-10 23:01:24 +08:00

12 KiB

ADB device inspection and acquisition

REA inspects connected Android devices, reads device state, and transfers files and packages through the caller's adb binary. Emulators, USB devices, and network devices participate alike: REA neither starts nor manages them and never installs platform tools. Every operation composes a fixed adb argument vector — caller values occupy positional arguments only and are quoted when adb forwards them to the device shell.

The adb client may start its local server on first device query; that is adb client behavior, recorded in the tool result when observed. inspect_adb_client probes only the binary and never contacts the server.

Supply tools

Use any Android platform-tools adb (verified with 34.0.5). Select it through the environment:

# Optional: absolute path to the adb binary; without it, adb resolves from PATH.
export REA_ADB_PATH=/absolute/path/to/adb

REA never installs the Android SDK, an emulator, or a system image. Readiness is rea inspect-adb-client: it runs adb version and reports the resolved binary, its version, and where it was selected from. A missing binary reports capability_unavailable with the REA_ADB_PATH remediation rather than attempting an installation.

Answer an analyst question

# Device identity
rea inspect-adb-client
rea list-adb-devices
rea inspect-adb-device emulator-5554
rea inspect-adb-display emulator-5554
rea list-adb-features emulator-5554
rea list-adb-services emulator-5554

# Find and acquire an app (the easy-get flow)
rea resolve-adb-packages emulator-5554 whatsapp
rea list-adb-packages emulator-5554 --scope third_party
rea inspect-adb-package emulator-5554 com.whatsapp
rea pull-adb-package emulator-5554 com.whatsapp --output-directory /tmp/acq
rea inspect-android-package /tmp/acq/com.whatsapp/base.apk

# Device state and files
rea list-adb-processes emulator-5554
rea inspect-adb-window emulator-5554
rea read-adb-setting emulator-5554 global adb_enabled
rea read-adb-logcat emulator-5554 --count 200
rea list-adb-directory emulator-5554 /data/data/com.whatsapp
rea pull-adb-file emulator-5554 /data/local/tmp/notes.txt --output-directory /tmp/acq
rea push-adb-file emulator-5554 /tools/frida-server /data/local/tmp/frida-server
rea capture-adb-screen emulator-5554 --output-directory /tmp/shots
rea collect-adb-bugreport emulator-5554 --output-directory /tmp/bugs

# Package lifecycle and dynamic analysis (device-mutating)
rea install-adb-package emulator-5554 /tmp/app.apk --replace
rea start-adb-app emulator-5554 com.example.app
rea read-adb-logcat emulator-5554 --pid 12345
rea start-adb-activity emulator-5554 android.intent.action.VIEW --data-uri https://example.com
rea stop-adb-app emulator-5554 com.example.app
rea uninstall-adb-package emulator-5554 com.example.app

Equivalent MCP methods use the snake_case names (list_adb_devices, pull_adb_package, resolve_adb_packages, …). Device operations take serial exactly as adb devices reports it. The pulled APK set feeds the existing analysis families directly, for example rea inspect-android-package /tmp/acq/<package>/base.apk.

Operations

Tool Kind What it does
inspect_adb_client read Resolved binary, version, install path; never contacts the server
list_adb_devices read Serial, state, transport, model metadata, inferred emulator/physical kind with basis
inspect_adb_device read Fixed getprop whitelist: build, SDK, patch, fingerprint, product, ABIs, debuggable, ro.kernel.qemu
inspect_adb_display read wm size and wm density as declared
list_adb_features read pm list features (hardware/software features, hex GL versions)
list_adb_services read service list binder services and interfaces
list_adb_processes read ps -A: user, pid, ppid, rss, state, name
inspect_adb_window read dumpsys window focus declarations (what is on screen)
read_adb_setting read One settings get value; device-side null is reported as such
read_adb_logcat read Bounded logcat -d -t dump for one buffer, optional pid filter
list_adb_packages read pm list packages -f with scope; base APK device paths
resolve_adb_packages read Case-insensitive package-name substring search with exact-match flag
inspect_adb_package read dumpsys package projection: versions, install times, installer, flags, permission count
list_adb_directory read ls -la: kinds, permissions, owner, group, sizes, symlink targets
pull_adb_package read Complete split APK set from pm path, each file digested
pull_adb_file read One absolute device path pulled and digested
capture_adb_screen read One screencap -p frame (exec-out, no device-side file) with dimensions
collect_adb_bugreport read Device-generated bugreport zip, digested
push_adb_file mutates device One local file to a device path (e.g. pushing tools into /data/local/tmp); refuses existing targets without explicit overwrite; verifies via the device's own sha256sum
install_adb_package mutates device Local APK via adb install [-r]; preserves the device's Success/Failure verdict
uninstall_adb_package mutates device Removes a package and its data for user 0
start_adb_app mutates device Resolves the package's launcher activity (cmd package resolve-activity --brief) and starts it (am start -n)
start_adb_activity mutates device Starts an intent (am start -a with optional URI, component, and up to 16 typed extras); preserves the activity manager's verdict
stop_adb_app mutates device Force-stops every process of a package, ending a dynamic-analysis run

Interpret the result

  • Inferences carry their basis. Device kind reports kind_basis (serial prefix or USB transport); APK role reports role_basis: file_name; emulator_observed is the build's own ro.kernel.qemu value, null when absent.
  • Device paths are not host paths. base_apk_device_path values live on the device filesystem. Only pulls and captures produce host-local paths, each paired with byte size and SHA-256 digest.
  • Splits are part of the answer. pm path enumerates the base APK and every installed split; pulls cover the whole set. System packages whose APKs keep other names report the unknown role rather than guessing.
  • Partial results keep facts. Failed individual pulls return successful artifacts with coverage: "partial" and per-file failures. Listings that contain unrecognized lines report them as unparsed_lines and mark partial coverage instead of dropping rows.
  • Mutations are explicit. push_adb_file, install_adb_package, and uninstall_adb_package declare mutatesTarget and never run implicitly: pushes refuse existing device files unless overwrite is set and verify the transfer with the device's own sha256sum; installs preserve the device's Failure [...] reason verbatim.
  • Producer identity stays attached. Every result embeds the producing adb client (binary_path, path_source, version, installed_path).

Resource and lifecycle limits

  • Caller values reach adb only as positional arguments in fixed vectors; no tool accepts an arbitrary shell command line, and UI interaction (input, am, monkey) is deliberately not exposed.
  • Per-invocation budgets: client/devices 10-20 s, shell reads 20-60 s with 64 KiB-8 MiB capture caps (logcat 4 MiB, screen 32 MiB), transfers and bugreports 600 s. Exceeded budgets report resource_constraint; stopped commands report provider_timeout.
  • Pull and capture outputs are additive: the destination must not already exist. Re-running a completed pull refuses the existing directory instead of overwriting evidence.
  • The provider holds no long-lived processes; every invocation is one bounded adb run. The adb server belongs to the caller.

UI interaction beyond intent starts (tapping, typing, monkey fuzzing, screen recording) remains a separate lane: candidates such as mobile-device-mcp install driver software on the device and pair with the Frida instrumentation lane (#1359) behind explicit mutation-authority contracts. Network transports beyond already-connected devices (adb connect) are not exposed yet.

Real verification

npm run verify:adb exercises every operation against a live device. It requires adb (or REA_ADB_PATH) and any attached emulator or phone; --serial selects one when several are attached, --pull PACKAGE pulls a real package and verifies the returned digests against the files on disk, and --install-apk PATH drives a full install → resolve → uninstall lifecycle with a lane-owned probe.

Record: verified against adb 34.0.5 on Linux against an Android 14 (API 34) x86_64 emulator — all operations above, a 224-package inventory, 287 services, 92 features, a real two-APK split set pulled with byte-exact digests, a verified push/pull roundtrip, a 1.3 MB screen capture, and the complete package lifecycle; see docs/testing.md for the lane summary.