Batuta is a Brazilian Portuguese word that means "baton" in English. Like a conductor's baton that directs an orchestra, batuta aims to be the main tool to orchestrates Android reverse engineering.
batuta is a Python CLI for static Android application analysis — designed for penetration testers, bug bounty hunters, and malware analysts. It wraps battle-tested RE tools (apktool, jadx, adb, APKEditor) behind a clean, composable interface.
- APK pulling — pull APKs by package name, app name, or filter pattern
- Split APK support — automatically detects split packages, pulls every part, and merges them via
batuta apk mergeor--auto-merge - Decompilation — run
jadxand/orapktoolviabatuta apk decompileorbatuta apk pull --decompile - APK patching — rebuild, align, sign, and optionally install via
batuta apk patch - Framework detection — detect cross-platform frameworks (Flutter, React Native, Xamarin, Cordova, Unity) via
batuta analyze framework - Interactive selection — choose from multiple matches when searching (prompted automatically)
- Scriptable by design — every command supports
--jsonoutput for piping intojq,grep, or custom tooling - Library-first architecture — core logic is importable independently of the CLI
- Python >= 3.14
These must be installed and available on your PATH:
| Tool | Purpose | Install |
|---|---|---|
adb |
Android Debug Bridge — device communication | Android SDK Platform Tools |
apktool |
APK decoding, smali disassembly | Apktool Webiste |
jadx |
Java/Kotlin decompilation | Jadx GitHub Repository |
APKEditor |
Split APK merging | APKEditor Repository |
batuta checks for required tools at command entry and reports clear installation instructions when missing.
APKEditor ships as a JAR file. Batuta resolves it in this order:
APKEDITOR_JARenvironment variable (points to the.jaror its parent directory)~/.batuta/config.json→apkeditor_path- Executable wrapper called
APKEditorsomewhere onPATH
Examples:
# 1. Environment variable (temporary for current shell)
export APKEDITOR_JAR="$HOME/tools/APKEditor/APKEditor.jar"
# 2. Config file (~/.batuta/config.json)
{
"apkeditor_path": "~/tools/APKEditor/APKEditor.jar"
}
# 3. Wrapper script placed on PATH (e.g., /usr/local/bin/APKEditor)
#!/bin/bash
exec java -jar "$HOME/tools/APKEditor/APKEditor.jar" "$@"You can point apkeditor_path to either the JAR file itself or the directory
containing APKEditor.jar. Batuta keeps the pulled split directory intact after
merging, regardless of the resolution method you use.
Install from source with uv:
git clone https://github.com/luca-regne/batuta
cd batuta
uv syncOr with pip:
pip install -e .# List connected devices
batuta device list
# Search for packages by name
batuta apk search google
# Get detailed info about a package
batuta apk info com.example.app
# Pull an APK (supports partial names and filters)
batuta apk pull youtube
# Pull with interactive selection when multiple matches (prompted automatically)
# Pull to a specific directory
batuta apk pull com.example.app --output ./apks/
# Pull and immediately decompile
batuta apk pull com.example.app --decompile
# Standalone decompile from local APK
batuta apk decompile ./apks/com.example.app.apk --java-only
# Merge a split APK directory (keeps original files)
batuta apk merge ./apks/com.example.app --output ./apks/com.example.app.merged.apk
# Detect cross-platform frameworks
batuta analyze framework ./apks/com.example.app.apk
# Framework detection with JSON output
batuta analyze framework ./apks/com.example.app.apk --jsonbatuta
├── analyze
│ └── framework <apk> Detect cross-platform frameworks in APK
│
├── device
│ ├── list List connected ADB devices
│ └── shell [COMMAND...] Open ADB shell (or run command)
│
└── apk
├── list List installed packages
├── search <query> Search packages by name or filter
├── info <query> Show detailed package information
├── pull <query> Pull APK from connected device (optional decompile)
├── merge <dir> Merge split APK folder into a single APK (keeps folder)
├── patch <apktool-dir> Build/align/sign APK from apktool output
└── decompile <apk> Decompile APK to Java and/or smali
| Option | Description |
|---|---|
--device, -d |
Target specific device by ID |
--json, -j |
Output as JSON for scripting |
--system, -s |
Include system packages in search |
--decompile |
(pull) Decompile after pulling (Java + smali) |
--auto-merge |
(pull) Merge split APK folders via APKEditor |
--java-only |
(pull/decompile) Limit to Java (jadx) output |
--smali-only |
(pull/decompile) Limit to smali/resources (apktool) |
Split APK pulls always save the original directory of base/split parts.
Use --auto-merge for immediate merging (the folder stays untouched) or
run batuta apk merge <dir> later. When --decompile is supplied, splits
are merged automatically so jadx/apktool can run without extra steps.
# JSON output for scripting
batuta device list --json | jq '.[0].id'
batuta apk search facebook --json | jq '.[].package_name'
# Target specific device
batuta apk pull com.example.app --device RX8WC00D7JE
# Include system packages
batuta apk list --system --filter androidThe core logic is importable independently of the CLI:
from batuta.core.adb import ADBWrapper
# List devices
adb = ADBWrapper()
devices = adb.list_devices()
for device in devices:
print(f"{device.id}: {device.model} ({device.state})")
# Search packages
packages = adb.search_packages("google")
for pkg in packages:
print(f"{pkg.package_name} v{pkg.version_name}")
# Pull an APK
result = adb.pull_apk("com.example.app", output_dir=Path("./apks"))
print(f"Pulled to: {result.local_path}")src/batuta/
├── cli/ # Typer commands — argument parsing and rich output only
├── core/ # Business logic — importable as a library
├── models/ # Pydantic v2 data models
├── utils/ # Shared utilities: deps checker, output helpers, process wrapper
└── exceptions.py # Typed exception hierarchy
Key architectural constraints:
cli/never contains business logic — only calls intocore/- All subprocess invocations go through
utils/process.py - All external tool requirements are checked at command entry via
utils/deps.py
# Install with dev dependencies
uv sync --group dev
# Lint
ruff check src/batuta/
# Auto-fix lint issues
ruff check src/batuta/ --fix
# Type check
mypy src/batuta/
# Run the CLI
uv run batuta --helpMIT