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.
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:
- https://asthook.ci-yow.com/how.install.html#setup-sdktools install:
- apktool
- pip install -r requirements.txt
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
Metadata
Release files for apkpatcher 0.1.43
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| apkpatcher-0.1.43.tar.gz | 63.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| apkpatcher-0.1.43-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 122.5 kB
Release files / apkpatcher-0.1.43.tar.gz
| Download URL | apkpatcher-0.1.43.tar.gz |
|---|---|
| Size | 63.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9d6e83d230e3ae5546a283ec4adc91528b94525087fa65a48a8b3bb1eda1a40d
|
|
BLAKE2b-256 checksum How to use checksums |
469cc38d1907047cdc750dfb8be06082c4002bccce0efdebcbca3973e6385bf6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.0
|
Release files / apkpatcher-0.1.43-py3-none-any.whl
| Download URL | apkpatcher-0.1.43-py3-none-any.whl |
|---|---|
| Size | 59.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4b63e6eba5cb3dd89140908c41913dfb6b48e84258fd1d66f68eea0daa1eda47
|
|
BLAKE2b-256 checksum How to use checksums |
9aeae1e456b6e586b8f1b93db2912258d5028262af19e1fcf0c034beb3168a42
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.0
|