Stitch
Stitch is a powerful APK patching python library that allows you to inject your own java module into any APK, bundle or XAPK file.
How it works
Will be added soon.
Projects using Stitch:
- WhatsAppPatcher- A patcher for WhatsApp Android app.
- MoovitPatcher- A patcher for Moovit app.
- MakoPatcher- A patcher for 12+ app.
How to use
Installation
You can install Stitch using pip:
# For now, install the test version from TestPyPI
pip install stitch
Basic Usage
Create an Android Gradle project like my smali_generator (Check it out in on of the examples) that generates the java module you want to inject.
Then, use the following code to patch an APK:
from stitch import Stitch
from stitch.common import ExternalModule
from pathlib import Path
with Stitch(
apk_path='./input.apk',
output_apk='./output.apk',
external_modules=[ExternalModule(Path(__file__).parent / './smali_generator',
'com.smali_generator.InitProvider')]
) as stitch:
stitch.patch()
And that's it! Your APK will be patched with the injected module.
The second argument to ExternalModule is the fully qualified name of a ContentProvider in your module.
Stitch registers it in the target's AndroidManifest.xml with a very high initOrder, so its onCreate runs
before the app's own components and your patch is loaded as early as possible.
Registering activities and other components
Stitch merges your module's own AndroidManifest.xml into the target's. Declare the component in the Gradle
module the way you normally would:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application>
<activity
android:name=".SettingsActivity"
android:exported="true"
android:theme="@android:style/Theme.Material.Dialog" />
</application>
</manifest>
and it lands in the patched APK. activity, activity-alias, service, receiver and provider are carried
over from <application>, along with root-level uses-permission, uses-permission-sdk-23, uses-feature and
queries. The target's own <application> and <uses-sdk> attributes are never touched, and a component the
target already declares is not added twice.
Two things to keep in mind:
- Manifest attributes cannot point at your module's resources. The target's manifest is read against the
target's resource table, so
android:label="@string/app_name"orandroid:icon="@mipmap/ic_launcher"would resolve to something unrelated. Stitch drops those attributes with a warning; framework resources (@android:style/...) are kept. Your layouts and drawables are still available at runtime -- see Using your module's resources. - A component with an
<intent-filter>must declareandroid:exported, otherwise Android 12+ refuses to install the patched APK. Stitch raises rather than guessing.
Pass merge_module_manifests=False to Stitch to turn the merge off and register only the provider.
Adding assets
Anything under src/main/assets/ in your module is copied into the patched APK, so you can ship data files
alongside your code and read them at runtime the usual way:
InputStream stream = context.getAssets().open("my_module/config.json");
Each file keeps the compression AGP chose for it, which matters for assets that have to stay uncompressed to be
memory-mapped. If the app you are patching already has a file at the same path, the module's copy wins and Stitch
prints the path it replaced -- handy when you want to swap out an asset of the target app on purpose. When several
modules ship the same path, the last ExternalModule wins.
Assets merge cleanly because AssetManager reads them by path and they have no entry in resources.arsc.
Using your module's resources
Your module's res/ cannot be merged into the target's resource table: both are numbered from 0x7f, so the same
id means different things in each. In a patched 2048, the R.layout.activity_demo of the example module
(0x7f020000) is animator/fragment_close_enter in the target -- setContentView would inflate the wrong thing.
Instead, Stitch injects your module's resource table as its own APK at assets/stitch/<your.package>.apk, and the
module loads it at runtime into a separate Resources. Because that Resources contains only your table, your
generated R constants mean exactly what they meant at compile time. StitchResources in the example module does
this for you:
public class DemoActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Context moduleContext = StitchResources.wrap(this);
View root = LayoutInflater.from(this).cloneInContext(moduleContext).inflate(R.layout.activity_demo, null);
setContentView(root);
Button button = root.findViewById(R.id.demo_button);
}
}
StitchResources.wrap returns a context whose resources are your module's; StitchResources.get returns the
Resources on its own if you want to look something up directly.
Two caveats:
- It builds the
AssetManagerby reflection, since there is no public way to construct one.addAssetPathis a non-SDK API on the unsupported list rather than the blocked one, and it works on Android 16 as of writing. If it is ever blocked, the replacement isResourcesLoader(public since API 30) with the module built under a reserved package id. - Theme attributes (
?attr/...) in your layouts resolve against the theme of the activity, which belongs to the target app. Stick to explicit values or framework attributes.
Pass inject_module_resources=False to Stitch to skip shipping the table.
Contributing
I will be happy if you want to contribute to this project. Feel free to open issues or submit pull requests.
Disclaimer
For educational purpose only or something like that. I am not responsible for any misuse of this software.
Metadata
Release files for stitch 1.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| stitch-1.4.1.tar.gz | 16.2 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| stitch-1.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.5 MB
Release files / stitch-1.4.1.tar.gz
| Download URL | stitch-1.4.1.tar.gz |
|---|---|
| Size | 16.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3b66c7ff23969aeb4f220898eae3b989a56b3a84aeab8f72ca9467bc07f153d4
|
|
BLAKE2b-256 checksum How to use checksums |
78e1eb2bf2e2dc586e7e2a216c81228201dea6122d8fb2f37ccad73ccb2dca66
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / stitch-1.4.1-py3-none-any.whl
| Download URL | stitch-1.4.1-py3-none-any.whl |
|---|---|
| Size | 16.2 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7feeee1b41decec1206f382e59407dd0fb28bed51762c68648fbf8b568932611
|
|
BLAKE2b-256 checksum How to use checksums |
20c82fd6316157349b986c9c11b6b2e7608b2023a7b7a24e689033eae359f03e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|