Pure Rust APK pack / unpack / patch tool — Apktool-like workflow on the Androguard crate suite.
- Code format: dex-txt (not smali)
- Build output: signed APK by default (v1 + v2 + v3)
- Resources: pure-Rust
resources.arscrebuild (optional aapt2) - Containers:
.apk,.xapk, and.apkmround-trip
Androguard dependencies (apkparser, axml-parser, dex-parser, dex-bytecode)
are pulled from GitHub — no sibling checkouts required.
cargo build -p apk-patch-cli --release
./target/release/apk-patch --help
# or without installing:
cargo run -p apk-patch-cli -- --helpGlobal flags: -q / --quiet, -v / --verbose.
# 1. Decode APK (or XAPK / APKM) into an editable project
apk-patch decode app.apk -f -o out/
# 2. Edit what you need:
# - out/dex/**/*.dex.txt (bytecode)
# - out/AndroidManifest.xml (text)
# - out/res/** (resources)
# - out/apkpatch.yml (sdk/version metadata)
# 3. Rebuild → signed package at out/dist/<apkFileName>
apk-patch build out/ -fAliases: d = decode, b = build.
# Unsigned (Apktool-like)
apk-patch build out/ -f --no-sign
# XAPK / APKM: decode preserves splits under container/; build repacks .xapk/.apkm
apk-patch decode Facebook.xapk -f -o out-fb/
apk-patch build out-fb/ -f
# → out-fb/dist/Facebook.xapk (rebuilt base + original splits)# Manifest only
apk-patch d app.apk -o man/ -f --only-manifest
# Fast inspect: raw .dex + raw resources.arsc
apk-patch d app.apk -o raw/ -f -s -r
# Disassemble every classes*.dex as dex-txt
apk-patch d app.apk -o full/ -f -a# Debuggable + cleartext traffic config
apk-patch b out/ -f --debuggable --net-sec-conf
# Write intermediates only (no final ZIP)
apk-patch b out/ -f --no-apk
# → out/build/apk/{AndroidManifest.xml,resources.arsc,classes.dex,…}
# Optional aapt2 resource path
apk-patch b out/ -f --use-aapt2 --aapt "$ANDROID_HOME/build-tools/36.1.0/aapt2"One-shot: decode → pack libgoauld_agent.so + early-load ContentProvider → rebuild & sign.
# Default agent: $GOAULD_AGENT_SO or ../arm_goauld/dist/android-arm64/libgoauld_agent.so
apk-patch inject-goauld app.apk -o app-goauld.apk
apk-patch inject-goauld app.apk \
--agent /path/to/libgoauld_agent.so \
-o app-goauld.apk \
--keep-project /tmp/app-goauld-projectSmoke (needs adb + agent .so):
./scripts/build-hello.sh
./scripts/smoke-inject-goauld.shIn-memory bindings live in crates/apk-patch-py (module name apk_patch).
They decode / build from bytes — no temp project on disk required.
Requires a Rust toolchain and maturin:
pip install maturin
cd crates/apk-patch-py
maturin develop --release
# or: maturin build --release → wheel under target/wheels/| Function | Role |
|---|---|
apk_patch.decode(apk_bytes, …) |
APK → in-memory project (files dict) |
apk_patch.build(files, …) |
Project dict → APK bytes |
apk_patch.inject_goauld(apk_bytes, agent_so, …) |
Inject arm64 agent + rebuild |
decode returns:
{
"files": dict[str, bytes], # paths like "project/AndroidManifest.xml"
"project_root": str, # usually "project"
"entry_count": int,
"dex_class_count": int,
}Round-trip (decode → tweak → build):
from pathlib import Path
import apk_patch
apk = Path("app.apk").read_bytes()
proj = apk_patch.decode(apk, apk_name="app.apk")
files = proj["files"]
root = proj["project_root"] # "project"
# Edit the text manifest in memory
manifest_key = f"{root}/AndroidManifest.xml"
xml = files[manifest_key].decode("utf-8")
xml = xml.replace('android:debuggable="false"', 'android:debuggable="true"')
files[manifest_key] = xml.encode("utf-8")
# Rebuild (signed by default)
out = apk_patch.build(files, project_root=root, sign=True)
Path("app-patched.apk").write_bytes(out)Skip sources / resources (faster inspect):
proj = apk_patch.decode(
apk,
apk_name="app.apk",
no_src=True, # keep raw .dex, no dex-txt
no_res=True, # keep raw resources.arsc
)
# or: only_manifest=TrueInject goauld agent:
agent = Path("libgoauld_agent.so").read_bytes()
out = apk_patch.inject_goauld(apk, agent, apk_name="app.apk", sign=True)
Path("app-goauld.apk").write_bytes(out)List / patch a dex-txt class:
proj = apk_patch.decode(apk, all_src=True)
files = proj["files"]
root = proj["project_root"]
# Find a class file
keys = [k for k in files if k.endswith(".dex.txt")]
sample = keys[0]
text = files[sample].decode("utf-8")
# …edit mnemonic lines…
files[sample] = text.encode("utf-8")
out = apk_patch.build(files, project_root=root)Notes:
- Paths inside
filesmust stay underproject_root(same layout as a CLI decode). - Python build uses the pure-Rust ARSC path (
rebuild_resources=True); it does not spawn aapt2. - Signing uses the same debug keystore as the CLI unless you pass
sign=False.
After decode:
out/
├── apkpatch.yml # metadata (sdk/version, packageFormat, …)
├── AndroidManifest.xml # text XML (edit this)
├── dex/ # classes.dex → *.dex.txt
├── dex_classes2/ # multi-dex (if present)
├── res/
├── assets/
├── lib/
├── container/ # XAPK/APKM splits + manifest.json (if any)
├── original/ # binary originals for rebuild fallback
└── unknown/
After build:
out/
├── build/apk/ # packed entry tree (--no-apk stops here)
└── dist/
└── app.apk # or .xapk / .apkm when decoded from a container
| Command | Alias | Purpose |
|---|---|---|
decode |
d |
APK / XAPK / APKM → editable project |
build |
b |
Project → APK (or XAPK/APKM); signed by default |
inject-goauld |
Pack libgoauld_agent.so + early-load provider |
|
install-framework |
if |
Install a framework APK into the cache |
clean-frameworks |
cf |
Remove cached frameworks |
list-frameworks |
lf |
List cached frameworks |
publicize-resources |
pr |
Set SPEC_PUBLIC on entries in a resources.arsc |
| Flag | Description |
|---|---|
-f, --force |
Overwrite existing output directory |
-o, --output |
Output project directory |
-s, --no-src |
Skip dex-txt; copy raw .dex |
-r, --no-res |
Skip resource decode; keep raw resources.arsc |
-a, --all-src |
Disassemble all root .dex files |
-j, --jobs |
Parallel dex-txt workers |
-p / -t |
Framework path / tag |
--no-assets |
Skip assets/ |
--only-manifest |
Decode only AndroidManifest.xml |
--no-debug-info |
Omit debug annotations in dex-txt |
--keep-broken-res |
Keep going if resource decode fails |
--res-resolve-mode |
default | greedy | lazy |
--ignore-raw-values |
Skip opaque raw Res_value dumps |
| Flag | Description |
|---|---|
-f, --force |
Force rebuild even if output looks up-to-date |
-o, --output |
Output path (default: dist/<apkFileName>) |
-j, --jobs |
Parallel dex-txt assembly |
--debuggable |
Set android:debuggable="true" |
--net-sec-conf |
Inject permissive network security config |
--copy-original |
Pack original/ manifest + META-INF |
--no-apk |
Intermediates only (build/apk/) |
--no-sign |
Leave unsigned |
--use-aapt2 / --aapt / --no-crunch |
Optional aapt2 resource path |
--v1-signing-enabled / --v2-… / --v3-… |
Toggle signing schemes |
SDK / version from apkpatch.yml are applied to the text manifest on build.
Legacy apktool.yml is still accepted on load.
apk-patch if framework-res.apk -p ~/.local/share/apktool/framework
apk-patch lf -p ~/.local/share/apktool/framework
apk-patch cf -p ~/.local/share/apktool/framework --all
apk-patch pr path/to/resources.arscDefault framework dirs (when -p omitted): macOS ~/Library/apktool/framework, Linux XDG data, Windows LocalAppData. An embedded minimal 1.apk is written automatically if missing.
Additive APIs for embedding and browsers:
apk-patch-vfs—Vfs,StdFs(native),MemVfs(in-memory)decode_apk_vfs/build_project_vfsdecode_apk_bytes/build_project_bytes/inject_goauld_bytes
Cargo features on apk-patch-core (defaults preserve CLI behavior):
| Feature | Default | Notes |
|---|---|---|
parallel |
on | rayon in dex-txt; disable for WASM |
aapt2 |
on | host aapt2 spawn; disable for WASM |
native-fs |
on | StdFs / walkdir |
goauld |
on | host agent path helpers |
WASM / browser: depend with default-features = false. Signing uses an embedded debug key on wasm32.
- DEX — edit
dex/**/*.dex.txt. See DEX-TXT.md. Add/remove a class by adding/deleting a.dex.txtfile. - Manifest — edit root
AndroidManifest.xml(text). Build encodes it to binary AXML. - Resources — edit
res/values*/*.xmland layouts; keeppublic.xmlids stable when possible. - Fallback — if resource rebuild fails, the tool packs
original/resources.arscand warns.
| Path | Purpose |
|---|---|
scripts/build-hello.sh |
Build testapps/hello → hello.apk |
scripts/smoke-roundtrip.sh |
Decode/build roundtrip |
scripts/smoke-inject-goauld.sh |
Inject + install + check logcat |
scripts/regen-goauld-loader.sh |
Rebuild embedded loader DEX |
testapps/hello/ |
Minimal Android app for smoke tests |
| Apktool | apk-patch | |
|---|---|---|
| Language | Java | Rust |
| Code format | smali | dex-txt |
| Metadata file | apktool.yml |
apkpatch.yml (loads legacy apktool.yml) |
| Build output | unsigned | signed by default |
| Resources rebuild | aapt2 | pure-Rust ARSC (aapt2 via --use-aapt2) |
| Split packages | limited | XAPK / APKM round-trip |
Apache-2.0 — see LICENSE.
