ax / apk.sh

apk.sh makes reverse engineering Android apps easier, automating some repetitive tasks like pulling, decoding, rebuilding and patching an APK.
GNU General Public License v3.0
3.34k stars 189 forks source link

building split apks / single apk #23

Closed jeromew closed 11 months ago

jeromew commented 1 year ago

Hello,

I tried to pull a splitted apk and apk.sh built a file.single.apk. This file.single.apk did not work as expected after installation (android complained about missing components and proposed to re-download the app via the app store)

After some research, I found how to install splitted apks (avoiding the need for the single apk hack)

  1. first you need to push the apks to the device in /data/local/tmp
  2. then you need to execute a sh script in an adb shell
TOTALSIZE=`wc -c /data/local/tmp/*.apk | tail -n 1 | cut -f1 -d " "`
SESSIONID=`pm install-create -S $TOTALSIZE | tr -dc '0-9'`
INDEX=0
for f in /data/local/tmp/*.apk
do
  let SIZE=`wc -c ${f} | cut -f1 -d " "`
  echo streaming ${f}
  pm install-write -S ${SIZE} ${SESSIONID} ${INDEX} ${f}
  let INDEX=${INDEX}+1
done
pm install-commit ${SESSIONID}
rm /data/local/tmp/*.apk

this creates an install session on the devices and then writes the different apks in this session.

Using this the installed app starts correctly

ax commented 1 year ago

Which apk? Which Android version?

Thanks for sharing!

jeromew commented 1 year ago

the single apk feature was mis-behaving with deezer 7.1.4.88 installed yesterday from the google play store. device: samsung galaxy s5 running android 6.0.1 ; I agree this is very old by 2023 standards ;-)

jeromew commented 1 year ago

Also note that inside the AndroidManifest there is

        <meta-data android:name="com.android.vending.splits.required" android:value="true"/>
        <meta-data android:name="com.android.stamp.source" android:value="https://play.google.com/store"/>
        <meta-data android:name="com.android.stamp.type" android:value="STAMP_TYPE_DISTRIBUTION_APK"/>
        <meta-data android:name="com.android.vending.splits" android:resource="@xml/splits0"/>
        <meta-data android:name="com.android.vending.derived.apk.id" android:value="3"/>

so that is maybe another way that android has to require the splits. apk.sh currently only patches "android:isSplitRequired" which is also present in the manifest.

jeromew commented 1 year ago

I can confirm that after setting the "com.android.vending.splits.required" to false, the single apk works

jeromew commented 1 year ago

After some more testing, there seem to be another issue. the app starts but after a specific click I get in adb logcat

10-17 20:26:37.774  5557  5557 I AndroidRuntime: VM exiting with result code 0, cleanup skipped.
10-17 20:27:00.274  6377  6377 D AndroidRuntime: Shutting down VM
10-17 20:27:00.894  6377  6377 E AndroidRuntime: FATAL EXCEPTION: main
10-17 20:27:00.894  6377  6377 E AndroidRuntime: Process: xxx.android.app, PID: 6377
10-17 20:27:00.894  6377  6377 E AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{xxx.android.app/com.xxx.feature.UnkActivity}: android.view.InflateException: Binary XML file line #2: Binary XML file line #2: Error inflating class xxx.android.masthead.MastheadCoordinatorLayout
10-17 20:27:00.894  6377  6377 E AndroidRuntime:        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3253)
10-17 20:27:00.894  6377  6377 E AndroidRuntime:        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3349)
10-17 20:27:00.894  6377  6377 E AndroidRuntime:        at android.app.ActivityThread.access$1100(ActivityThread.java:221)
10-17 20:27:00.894  6377  6377 E AndroidRuntime:        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1794)
10-17 20:27:00.894  6377  6377 E AndroidRuntime:        at android.os.Handler.dispatchMessage(Handler.java:102)
ax commented 1 year ago

Thanks for sharing jeromew! Is the app still not working? Do you figured out why?

jeromew commented 1 year ago

After some more debugging, I don't think this Exception trace is related to apk.sh. It is related to a problem in the decode/build cycle of apktool that misses some @drawable resources references. I don't know exactly what is the problem but it is not with apk.sh. This is probably a problem specific to the application I was testing. I fixed my problem in the split-apk case by using 'apktool d -r base.apk', the -r option avoiding the resource decoding step. I don't think it would work in the single apk case because there are resources that would need to be merged and if the resource decoding step is lossy that would create the Exception issue.

jeromew commented 1 year ago

btw PR #25 is still necessary to start the app in the single/merged apk scenario

ax commented 11 months ago

Thanks for your work @jeromew! PR merged. After all, do you managed to correctly merge the apks?

jeromew commented 11 months ago

No I worked with the apktool -r flag that does not decode the resources but then all the resource files (including AndroidManifest.xml) are binary files which makes it hard patching them or even detecting the MainActivity to inject frida.

the script I gave when I created the issue works for re-installing base.apk & splits after smali modifications (even adding frida manually). I found a way to execute it on the device from a .sh script if you think it could be a good addition to apk.sh, but you'll have to decide on the apk.sh API to decide how to handle the single/splitted in parallel

# sign apks
ls -1 *.apk | xargs -I{} ~/.apk.sh/sdk_root/build-tools/33.0.1/apksigner sign --ks-pass pass:keykey --ks ../my-key.keystore {}

# push signed apks to android device
ls -1 *.apk | xargs -I{} ~/.apk.sh/sdk_root/platform-tools/adb push {} /data/local/tmp/{}

REMOTECODE=$(cat <<"EOF"
TOTALSIZE=`wc -c /data/local/tmp/*.apk | tail -n 1 | cut -f1 -d " "`
SESSIONID=`pm install-create -S $TOTALSIZE | tr -dc '0-9'`
INDEX=0
for f in /data/local/tmp/*.apk
do
  let SIZE=`wc -c ${f} | cut -f1 -d " "`
  echo streaming ${f}
  pm install-write -S ${SIZE} ${SESSIONID} ${INDEX} ${f}
  let INDEX=${INDEX}+1
done
pm install-commit ${SESSIONID}
rm /data/local/tmp/*.apk
exit
EOF
)

echo "$REMOTECODE"
# install app on android device
~/.apk.sh/sdk_root/platform-tools/adb shell "$REMOTECODE"
jeromew commented 11 months ago

I digged a little bit further on the single apk problem.

it seems that the resources exist in one of the split apk, but apktool does not replace them with a DUMMY tag. It simply puts drawable="@null" instead so the DUMMY replacement mechanism does not recover the resource id and those resource links go missing in the final apk.

jeromew commented 11 months ago

ok I may have found the issue (I need to re-do a whole decode / build round trip)

According to the changelog of apktool - https://github.com/iBotPeaches/Apktool/releases/tag/v2.9.0 the 2.9.0 version introduced a new option called "res-mode" which has an impact on how missing ressources are handled.

The default res-mode is "remove" (DECODE_RES_RESOLVE_REMOVE) which does what I observe, setting missing resources to @null.

but when calling "apktool_2.9.0.jar d -resm dummy base.apk", the DUMMY placeholders are appearing ! hopefully the apk.sh DUMMY replacement sequence should do the correct thing now.

jeromew commented 11 months ago

Note that the options for the res-mode are

switch (mode) {
                case "remove":
                case "delete":
                    config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_REMOVE);
                    break;
                case "dummy":
                case "dummies":
                    config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_DUMMY);
                    break;
                case "keep":
                case "preserve":
                    config.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_RETAIN);
                    break;
                default:
                    System.err.println("Unknown resolve resources mode: " + mode);
                    System.err.println("Expect: 'remove', 'dummy' or 'keep'.");
                    System.exit(1);
            }
jeromew commented 11 months ago

It now works with PR#27.

Note that I added "--resource-mode dummy" without checking that the apktool version is >= 2.9.0 so we could say this is not compatible with apktool < 2.9.0. This seems ok to me as working on an older apktool version should not be recommended.

apktool cli doc - https://apktool.org/docs/cli-parameters image

Also note that I had to change the DUMMY replacement strategy because the one-by-one strategy was simply too slow on my setup and the app I used for testing.

ax commented 11 months ago

Thanks @jeromew! v1.0.7 contains your PR!

mikebgrep commented 6 months ago

@ax @jeromew I experience the same issue for APK that have split file. I got this exception

Process: com.package, PID: 8459
04-13 14:59:53.979  8459  8459 E AndroidRuntime:    at com.package.ui.MainActivity.m0(Unknown Source:41)
04-13 14:59:53.979  8459  8459 E AndroidRuntime: Caused by: android.content.res.Resources$NotFoundException: Drawable com.package:drawable/APKTOOL_DUMMY_3ed with resource ID #0x7f0803ed

I try couple of things:

  1. I copy all the drawable from the split file and paste them in the drawable of the base.apk #Does not work
  2. I try to delete the APKTOOL_DUMMY_3ed from the drawable and properties but still the app search it by ID and didn't work again Is there any solution for this, or I need to search for alternative to pull APK that have split.
ax commented 6 months ago

Are you using the latest version? Can u open a new issue?

mikebgrep commented 6 months ago

Yes I am using the last version.I transferred the comment to a new issue. https://github.com/ax/apk.sh/issues/36 @ax I just check and I am using 1.0.6 not the last version of the apk.sh. I will double check with the new version.