Replace file in an android program
Just like I found myself with a jar file and the need to replace one file, it happened more than once that I needed to modify an existing Android program by replacing some files too.
apk, aab and aar files are like jar files: zip archives
Which means it is relatively easy to replace the content.
But not all zip files are valid apk files, How do we ensure that the new archive is still an apk file that can be used by other tools?
By using the tools that come from the toolchain: jar
, apksigner
, and zipalign
.
Add and replace the files
The first step is to make a copy of the apk we want to modify and create the folder structure for the files we want to replace
cp "$INPUT_APK" copy.zip;
mkdir "$TMPDIR";
mkdir -p "$TMPDIR/$FOLDERSTRUCTURE";
cp "$FILETOINSERT" "$TMPDIR/$FOLDERSTRUCTURE";
jar --update --file copy.zip -C "$TMPDIR/" "$FOLDERSTRUCTURE/$FILETOINSERT";
rm -r "$TMPDIR";
If you want to replace a whole folder, the process is very similar; you need to give the path to the folder you want to update and not the single file.
Note that replacing or adding a folder in a zip archive is not the same as replacing or adding individual files in a folder.
When adding individual files, the zip archive will not contain an entry for the folder, but only entries for the files. When adding a folder, the zip archive will also contain an entry for the folder.
The tool zipinfo can show the difference. In practice a folder entry is only useful if you want to store an empty folder, otherwise, it provides no benefit, unless a particular program has issue with archives without folder entries.
To sum it up; the difference should not be relevant, but when signing an aab (see the section about signing), the signing tool did not seem to be happy about it.
Thus, you might want to enlist all files to insert one by one, instead of pushing a whole folder at once.
Reproducible builds
I always recommend making reproducible builds, thus I would recommend adding the parameter --date "1980-01-01T12:01:00Z"
to the jar --update
command.
As described in ho to create reproducible zip archive, 1980-01-01T12:01:00Z
is the timestamp used by other tools. If your build system uses another point in time, use that.
Ensure alignment is correct
The next thing to do is to ensure that the alignment is unchanged, for this task, the tool to use is zipalign 🗄️.
The current recommended practice, especially if the apk contains some native libraries (.so
files), is to use the parameter combination -P 16 -f 4
, and to set the alignment before signing the apk.
zipalign -c -P 16 -v 4 "$INPUT_APK" > alignment_input.txt;
zipalign -P 16 -f 4 copy.zip copy.align.zip;
mv copy.align.zip output.zip;
# MINSDKVERSION should be at least 24, otherwise use v1 instead of v2
zipalign -c -P 16 -v 4 copy.zip > alignment_copy.txt;
# compare alignment_input.txt and alignment_output.txt
Note that before Android 15 and the introduction of 16 KB page sizes 🗄️, the recommended practice was to use -p -f 4
. This is the main disadvantage of doing things by hand, the build system might use the right flags out of the box, but when doing things manually, one needs to ensure that all steps are still up-to-date.
Is it really necessary?
I do not think that the alignment is relevant if the native libraries are extracted.
If the manifest looks like the following one
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" >
<application
android:name="..."
android:hasCode="true"
android:label="...."
android:extractNativeLibs="true" > <!-- this is the relevant attribute -->
</application>
</manifest>
then the alignment should not matter.
But to be on the safe side, I would set the alignment back to the value it was before modifying any files.
Sign the artifact
The last step is to sign the apk, aab, and/or aab.
If the input file was signed, then the signature will not be valid anymore.
The right tool for this job is apksigner
, which works similarly to jarsigner
apksigner sign -v --ks sw-kstore.jks --ks-pass pass:$keystorepassword --key-pass pass:$keypass --min-sdk-version $MINSDKVERSION --v2-signing-enabled true --v1-signing-enabled false output.zip
rm output.zip.idsig
apksigner verify output.zip
mv output.zip output.apk
apksigner does not seem to support aab files, contrary to aar and apk files.
Use Android Studio and the Android Gradle plugin to build and sign an Android App Bundle. However, if using the IDE is not an option—for example, because you are using a continuous build server—you can also build your app bundle from the command line and sign it using
jarsigner
.
from the android documentation
jarsigner -keystore sw-kstore.jks -storepass $pass -signedjar output.zip output.signed.zip google-android-software
What about processed files?
If you ever wanted to inspect (not even modify) the AndroidManifest.xml
(or other resource files), you will have noticed that it it not a real xml file, but something binary.
In case you want to inspect or modify one of those files, a tool like apktool can be more effective.
For extracting an existing apk
, you can execute
apktool decode "$APK.apk" -o decoded_apk; # extracts content in directory decoded
After editing the files in the directory decoded_apk
, the apk can be rebuilt with
apktool build decoded_apk -o new_apk.apk; # in case of success builds new_apk.apk
And after that, you’ll need to execute zipalign
and jarsigner
as already mentioned.
Do you want to share your opinion? Or is there an error, some parts that are not clear enough?
You can contact me anytime.