Konubinix' opinionated web of thoughts

From the Python Android Runtime to a Custom App

Fleeting

Following a python runtime on android, I find it not very practical to have to keep the android SDK (and several tens of GB in my hard drive) to change a bit of python code to get another application.

I would like to

  1. open the poc apk
  2. change the code
  3. close it
  4. install it

My first use case is with my wiko cink peax, where I cannot even build any application anymore, due to the age of the installed android.

So to continuing getting use of it, I can install several python applications that do exactly what I want them to do.

So, let’s use the legacy from another lifetime as a basis.

Let’s first get it and extract the content.

clk ipfs get [[https://konubinix.eu/ipfs/bafybeicee4j5yofsixywtiqow6omapgxkconwdpb2w6j7ye63a2pcapmwu?filename=poc.apk][poc.apk]]
apktool d poc.apk
Saving file(s) to poc.apk
I: Using Apktool 2.10.0 on poc.apk with 4 thread(s).
I: Baksmaling classes.dex...
I: Loading resource table...
I: Decoding file-resources...
I: Loading resource table from file: /home/sam/perso/perso/konix/share/apktool/framework/1.apk
I: Decoding values */* XMLs...
I: Decoding AndroidManifest.xml with resources...
I: Regular manifest package...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...

changing the python script

Now, let’s untar the private.mp3 file, that is in fact a tar.gz archive.

pushd "poc/assets/" > /dev/null
{
    tar -x --gzip -f private.mp3
    chmod 777 -R .
    ls
}
popd > /dev/null
include
lib
main.py
private.mp3
service
sitecustomize.pyo

The files are in optimized form, but it is fairly simple to read the initial code (because it is in python2.7).

pushd "poc/assets/" > /dev/null
{
    uncompyle6 main.pyo
}
popd > /dev/null
# uncompyle6 version 3.9.2
# Python bytecode version base 2.7 (62211)
# Decompiled from: Python 3.12.6 (main, Sep  7 2024, 14:20:15) [GCC 14.2.0]
# Embedded file name: /home/test/project/.buildozer/android/app/main.py
# Compiled at: 2018-04-03 17:48:53
__version__ = '0.1
import sys
sys.path.insert(0, '/sdcard/poc/')
import main
main.run()

# okay decompiling main.pyo

Let’s change this main and simply print a message.

pushd "poc/assets/" > /dev/null
{
    rm main.pyo
    cat<<EOF > main.py
print("HEEEEEEELLLLLOOOOOO!!!!!!")
EOF
}
popd > /dev/null

I need to recreate the private.mp3 file now.

pushd "poc/assets/" > /dev/null
{
    tar -c --gzip -f /tmp/private.mp3 . && rm -rf * && mv /tmp/private.mp3 ./
}
popd > /dev/null

And then package it again.

apktool b poc
I: Using Apktool 2.10.0 with 4 thread(s).
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
I: Checking whether resources has changed...
I: Building resources...
I: Building apk file...
I: Copying unknown files/dir...
I: Built apk into: poc/dist/poc.apk

Now, we have to sign it, so let’s create a dumb keystore for this.

keytool -genkeypair -keystore your-keystore.jks -storepass 000000 -keypass 000000 -alias your-alias -dname "CN=Your Name, OU=Your Unit, O=Your Organization, L=Your City, ST=Your State, C=Your Country" -keyalg RSA -keysize 2048 -validity 10000

And sign the apk with it

apksigner sign --ks your-keystore.jks --ks-key-alias your-alias --ks-pass pass:000000 ./poc/dist/poc.apk

Now, we should be able to install it and see the printed message.

clk android -d cinkpeax adb uninstall eu.konubinix.konixpoc
clk android -d cinkpeax adb install ./poc/dist/poc.apk
clk android -d cinkpeax poc re
clk android -d cinkpeax adb shell logcat -d |gi "i/\(python\|poc\)"|tail
I/python  ( 6510): Setting up python from ANDROID_PRIVATE
I/python  ( 6510): ('Android path', ['/data/data/org.poc.poc/files/app/lib/python27.zip', '/data/data/org.poc.poc/files/app/lib/python2.7/', '/data/data/org.poc.poc/files/app/lib/python2.7/lib-dynload/', '/data/data/org.poc.poc/files/app/lib/python2.7/site-packages/', '/data/data/org.poc.poc/files/app'])
I/python  ( 6510): ('os.environ is', {'ANDROID_APP_PATH': '/data/data/org.poc.poc/files/app', 'EXTERNAL_STORAGE': '/storage/sdcard0', 'LOOP_MOUNTPOINT': '/mnt/obb', 'ANDROID_SOCKET_zygote': '9', 'BOOTCLASSPATH': '/system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar:/system/framework/mediatek-common.jar:/system/framework/mediatek-framework.jar:/system/framework/mediatek-op.jar:/system/framework/secondary-framework.jar:/system/framework/CustomProperties.jar', 'ANDROID_PROPERTY_WORKSPACE': '8,49574', 'PATH': '/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin', 'ANDROID_BOOTLOGO': '1', 'ANDROID_ASSETS': '/system/app', 'LD_LIBRARY_PATH': '/vendor/lib:/system/lib', 'PYTHONOPTIMIZE': '2', 'ANDROID_PRIVATE': '/data/data/org.poc.poc/files', 'ANDROID_ENTRYPOINT': 'main.pyo', 'SECONDARY_STORAGE': '/storage/sdcard1', 'PYTHONPATH': '/data/data/org.poc.poc/files/app:/data/data/org.poc.poc/files/app/lib', 'ANDROID_DATA': '/data', 'PYTHON_NAME': 'python', 'ANDROID_ROOT': '/system', 'PYTHONHOME': '/data/data/org.poc.poc/files/app', 'ASEC_MOUNTPOINT': '/mnt/asec', 'ANDROID_UNPACK': '/data/data/org.poc.poc/files/app', 'ANDROID_ARGUMENT': '/data/data/org.poc.poc/files/app'})
I/python  ( 6510): ('Android kivy bootstrap done. __name__ is', '__main__')
I/python  ( 6510): ['/data/data/org.poc.poc/files/app/lib/python2.7/site-packages', '/data/data/org.poc.poc/files/app/lib/site-python']
I/python  ( 6510): AND: Ran string
I/python  ( 6510): Run user program, change dir and execute entrypoint
I/python  ( 6510): main.py
I/python  ( 6510): HEEEEEEELLLLLOOOOOO!!!!!!
I/python  ( 6510): Python for android ended.

installing with another name

Now, let’s try to install it with another name.

Let’s reinstall the poc in it initial form.

clk android -d cinkpeax adb uninstall org.poc.poc
clk android -d cinkpeax adb install ./poc.apk

Now, I would like to install the same one again. In the future, it would contain some other scripts, of course.

Let’s call the new package org.poc.cop.

IIUC, it is enough to rename all the org.poc.poc and org/poc/poc in the files before creating the new apk.

grep -rl 'org.poc.poc' | while read line
do
    echo "Editing ${line}"
    sed -i -r 's/org(.)poc(.)poc/eu\1konubinix\2konixpoc/' "${line}"
done
Editing poc/smali/org/poc/poc/R$attr.smali
Editing poc/smali/org/poc/poc/R.smali
Editing poc/smali/org/poc/poc/R$string.smali
Editing poc/smali/org/poc/poc/R$drawable.smali
Editing poc/smali/org/poc/poc/R$id.smali
Editing poc/smali/org/poc/poc/R$layout.smali
Editing poc/smali/org/poc/poc/ServicePoc.smali
Editing poc/smali/org/poc/poc/BuildConfig.smali
Editing poc/AndroidManifest.xml

I also need to rename the files org/poc/poc into org/poc/cop.

find -path "*org/poc/poc"
./poc/smali/org/poc/poc

This, I also did manually

mv ./poc/smali/org/poc/poc ./poc/smali/org/poc/cop

I can also change the name of the application.

grep -rl 'ProofOfConcept' | while read line
do
    echo "Editing ${line}"
    sed -i -r 's/ProofOfConcept/MyWonderfullApp/' "${line}"
done

provide an alternative image for the application icon

magick https://konubinix.eu/ipfs/bafkreicqn4vs2dxbyymucymkjuz7xt5rhe65xozti6lwo5hblwlq4vnfhi -resize 128x128 -background white -gravity center -extent 128x128 ./poc/res/drawable/icon.png

And a different splash screen

ipfs get https://konubinix.eu/ipfs/bafkreicqn4vs2dxbyymucymkjuz7xt5rhe65xozti6lwo5hblwlq4vnfhi -o ./poc/res/drawable/presplash.jpg

Then, let package, sign, upload it again.

apktool b poc && apksigner sign --ks your-keystore.jks --ks-key-alias your-alias --ks-pass pass:000000 ./poc/dist/poc.apk
clk android -d cinkpeax adb install ./poc/dist/poc.apk
clk android -d cinkpeax adb shell am start -n org.poc.cop/org.kivy.android.PythonActivity

And voilĂ  !!

with a more recent apk

TL;DR: The same manipulations worked without visible issues.

Let’s now use the last apk I built

clk ipfs get [[https://konubinix.eu/ipfs/bafybeibafr56emdc2kjpozzjh3wz3s7zkfxjf5sady2r6ouvjnr7b7qwdi?filename=app.apk][app.apk]]
apktool d --force app.apk
Saving file(s) to app.apk
I: Using Apktool 2.10.0 on app.apk with 4 thread(s).
I: Baksmaling classes.dex...
I: Baksmaling classes2.dex...
I: Baksmaling classes3.dex...
I: Baksmaling classes4.dex...
I: Loading resource table...
I: Decoding file-resources...
I: Loading resource table from file: /home/sam/perso/perso/konix/share/apktool/framework/1.apk
I: Baksmaling classes5.dex...
I: Baksmaling classes6.dex...
I: Decoding values */* XMLs...
I: Decoding AndroidManifest.xml with resources...
I: Regular manifest package...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...

#+end_example

pushd "app/assets/" > /dev/null
{
    tar xvf private.tar
}
popd > /dev/null

I cannot decompily the main.pyc, so that’s a good thing that I documented everything in its creation.

Let’s try substituting it with another main.py

pushd "app/assets/" > /dev/null
{
    cat<<EOF > main.py
print("HEEEEELLLLLOOO !!!")
EOF
    rm main.pyc
    rm private.tar
    tar -c --gzip -f private.tar .
    mv private.tar /tmp/private.tar && rm * && mv /tmp/private.tar ./
}
popd > /dev/null
tar: .: file changed as we read it
apktool b app && apksigner sign --ks your-keystore.jks --ks-key-alias your-alias --ks-pass pass:000000 ./app/dist/app.apk
I: Using Apktool 2.10.0 with 4 thread(s).
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
I: Checking whether sources has changed...
I: Smaling smali_classes2 folder into classes2.dex...
I: Checking whether sources has changed...
I: Smaling smali_classes3 folder into classes3.dex...
I: Checking whether sources has changed...
I: Smaling smali_classes4 folder into classes4.dex...
I: Checking whether resources has changed...
I: Building resources...
I: Checking whether sources has changed...
I: Smaling smali_classes5 folder into classes5.dex...
I: Checking whether sources has changed...
I: Smaling smali_classes6 folder into classes6.dex...
I: Copying libs... (/lib)
I: Checking whether sources has changed...
I: Smaling smali_classes7 folder into classes7.dex...
I: Building apk file...
I: Copying unknown files/dir...
I: Built apk into: app/dist/app.apk
clk android adb uninstall eu.konix.poc
clk android adb install ./app/dist/app.apk
clk android poc start
clk android adb logcat -d|gi "i \(python\|poc\)"|tail|uncolor
11-08 13:43:00.692 11349 11381 I python  : Initialized python
11-08 13:43:00.693 11349 11381 I python  : AND: Init threads
11-08 13:43:00.693 11349 11381 I python  : testing python print redirection
11-08 13:43:00.696 11349 11381 I python  : Android path ['.', '/data/user/0/eu.konix.poc/files/app/_python_bundle/stdlib.zip', '/data/user/0/eu.konix.poc/files/app/_python_bundle/modules', '/data/user/0/eu.konix.poc/files/app/_python_bundle/site-packages']
11-08 13:43:00.696 11349 11381 I python  : os.environ is environ({'PATH': '/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin', 'ANDROID_BOOTLOGO': '1', 'ANDROID_ROOT': '/system', 'ANDROID_ASSETS': '/system/app', 'ANDROID_DATA': '/data', 'ANDROID_STORAGE': '/storage', 'ANDROID_ART_ROOT': '/apex/com.android.art', 'ANDROID_I18N_ROOT': '/apex/com.android.i18n', 'ANDROID_TZDATA_ROOT': '/apex/com.android.tzdata', 'EXTERNAL_STORAGE': '/sdcard', 'ASEC_MOUNTPOINT': '/mnt/asec', 'BOOTCLASSPATH': '/apex/com.android.art/javalib/core-oj.jar:/apex/com.android.art/javalib/core-libart.jar:/apex/com.android.art/javalib/core-icu4j.jar:/apex/com.android.art/javalib/okhttp.jar:/apex/com.android.art/javalib/bouncycastle.jar:/apex/com.android.art/javalib/apache-xml.jar:/system/framework/framework.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/framework-atb-backward-compatibility.jar:/system/framework/org.ifaa.android.manager.jar:/system/framework/telephony-ext.jar:/apex/com.android.conscrypt/javalib/conscrypt.jar:/apex/com.android.media/javalib/updatable-media.jar:/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar:/apex/com.android.os.statsd/javalib/framework-statsd.jar:/apex/com.android.permission/javalib/framework-permission.jar:/apex/com.android.sdkext/javalib/framework-sdkextensions.jar:/apex/com.android.wifi/javalib/framework-wifi.jar:/apex/com.android.tethering/javalib/framework-tethering.jar', 'DEX2OATBOOTCLASSPATH': '/apex/com.android.art/javalib/core-oj.jar:/apex/com.android.art/javalib/core-libart.jar:/apex/com.android.art/javalib/core-icu4j.jar:/apex/com.android.art/javalib/okhttp.jar:/apex/com.android.art/javalib/bouncycastle.jar:/apex/com.android.art/javalib/apache-xml.jar:/system/framework/framework.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/framework-atb-backward-compatibility.jar:/system/framework/org.ifaa.android.manager.jar:/system/framework/telephony-ext.jar', 'SYSTEMSERVERCLASSPATH': '/system/framework/org.lineageos.platform.jar:/system/framework/com.android.location.provider.jar:/system/framework/services.jar:/system/framework/ethernet-service.jar:/apex/com.android.permission/javalib/service-permission.jar:/apex/com.android.ipsec/javalib/android.net.ipsec.ike.jar', 'DOWNLOAD_CACHE': '/data/cache', 'TERMINFO': '/system/etc/terminfo', 'ANDROID_SOCKET_zygote': '21', 'ANDROID_SOCKET_usap_pool_primary': '23', 'ANDROID_ENTRYPOINT': 'main.py', 'ANDROID_ARGUMENT': '/data/user/0/eu.konix.poc/files/app', 'ANDROID_APP_PATH': '/data/user/0/eu.konix.poc/files/app', 'ANDROID_PRIVATE': '/data/user/0/eu.konix.poc/files', 'ANDROID_UNPACK': '/data/user/0/eu.konix.poc/files/app', 'PYTHONHOME': '/data/user/0/eu.konix.poc/files/app', 'PYTHONPATH': '/data/user/0/eu.konix.poc/files/app:/data/user/0/eu.konix.poc/files/app/lib', 'PYTHONOPTIMIZE': '2', 'P4A_BOOTSTRAP': 'SDL2', 'PYTHON_NAME': 'python', 'P4A_IS_WINDOWED': 'True', 'KIVY_ORIENTATION': 'Portrait', 'P4A_NUMERIC_VERSION': 'None', 'P4A_MINSDK': '21', 'LC_CTYPE': 'C.UTF-8'})
11-08 13:43:00.696 11349 11381 I python  : Android kivy bootstrap done. __name__ is __main__
11-08 13:43:00.696 11349 11381 I python  : AND: Ran string
11-08 13:43:00.696 11349 11381 I python  : Run user program, change dir and execute entrypoint
11-08 13:43:00.697 11349 11381 I python  : HEEEEELLLLLOOO !!!
11-08 13:43:00.697 11349 11381 I python  : Python for android ended.

Now, changing the name.

grep -rl 'eu.konix.poc' | while read line
do
    echo "Editing ${line}"
    sed -i -r 's/eu(.)konix(.)poc/eu\1konix\2cop/' "${line}"
done
Editing app/smali_classes3/eu/konix/poc/ServicePoc.smali
Editing app/smali/eu/konix/poc/R.smali
Editing app/smali/eu/konix/poc/R$string.smali
Editing app/smali/eu/konix/poc/R$drawable.smali
Editing app/smali/eu/konix/poc/R$id.smali
Editing app/smali/eu/konix/poc/R$layout.smali
Editing app/smali/eu/konix/poc/R$mipmap.smali
Editing app/AndroidManifest.xml
Editing app/build/apk/classes3.dex
Editing app/build/apk/classes.dex
find -path "*eu/konix/poc"
./app/smali_classes3/eu/konix/poc
./app/smali/eu/konix/poc
mv ./app/smali_classes3/eu/konix/{poc,cop}
mv ./app/smali/eu/konix/{poc,cop}
apktool b app && apksigner sign --ks your-keystore.jks --ks-key-alias your-alias --ks-pass pass:000000 ./app/dist/app.apk
clk android adb install ./app/dist/app.apk
clk android adb shell am start -n eu.konix.cop/org.kivy.android.PythonActivity
clk android adb logcat -d|gi "i \(python\|cop\)"|tail|uncolor
11-08 13:49:30.716 11550 11587 I python  : Initialized python
11-08 13:49:30.716 11550 11587 I python  : AND: Init threads
11-08 13:49:30.717 11550 11587 I python  : testing python print redirection
11-08 13:49:30.718 11550 11587 I python  : Android path ['.', '/data/user/0/eu.konix.cop/files/app/_python_bundle/stdlib.zip', '/data/user/0/eu.konix.cop/files/app/_python_bundle/modules', '/data/user/0/eu.konix.cop/files/app/_python_bundle/site-packages']
11-08 13:49:30.719 11550 11587 I python  : os.environ is environ({'PATH': '/product/bin:/apex/com.android.runtime/bin:/apex/com.android.art/bin:/system_ext/bin:/system/bin:/system/xbin:/odm/bin:/vendor/bin:/vendor/xbin', 'ANDROID_BOOTLOGO': '1', 'ANDROID_ROOT': '/system', 'ANDROID_ASSETS': '/system/app', 'ANDROID_DATA': '/data', 'ANDROID_STORAGE': '/storage', 'ANDROID_ART_ROOT': '/apex/com.android.art', 'ANDROID_I18N_ROOT': '/apex/com.android.i18n', 'ANDROID_TZDATA_ROOT': '/apex/com.android.tzdata', 'EXTERNAL_STORAGE': '/sdcard', 'ASEC_MOUNTPOINT': '/mnt/asec', 'BOOTCLASSPATH': '/apex/com.android.art/javalib/core-oj.jar:/apex/com.android.art/javalib/core-libart.jar:/apex/com.android.art/javalib/core-icu4j.jar:/apex/com.android.art/javalib/okhttp.jar:/apex/com.android.art/javalib/bouncycastle.jar:/apex/com.android.art/javalib/apache-xml.jar:/system/framework/framework.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/framework-atb-backward-compatibility.jar:/system/framework/org.ifaa.android.manager.jar:/system/framework/telephony-ext.jar:/apex/com.android.conscrypt/javalib/conscrypt.jar:/apex/com.android.media/javalib/updatable-media.jar:/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar:/apex/com.android.os.statsd/javalib/framework-statsd.jar:/apex/com.android.permission/javalib/framework-permission.jar:/apex/com.android.sdkext/javalib/framework-sdkextensions.jar:/apex/com.android.wifi/javalib/framework-wifi.jar:/apex/com.android.tethering/javalib/framework-tethering.jar', 'DEX2OATBOOTCLASSPATH': '/apex/com.android.art/javalib/core-oj.jar:/apex/com.android.art/javalib/core-libart.jar:/apex/com.android.art/javalib/core-icu4j.jar:/apex/com.android.art/javalib/okhttp.jar:/apex/com.android.art/javalib/bouncycastle.jar:/apex/com.android.art/javalib/apache-xml.jar:/system/framework/framework.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/framework-atb-backward-compatibility.jar:/system/framework/org.ifaa.android.manager.jar:/system/framework/telephony-ext.jar', 'SYSTEMSERVERCLASSPATH': '/system/framework/org.lineageos.platform.jar:/system/framework/com.android.location.provider.jar:/system/framework/services.jar:/system/framework/ethernet-service.jar:/apex/com.android.permission/javalib/service-permission.jar:/apex/com.android.ipsec/javalib/android.net.ipsec.ike.jar', 'DOWNLOAD_CACHE': '/data/cache', 'TERMINFO': '/system/etc/terminfo', 'ANDROID_SOCKET_zygote': '21', 'ANDROID_SOCKET_usap_pool_primary': '23', 'ANDROID_ENTRYPOINT': 'main.py', 'ANDROID_ARGUMENT': '/data/user/0/eu.konix.cop/files/app', 'ANDROID_APP_PATH': '/data/user/0/eu.konix.cop/files/app', 'ANDROID_PRIVATE': '/data/user/0/eu.konix.cop/files', 'ANDROID_UNPACK': '/data/user/0/eu.konix.cop/files/app', 'PYTHONHOME': '/data/user/0/eu.konix.cop/files/app', 'PYTHONPATH': '/data/user/0/eu.konix.cop/files/app:/data/user/0/eu.konix.cop/files/app/lib', 'PYTHONOPTIMIZE': '2', 'P4A_BOOTSTRAP': 'SDL2', 'PYTHON_NAME': 'python', 'P4A_IS_WINDOWED': 'True', 'KIVY_ORIENTATION': 'Portrait', 'P4A_NUMERIC_VERSION': 'None', 'P4A_MINSDK': '21', 'LC_CTYPE': 'C.UTF-8'})
11-08 13:49:30.719 11550 11587 I python  : Android kivy bootstrap done. __name__ is __main__
11-08 13:49:30.719 11550 11587 I python  : AND: Ran string
11-08 13:49:30.719 11550 11587 I python  : Run user program, change dir and execute entrypoint
11-08 13:49:30.719 11550 11587 I python  : HEEEEELLLLLOOO !!!
11-08 13:49:30.719 11550 11587 I python  : Python for android ended.

And changing the icons,

magick https://konubinix.eu/ipfs/bafkreicqn4vs2dxbyymucymkjuz7xt5rhe65xozti6lwo5hblwlq4vnfhi -resize 128x128 -background white -gravity center -extent 128x128 ./app/res/mipmap/icon.png

And a different splash screen

ipfs get https://konubinix.eu/ipfs/bafkreicqn4vs2dxbyymucymkjuz7xt5rhe65xozti6lwo5hblwlq4vnfhi -o ./app/res/drawable/presplash.jpg

And for the name

grep -rl '\bpoc\b' | while read line
do
    echo "Editing ${line}"
    sed -i -r 's/ProofOfConcept/MyWonderfullApp/' "${line}"
done

Note that I did it from emacs, as poc is to generic a name and it was hard not to match the binary files.

And voilĂ  !!

Notes linking here