While trying to figure out how to put classes.dex in an apk file without the invalidating the signatures in the APK file, I found that apks installed to /system/app/ do not require a valid signature for each file. Tested as follows:
Create an apk and install it with adb install TestCmd.apk
Verifify that reinstallation is still possible: adb install -r TestCmd.apk
Add an extra, unsigned file: date > date.txt; zip TestCmd.apk date.txt
Try to install again: adb install -r TestCmd.apk
Installation fails, as expected. Logcat shows (for Android 4.4.4, CM-11-20141107): PackageParser E Package nl.lekensteyn.TestCmd has no certificates at entry date.txt; ignoring!
Now I tried to install such a modified on Android 5.1.1 by putting it into /system/app/TestCmd/TestCmd.apk from recovery. Then I restarted and to my surprise the tampered app is still available!
Digging in the framework code, I found the message referenced in PackageParser.java with a special case for PARSE_IS_SYSTEM. According to Yuri, this flag is set for apps from /system/app. This code is from PackageParser.java:
private static void collectCertificates(Package pkg, File apkFile, int flags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
StrictJarFile jarFile = null;
try {
jarFile = new StrictJarFile(apkPath);
// Always verify manifest, regardless of source
final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
if (manifestEntry == null) {
throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Package " + apkPath + " has no manifest");
}
final List<ZipEntry> toVerify = new ArrayList<>();
toVerify.add(manifestEntry);
// If we're parsing an untrusted package, verify all contents
if ((flags & PARSE_IS_SYSTEM) == 0) {
final Iterator<ZipEntry> i = jarFile.iterator();
while (i.hasNext()) {
final ZipEntry entry = i.next();
if (entry.isDirectory()) continue;
if (entry.getName().startsWith("META-INF/")) continue;
if (entry.getName().equals(ANDROID_MANIFEST_FILENAME)) continue;
toVerify.add(entry);
}
}
// Verify that entries are signed consistently with the first entry
// we encountered. Note that for splits, certificates may have
// already been populated during an earlier parse of a base APK.
for (ZipEntry entry : toVerify) {
final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
if (ArrayUtils.isEmpty(entryCerts)) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"Package " + apkPath + " has no certificates at entry "
+ entry.getName());
}
...
As you can see, files which do not occur in the manifest file (META-INF/MANIFEST.MF) are not checked if they are in /system/app/... which results in the observed behavior.
The relevance of this for make-gapps-zip is that classes.dex can additionally be included in the apk file without breaking the application signature. Another possible result is that adversaries can distribute malicious modifications (additions) to the APKs which are accepted by the system.
Btw, this only applies up to Lollipop. In Marshmallow many ROMs (but not all) do require the APKs content to be equal to filelist stated in the manifest.
While trying to figure out how to put classes.dex in an apk file without the invalidating the signatures in the APK file, I found that apks installed to /system/app/ do not require a valid signature for each file. Tested as follows:
adb install TestCmd.apk
adb install -r TestCmd.apk
date > date.txt; zip TestCmd.apk date.txt
adb install -r TestCmd.apk
PackageParser E Package nl.lekensteyn.TestCmd has no certificates at entry date.txt; ignoring!
Now I tried to install such a modified on Android 5.1.1 by putting it into
/system/app/TestCmd/TestCmd.apk
from recovery. Then I restarted and to my surprise the tampered app is still available!Digging in the framework code, I found the message referenced in PackageParser.java with a special case for
PARSE_IS_SYSTEM
. According to Yuri, this flag is set for apps from /system/app. This code is from PackageParser.java:As you can see, files which do not occur in the manifest file (META-INF/MANIFEST.MF) are not checked if they are in
/system/app/
... which results in the observed behavior.The relevance of this for make-gapps-zip is that classes.dex can additionally be included in the apk file without breaking the application signature. Another possible result is that adversaries can distribute malicious modifications (additions) to the APKs which are accepted by the system.
To verify the authenticity of APKs, validate the certificate manually and verify the authenticity using jarsigner as described by Chris Stratton at How can I verify the authenticity of an apk file I downloaded?.