Skip to main content

Orchestrate Android reverse engineering tools into a unified security analysis pipeline

Project description

batuta

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.


Features

  • 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 merge or --auto-merge
  • Decompilation — run jadx and/or apktool via batuta apk decompile or batuta 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 --json output for piping into jq, grep, or custom tooling
  • Library-first architecture — core logic is importable independently of the CLI

Requirements

Python

  • Python >= 3.14

External Tools

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.

Configuring APKEditor

APKEditor ships as a JAR file. Batuta resolves it in this order:

  1. APKEDITOR_JAR environment variable (points to the .jar or its parent directory)
  2. ~/.batuta/config.jsonapkeditor_path
  3. Executable wrapper called APKEditor somewhere on PATH

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.


Installation

Install from source with uv:

git clone https://github.com/luca-regne/batuta
cd batuta
uv sync

Or with pip:

pip install -e .

Quick Start

# 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 --json

Command Reference

batuta
├── 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

Common Options

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.

Examples

# 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 android

Library Usage

The 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}")

Architecture

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 into core/
  • All subprocess invocations go through utils/process.py
  • All external tool requirements are checked at command entry via utils/deps.py

Development

# 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 --help

Release Process

This project uses automated release management with conventional commits.

For Maintainers

Creating a Release:

# Patch release (bug fixes): 1.0.0 -> 1.0.1
./scripts/release.sh patch

# Minor release (new features): 1.0.0 -> 1.1.0
./scripts/release.sh minor

# Major release (breaking changes): 1.0.0 -> 2.0.0
./scripts/release.sh major

The script will:

  1. Validate working directory is clean
  2. Run lint and type checks
  3. Bump version in pyproject.toml
  4. Update CHANGELOG.md
  5. Create git commit and tag
  6. Push to GitHub

GitHub Actions will then:

  1. Run full validation suite
  2. Build package
  3. Publish to PyPI
  4. Create GitHub Release with artifacts

For Contributors

All commits must follow Conventional Commits format.

See COMMIT_CONVENTION.md for details.

Setup pre-commit hooks:

pip install pre-commit
pre-commit install --hook-type commit-msg

Required tools for releases:

# macOS
brew install git-cliff

# Linux
cargo install git-cliff

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

batuta-2.0.0.tar.gz (59.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

batuta-2.0.0-py3-none-any.whl (66.7 kB view details)

Uploaded Python 3

File details

Details for the file batuta-2.0.0.tar.gz.

File metadata

  • Download URL: batuta-2.0.0.tar.gz
  • Upload date:
  • Size: 59.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for batuta-2.0.0.tar.gz
Algorithm Hash digest
SHA256 93300177da5044421eafe3179ec6dce85f067f39e1c834d2e66160fd86e458ec
MD5 f49f6fc5c923087f7c781ffe4ab5c35c
BLAKE2b-256 3d94571afa553ee83d0889e3dfe88721e08918050ebb252c4943e54d4cf91ac5

See more details on using hashes here.

File details

Details for the file batuta-2.0.0-py3-none-any.whl.

File metadata

  • Download URL: batuta-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 66.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for batuta-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 91e6e1e22131db7a304d1041b80459b0d4511f98936eb158a82b159cdde0ae20
MD5 18f8d85a72cbe1a7749729bee9ff30cd
BLAKE2b-256 09ef988ec10ff9754d0d85ccfa631e29276155327cffff0d34ccb5f8c361345f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page