October 20, 2017

Disassembly of Dalvik VM Bytecode to Prevent APK Uninstallation

Students, God bless them, went and forced me to learn how to disassemble APKs after continuing to accidentally uninstall applications from their own tablets, and (of course) from each other’s tablets.

This is a problem I’ve had for awhile. Initially I tried to solve it by making everything a system app, but some apps crash inexplicably when moved to /system/app or /system/priv-app. So for a few months I had half as system apps, and just left the rest as user apps which could still be deleted.

Eventually the number of students coming to me just to put applications back on their tablet got out of control. My first idea was to obtain the source code for the Launcher APK, disable the code pertaining to app uninstallation, compile the modified APK, and be done with it. That was a complete failure. These tablets are from 2013; after locally checking out the Launcher2 APK git repository and switching to the android-4.4.2_r1 release branch, I realized that the build system is more involved than just running make. So I downloaded Android Studio only to realize that after 2013, the build system was apparently transitioned to Gradle. I would have had to download Eclipse with the old Android build packages and find my way down that path. But by then I had already blown a few hours and was searching for an easier way out.

Then I remembered: these APKs are just zip files with a bunch of Java classfiles inside them. It should be possible to diassemble the classfiles, make a change here or there, reassemble them and replace the stock Launcher2 APK. Thankfully this did indeed turn out to be possible, as detailed herein.

The trick is that Android commonly precompiles Dalvik bytecode, initially stored in a .dex file inside an APK, into an .odex file sitting alongside the APK. When I looked inside /system/priv-app I noticed this: there was Launcher2.apk and Launcher2.odex. I used ADB to pull these to my local filesystem, but when I renamed Launcher2.apk to Launcher2.zip and unzipped it, I found no class files.

After some confusion I came across the smali/baksmali project, which allows one to disassemble an .odex file into human-readable bytecode instructions stored in .smali files, then reassemble these instructions back into a classes.dex file for inclusion in the modified APK. After the modified APK is loaded, this classes.dex file will be pulled from the APK and precompiled into a new .odex file as usual.

Once I checked out a local copy of smali/baksmali, I ran the following to prepare to disassemble:

$ adb pull /system/framework .

then proceeded with disassembly:

$ ./baksmali deodex --api 15 -d ./framework Launcher2.odex

where the API value is specific to the version of Android you’re targeting.

Next I started sifting through the .smali files in the generated out folder, searching for relevant sections of code. I also had the original Java source files for Launcher2 open alongside as well. The .line annotations in the .smali files didn’t seem to match those of the Java files, but I could tell by the sequence and names of method invocations that it was a good enough match to proceed.

I ended up voiding out the startApplicationUninstallActivity method of /com/android/launcher2/Launcher.smali; thus this method body:

.line 2123
iget v0, p1, Lcom/android/launcher2/ApplicationInfo;->flags:I

and-int/lit8 v0, v0, 0x1

Involvesif-nez v0, :cond_12

.line 2126
const v0, 0x7f0d0044

.line 2127
const/4 v1, 0x0

invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;

move-result-object v0

invoke-virtual {v0}, Landroid/widget/Toast;->show()V

.line 2137

.line 2129
iget-object v0, p1, Lcom/android/launcher2/ApplicationInfo;->componentName:Landroid/content/ComponentName;

invoke-virtual {v0}, Landroid/content/ComponentName;->getPackageName()Ljava/lang/String;

move-result-object v0

.line 2130
iget-object v1, p1, Lcom/android/launcher2/ApplicationInfo;->componentName:Landroid/content/ComponentName;

invoke-virtual {v1}, Landroid/content/ComponentName;->getClassName()Ljava/lang/String;

move-result-object v1

.line 2131
new-instance v2, Landroid/content/Intent;

const-string v3, "android.intent.action.DELETE"

const-string v4, "package"

invoke-static {v4, v0, v1}, Landroid/net/Uri;->fromParts(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/net/Uri;

move-result-object v0

invoke-direct {v2, v3, v0}, Landroid/content/Intent;-><init>(Ljava/lang/String;Landroid/net/Uri;)V

.line 2133
const/high16 v0, 0x10800000

invoke-virtual {v2, v0}, Landroid/content/Intent;->setFlags(I)Landroid/content/Intent;

.line 2135
invoke-virtual {p0, v2}, Landroid/app/Activity;->startActivity(Landroid/content/Intent;)V

goto :goto_11

was replaced simply with:


After making this change, you can reassemble the bytecode using smali:

$ ./smali assemble out/ -o classes.dex

Copy this classes.dex file into a directory containing the unzipped contents of the stock Launcher2.apk, then rezip with:

$ zip -r Launcher2.zip AndroidManifest.xml classes.dex res/ META-INF/ resources.arsc

Align the zip with:

$ zipalign -v 4 Launcher2.zip Launcher2_ALIGNED.zip

then move this into /system/priv-app of the device:

$ adb push Launcher2_ALIGNED.zip /sdcard/Launcher2.apk
$ adb shell
(adb) su
(adb) mount -o rw,remount,rw /system
(adb) cp /sdcard/Launcher2.apk /system/priv-app

and delete the original .odex file:

(adb) rm /system/priv-app/Launcher2.odex

Finally, reboot the device.