Last week I had an Android application that I wanted to test in the Android emulator (the official one included in the SDK). I had the application installed from Play Store on a physical device, and as I’ve done many times, I just grabbed it using Drozer and issued the usual ADB command to install it on the emulator. (The sizes and package names have been altered to protect the innocent.)
$ adb install hu.silentsignal.blogpost.apk 1337 KB/s (27313378 bytes in 22.233s) pkg: /data/local/tmp/hu.silentsignal.blogpost.apk Failure [INSTALL_FAILED_CONTAINER_ERROR]
A quick search on the web revealed that the application most probably had the
installLocation parameter set to
preferExternal in the manifest file. Latter is an XML file called
AndroidManifest.xml that contains important metadata about Android applications and is transformed into a binary representation upon compilation to reduce the size and processing power required on resource-constrained devics. Running android-apktool converted it back to text format, and revealed that it was indeed the cause.
<?xml version="1.0" encoding="utf-8"?> <manifest android:versionCode="133744042" android:versionName="4.13.37" android:installLocation="preferExternal" package="hu.silentsignal.blogpost" xmlns:android="http://schemas.android.com/apk/res/android">
Most results of the web search agreed that the emulator (although capable of emulating SD cards) is incompatible with this setting, some suggested increasing the memory of the emulated device, others said the same about the SD card, but unfortunately none of these worked for us. The majority of accepted answers solved the problem by changing the
preferExternal parameter to
auto, which is the default.
Changing an Android application and repackaging it is also a breeze usually, apktool supports this natively, I just have to sign the resulting APK with a key of my own. However, this application used some features that apktool (and other tools invoked in the process, including AAPT) didn’t like. I’ve met this situation before, and there are usually two solutions.
- Removing such features in a way that the application can still be tested is a cumbersome series of iterations, and leads to almost certain insanity.
- Updating apktool and some dependencies so that they accept such features is rather painless, as it usually Just Works™.
However in this case, even beta versions couldn’t handle the task, I’ve even written some wrapper scripts around the dependencies to tweak with parameters like minimum and targeted API levels, but got nowhere. Then I realized, binary XML files still have to store the attribute names somewhere, so I fired up a hex editor and hoped that the Android runtime won’t complain about unknown attributes and will use the default value. It seemed that the format uses UTF-16 to store strings, and at offset 0x112, I changed the
o, resulting in
00f0 3a080000 82080000 0f006900 6e007300 |:.........i.n.s.| 0100 74006100 6c006c00 4c006f00 63006100 |t.a.l.l.L.o.c.a.| 0110 74006f00 6f006e00 00000b00 76006500 |t.o.o.n.....v.e.| 0120 72007300 69006f00 6e004300 6f006400 |r.s.i.o.n.C.o.d.| 0130 65000000 0b007600 65007200 73006900 |e.....v.e.r.s.i.|
Then, I simply updated the manifest in the APK file using a ZIP tool since APK files are just ZIP archives with specific naming conventions, just like JAR, DOCX and ODT files. Since the digital signature of the APK is now corrupted, I had to resign it with jarsigner, but installation still failed.
$ 7z -tzip a hu.silentsignal.blogpost.apk AndroidManifest.xml 7-Zip  9.20 Copyright (c) 1999-2010 Igor Pavlov 2010-11-18 p7zip Version 9.20 (locale=hu_HU.UTF-8,Utf16=on,HugeFiles=on,4 CPUs) Scanning Updating archive hu.silentsignal.blogpost.apk Compressing AndroidManifest.xml Everything is Ok $ jarsigner -sigalg SHA1withRSA -digestalg SHA1 \ -keystore s2.jks hu.silentsignal.blogpost.apk s2 Enter Passphrase for keystore: $ adb install hu.silentsignal.blogpost.apk 1337 KB/s (27313378 bytes in 22.233s) pkg: /data/local/tmp/hu.silentsignal.blogpost.apk Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES]
How can there be no certificates? Let’s list the contents with a ZIP tool, and look for the
META-INF, an old friend from the Java world (JAR files) that contains a list of files with name and (in case of Android, SHA-1) hash (
MANIFEST.MF), a public key (
*.RSA in case of RSA), and a signature of the former with the latter (
*.SF). The name of the public key and signature files are the same as their alias in the keystore converted to uppercase; see the last parameter of jarsigner in the above commands (
Date Time Attr Size Compressed Name ------------------- ----- -------- ------------ -------------------- 2014-04-03 14:44:42 ..... 362941 110461 META-INF/MANIFEST.MF 2014-04-03 14:44:42 ..... 369105 113933 META-INF/S2.SF 2014-04-03 14:44:42 ..... 2046 1865 META-INF/S2.RSA 2014-02-27 13:37:16 ..... 928 637 META-INF/CERT.RSA 2014-02-27 13:37:16 ..... 361020 113942 META-INF/CERT.SF
See? It’s there! Also, another certificate (
CERT.*), since without apktool (which rebuilds APK archives from scratch) everything except
AndroidManifest.xml is included from the original file. Let’s delete the
META-INF directory, and try again.
$ 7z -tzip d hu.silentsignal.blogpost.apk META-INF 7-Zip  9.20 Copyright (c) 1999-2010 Igor Pavlov 2010-11-18 p7zip Version 9.20 (locale=hu_HU.UTF-8,Utf16=on,HugeFiles=on,4 CPUs) Updating archive hu.silentsignal.blogpost.apk Everything is Ok $ jarsigner -sigalg SHA1withRSA -digestalg SHA1 \ -keystore s2.jks hu.silentsignal.blogpost.apk s2 Enter Passphrase for keystore: $ adb install hu.silentsignal.blogpost.apk 1337 KB/s (27313378 bytes in 22.233s) pkg: /data/local/tmp/hu.silentsignal.blogpost.apk Success
With this last modification, it worked, and I was able to explore the application within the emulator that makes it much more easier to set a global proxy and manipulate certificates than a physical device, and I haven’t even mentioned faking sensors and GSM information, or taking snapshots. The lesson here was that
- if a difficult format takes more than 5 minutes to recreate, it’s worth considering manual editing in case of simple modifications,
- most XML readers (including the Android runtime) tend to ignore unknown attributes, and
META-INFdirectory should be removed before signing an APK file, otherwise the runtime refuses it.