Skip to main content

README

GENERAL INFO

Project: Library to patch apk (inject frida gadget)
Author: MadSquirrel
License: GNU General Public License v3.0
Version: see apkpatcher.conf.VERSION (or apkpatcher --version)

GOAL

Library to patch apk (inject frida gadget) this code is inspired by this project :https://github.com/badadaf/apkpatcher.

The improvements added by this fork are the following:

  • modification of xml files such as AndroidManifest without extracting the resources. Extracting the resources usually prevents to rebuild the apk.
  • Use as an API
  • Installation as a package

This project has received funding from the OSINT Got Talent program of EPIEOS. Sponsor

USAGE

To use as library you just need to:

import apkpatcher
patcher = apkpatcher.Patcher(<apk_path>, <version_buildtools>, <sdktools>)
patcher.set_arch(<arch>)
patcher.patching(<path_gadget>, output_file=<output_file>, user_certificate=<true|false>)

To use as a program you just need to:

apkpatcher -a <apk_path> -g <path_gadget> -s <sdktools> -b <version> -r <arch> -o <output_file>

You could use it as docker with this command line:

docker run --rm -v .:/pwd -it madsquirrels/apkpatcher -a base.apk --download_frida_version 16.3.3

Editing the APK mid-patch inside Docker (-p/--pause)

-p/--pause stops apkpatcher right before repackaging so you can hand-edit the unpacked APK tree (smali, resources, manifest, ...). That unpacked tree lives under /tmp (Python's tempfile default) -- inside the container, that's invisible from the host unless you also mount a volume on /tmp:

mkdir -p ./work
docker run --rm -it \
  --user "$(id -u):$(id -g)" \
  -v .:/pwd \
  -v ./work:/tmp \
  madsquirrels/apkpatcher \
  -a base.apk -p -o base-patched.apk

When apkpatcher logs You can modify the apk here: /tmp/tmpXXXXXXXX, that same directory is ./work/tmpXXXXXXXX on the host -- edit files there, then press Enter in the container's terminal to resume; it repackages and signs the APK with your changes included. Without the second -v ./work:/tmp, that directory only exists inside the container and disappears with it, so there's nothing on the host to edit.

--user "$(id -u):$(id -g)" matters: the image otherwise runs as a fixed non-root user, which generally won't match whoever owns ./work on the host, so it can't write the extracted files there. Running the container as your own uid/gid instead means the plain mkdir above (unmodified, default permissions) already has the right owner -- no chmod needed. Verified end-to-end: a completely default-permissions mkdir, --user "$(id -u):$(id -g)", --pause, editing from the host, and resuming all worked with zero permission changes.

For more information please visit https://apkpatcher.ci-yow.com/

Quick examples

Each example uses only the flags actually required to work: -b is never required (build-tools version auto-detects from sdktools); -s is only needed when something requires signing/build-tools (not --only-unpack); and -g with a plain, non-arch-suffixed filename needs -r to say which single architecture that file is for -- otherwise apkpatcher looks for gadget_arm.so/gadget_arm64.so/... and finds none of them.

# Simple Frida injection
apkpatcher -a app.apk -s /opt/android-sdk -g gadget.so -r arm64 -o app-frida.apk

# Simple community patch
apkpatcher -a app.apk -s /opt/android-sdk --patch flutter-cert-pinning-bypass -o app-patched.apk

# Simple unpack (only -a is needed -- no signing/build-tools involved)
apkpatcher -a app.apk --only-unpack ./unpacked

# Simple repack (from a tree an earlier --only-unpack produced)
apkpatcher -a app.apk -s /opt/android-sdk --only-repack ./unpacked

These same four examples are also shown in apkpatcher --help's epilog.

EXEMPLE

import apkpatcher
patcher = apkpatcher.Patcher(<apk_path>, <version_buildtools>, <sdktools>)
# not mandatory
patcher.add_network_certificate(<custom_certificate>)
patcher.set_arch(<arch>)
patcher.pause = <True|False>
# end not mandatory
patcher.patching(<path_gadget>, output_file=<output_file>, user_certificate=<true|false>)

INSTALL

sudo python3 -m pip install .

Requirement

setup your sktools as follow:

CLI REFERENCE

apkpatcher --help groups every flag the same way this table does. Run it for the live, authoritative list (including which flags are [required]); this table is a companion overview, organized by intent.

apk : target APK, SDK, and output

Required flags come first in this table, matching --help's own ordering.

Flag Purpose
-a/--apk PATH APK to patch (required)
-s/--sdktools PATH Android SDK root (required unless $ANDROID_SDK_ROOT is set, or --only-unpack is used instead)
-m/--multiple_split PATH... Split APKs belonging to the same app
-b/--version_buildtools VERSION build-tools version under sdktools/build-tools/ (auto-detected if omitted)
-o/--output-file PATH Output APK path

Library equivalents: Patcher(apk, version_buildtools, sdktools, ...), patching(output_file=..., splits_apk=[...]).

library injection : inject a native library (e.g. a Frida gadget)

-g/--gadget isn't Frida-specific: it injects any native library. -r and --entrypoint describe whatever's injected here, Frida or not.

Flag Purpose
-g/--gadget PATH Native library (.so) to inject
-r/--arch {arm,arm64,x86,x64} Target architecture -- required to disambiguate a single, non-arch-suffixed library file (e.g. -g gadget.so); without it apkpatcher looks for gadget_arm.so/gadget_arm64.so/... instead
--entrypoint CLASS Class to inject the library loader into (auto-detected if omitted)

Library equivalents: the gadget path as patching()'s first positional argument, Patcher.set_arch(), patching(entrypoint=...).

frida : subsection of library injection: Frida-specific gadget config

These only make sense once a gadget has been injected via -g/--download_frida* above.

Flag Purpose
--download_frida_version VERSION Download a specific frida-gadget version instead of supplying -g
--download_frida Download whichever frida-gadget version matches the frida Python package installed locally
--frida-no-wait Gadget config: app starts immediately instead of waiting for a Frida client to attach
--frida-script PATH Embed a local .js script the gadget auto-loads on startup, no network interaction needed
--frida-config PATH Supply a full custom gadget config JSON (mutually exclusive with the two flags above)

Library equivalents: Patcher.set_use_download_frida(), Patcher.set_use_download_frida_from_installed(), Patcher.set_frida_no_wait(), Patcher.set_frida_script(), Patcher.set_frida_config().

network certificates

Flag Purpose
-e/--enable-user-certificates Let the app trust user-installed CA certificates
-c/--custom-certificate PATH Install a custom network certificate inside the APK

Library equivalents: patching(user_certificate=True), Patcher.add_network_certificate().

manifest & dex

Flag Purpose
--enable-debug Set android:debuggable="true"
--keep-debug-info Keep .line/.local/.parameter smali debug directives (off by default)
--disable-dex-split Fail on a 65536-method-reference overflow instead of auto-splitting into a second dex
--add-permissions PERM... Add one or more <uses-permission> entries

Library equivalents: Patcher.set_debug(), Patcher.keep_debug_info(), Patcher.disable_dex_split(), patching(permissions=[...]).

community patches

Community patches are shareable, reviewed --plugin-style scripts fetched from a static repository (default: https://apkpatcher-patches.ci-yow.com, configured in ~/.config/apkpatcher/config.toml).

Flag Purpose
--plugin PATH Run a local plugin script (repeatable)
--patch ID Fetch and run a community patch by id (repeatable)
--list-patches List available patches, then exit
--search-patches QUERY Search available patches by name/description/tags, then exit
--refresh-patches Bypass the local patch-index cache
--repo URL Add an extra patch repository for this run
--config PATH Use a different patch-repository config file
-y/--yes Skip the trust-on-first-use prompt
apkpatcher --list-patches
apkpatcher --search-patches flutter
apkpatcher -a app.apk -s /opt/android-sdk --patch flutter-cert-pinning-bypass -o out.apk

Library equivalents: Patcher.add_plugin()/Patcher.set_plugin() for --plugin; for --patch, apkpatcher.patchrepo.fetch_all(repos) to fetch the index and apkpatcher.patchrepo.resolve_and_apply_patches(patcher, patch_ids, all_patches, satisfied_capabilities) to fetch/verify/apply one or more patches by id, without going through the CLI at all:

from apkpatcher import Patcher, patchrepo

patcher = Patcher(<apk_path>, <version_buildtools>, <sdktools>)
repos = patchrepo.load_config()
all_patches = patchrepo.fetch_all(repos)
patchrepo.resolve_and_apply_patches(
    patcher, ["flutter-cert-pinning-bypass"], all_patches,
    satisfied_capabilities=set(), auto_trust=True,
)
patcher.patching(output_file=<output_file>)

packaging

Flag Purpose
--compression-level N Output zip compression level, 0-9 (default: 9)
--compression-method {STORED,DEFLATED} Output zip compression method (default: DEFLATED)

Library equivalent: Patcher.set_compression(zipfile.ZIP_DEFLATED, 9) (takes the raw zipfile module constants directly).

unpack/repack

Flag Purpose
--only-unpack PATH Extract without repackaging (only -a is needed -- no signing/build-tools involved)
--only-repack PATH Repackage a tree a prior --only-unpack produced

Library equivalents: Patcher.unpack_to(target_dir) and Patcher.repack_from(source_dir, output_apk).

misc

Flag Purpose
-v/--verbosity {0,1,2,3} Logging verbosity: 3=DEBUG, 2/unset=INFO, 0-1=ERROR
-p/--pause Pause right before repackaging, to hand-edit the unpacked tree (inside Docker, mount a volume on /tmp -- see above)
-V/--version Print the installed apkpatcher version
--download-jars Pre-download the bundled smali/baksmali jars, then exit
-j/--nb-jobs N Parallel worker count for dex processing (default: 4)

Library equivalents: apkpatcher.new_logger(logging.DEBUG), Patcher.pause (a plain property), apkpatcher.conf.VERSION, the nb_jobs constructor argument.

signing : certificate for signing the output APK

Kept last: distinct from the network certificates above (which the app trusts at runtime), this is the keystore apkpatcher signs the output APK with.

Flag Purpose
--keycertificate/--keyalias/--keypass Use a specific keystore to sign the output (all three together)
--keep-keycertificate Keep an auto-generated signing keystore instead of deleting it afterward
--v4 PATH Also produce a v4 signature file

Library equivalents: Patcher.add_certificate(cert, alias, pass), Patcher.keep_certificate(), Patcher.enable_v4_signature().

CHANGELOG

Download files

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

Source Distribution

apkpatcher-0.1.42.tar.gz (63.4 kB view details)

Uploaded Source

Built Distribution

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

apkpatcher-0.1.42-py3-none-any.whl (59.1 kB view details)

Uploaded Python 3

File details

Details for the file apkpatcher-0.1.42.tar.gz.

File metadata

  • Download URL: apkpatcher-0.1.42.tar.gz
  • Upload date:
  • Size: 63.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.0

File hashes

Hashes for apkpatcher-0.1.42.tar.gz
Algorithm Hash digest
SHA256 5af991ead0076f1582864630fb57be91e7ef133a46c146dcaff42a581df6809f
MD5 638e6d6f5df26314be71a1dcd3b62965
BLAKE2b-256 6ff317674ea6fa317537a1fa19a07d848500f8f612beea31b047b91011841657

See more details on using hashes here.

File details

Details for the file apkpatcher-0.1.42-py3-none-any.whl.

File metadata

  • Download URL: apkpatcher-0.1.42-py3-none-any.whl
  • Upload date:
  • Size: 59.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.0

File hashes

Hashes for apkpatcher-0.1.42-py3-none-any.whl
Algorithm Hash digest
SHA256 26bf9b0845946442ad24cd99a9e2ab764dadf1676fdd5a3713b55af427ddf555
MD5 48da8a3f8a44da2dbcfb60c930b2b688
BLAKE2b-256 e3542c671a5d7ed1f37fb0198abee45f01e15996ab60edd7c4283e2adca5d43c

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.43

2 files

This release

0.1.42 This release

2 files

0.1.41

2 files

0.1.40

2 files

0.1.39

2 files

0.1.37

2 files

0.1.36

2 files

0.1.35

2 files

0.1.34

2 files

0.1.33

2 files

0.1.32

2 files

0.1.31

2 files

0.1.30

2 files

0.1.29

2 files

0.1.28

2 files

0.1.27

2 files

0.1.26

2 files

0.1.25

2 files

0.1.24

2 files

0.1.23

2 files

0.1.22

2 files

0.1.21

2 files

0.1.20

2 files

0.1.19

2 files

0.1.18

2 files

0.1.17

2 files

0.1.16

2 files

0.1.15

2 files

0.1.14

2 files

0.1.13

2 files

0.1.12

2 files

0.1.11

2 files

0.1.10

2 files

0.1.9

2 files

0.1.8

2 files

0.1.7

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

0.0.15

2 files

0.0.14

2 files

0.0.13

2 files

0.0.12

2 files

0.0.11

1 file

0.0.10

2 files

0.0.9

1 file

0.0.8

2 files

0.0.7

1 file

0.0.6

2 files

0.0.5

3 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page