Ramping up security: additional APK checks are in place with the IzzyOnDroid repo
We use apps because we value their functionality. But sometimes they also include components we wouldn’t have chosen ourselves had we been asked. Pieces of code which are either not needed for the app’s valued functionality (such as ads or trackers) – or modules which are not FOSS, thus making the FOSS app a tainted one that strictly speaking, now is no longer FOSS. Two previous articles on this site give you some insights into that:
- What’s it all about those modules apps contain? (2017-04-22)
- Identify modules in apps (2021-01-29)
The library scanner active in the IzzyOnDroid repo, which is described in the latter article and available under a FOSS license, deals with those and makes transparent which libraries/modules an APK file contains. An overview on what measures are in place you can always find in the What about security? section of the repository’s info page.
In January 2024, additional security measures were established at the IzzyOnDroid repository. It is these that this article not only wants to introduce to you, but also give you some background about.
Scanning the AndroidManifest
Wikipedia introduces the term Manifesto as:
A manifesto is a written declaration of the intentions, motives, or views of the issuer
In computing there‘s a similar thing: the manifest file. With an Android app, the issuer of the manifest is the developer of the app. This app usually holds more than one manifest. The one we’re talking about here is the AndroidManifest.xml
. In it, the developer(s) declare the intentions and activities of the app:
- its name and version
- which permissions it plans to use
- which components (activities, services etc.) it provides
- and several other things.
Inside the APK file, this AndroidManifest.xml
is stored in binary form (called „AXML“), which can be extracted e.g. using androguard axml <file.apk>
, and then analyzed. This is being done for each APK file entering the IzzyOnDroid repo, were the following checks are being performed:
Checking for Flags
The official documentation calls them „attributes“. They declare each of the app’s components and some of its features. Most of them one wouldn’t consider sensitive – e.g. the icon, whether it allows to be backed up, which category it belongs to. But some of them are, and those are checked for now:
android:debuggable
: fdroidserver (the software used here to create the repository structures with the index of available apps) already checks for this and gives a warning, which results in a mail notification being sent to me by the updater. This flag is a bit controversial. On one hand, such an app might open doors to things it should rather not (except in the developers’ environments) – on the other hand, with newer Android versions (Android 12+) this is the only way to convinceadb backup
to include app data as well it seems. So it’s unclear if it should be handled as a risk or as a feature. Currently, this raises the corresponding anti-feature.android:testOnly
„Indicates whether this application is only for testing purposes. For example, it might expose functionality or data outside of itself that can cause a security hole, but be useful for testing.“ I didn’t encounter it yet in my repo (and hopefully never will). Such an app would not be useful there anyway: „This kind of APK only installs throughadb
.“android:usesCleartextTraffic
: Internet traffic should be encrypted, but e.g. „local resources“ (like a streaming media player accessing your local server) can be exempted as it’s hard to get a valid certificate for a server not reachable from outside. Reason against insecure traffic include that it can be easily read in transit, allows easy MITM attacks. Quoting the linked page: „The key reason for avoiding cleartext traffic is the lack of confidentiality, authenticity, and protections against tampering. A network attacker can eavesdrop on transmitted data and also modify it without being detected.“
Sensitive permissions
These will trigger a warning, which will cause a manual verification. Justified ones will be added to the „allow list“ of the app, like CONTACTS
for an address book, or CALL_PHONE
for a dialer. Others are highlighted in the corresponding APK’s permission section – or might lead to the removal (or non-inclusion) of the app. A list of those permissions can be found in issue 475 and in the corresponding library file. These cover security topics as well as privacy concerns.
A specific permission is REQUEST_INSTALL_PACKAGES
which usually indicates an integrated self-updater, violating the inclusion criteria of the repo as that would bypass the security checks in place. Here I reach out to the corresponding developers to verify (the permission could well be justified; e.g. a file explorer might need it to let you install APK files from your local storage) and, in case it is a self-updater, ask for either a separate build flavor having the self-updater removed, or at least make it opt-in with a proper warning, as shown by the example screenshots from RiMusic (very few exceptions are made for dedicated testing variants of apps, e.g. „beta channels“, which must be clearly marked as such). So far, my requests were honored with understanding.
Sensitive permissions are checked manually before a new app gets added to the repo, but also automatically with each incoming update. Justified ones are added to the app’s „allow list“ (and removed from there if they were removed from the app). Unclear ones are reached out about to the corresponding developer(s) which then either give clarification, or care for their removal/replacement (a process that worked out very fine so far). Remaining ones, as pointed out above, are clearly marked or may lead, in serious cases, to the app’s removal from the repo.
Sensitive intent filters
With Android apps, an „intent“ is an abstract description of an operation to be performed. Basically, there are three main use cases for them: starting an Activity, a Service, and to deliver messages to a BroadcastReceiver other apps can subscribe to in order to receive these messages.
There are some intent-filters which will trigger warnings, analog to sensitive permissions, as they might indicate unwanted behavior. These are handled similar to the above: manual check, asking the authors for clarification if needed. Then, either adding them to the allow-list, raise a specific anti-feature – or lead to exclusion of the app.
android.accessibilityservice.AccessibilityService
: this indicates the app makes use of Android’s AccessibilityService. Not necessarily a bad sign, but has its specific risks. Some actions are intentionally implemented to ensure they are performed by the human operating the device directly, and not by some app behind their back. AccessibilityServices provide the means to automate this (intended „to assist users with disabilities in using Android devices and apps“), so it should be ensured an app uses them in a responsible way.android.net.VpnService
: this indicates the app makes use of Android’s VpnService, and thus enables it to intercept network traffic. Only VPN clients should have this. Few exceptions include privacy-protecting apps, e.g. non-root firewalls like Netguard or non-root ad blockers like Tracker Control.android.view.InputMethod
InputMethods (IMEs) are, as their name suggests, for entering data – and the use of Android’s InputMethod framework is thus reserved for keyboard apps like AnySoftKeyboard or HeliBoard – and apps which are obviously intended to provide an input method (e.g. KeePass auto-type). So again, this should be well-justified.
Intent filters are scanned for with the app inclusion checks manually, but also automatically by the update checker for each incoming APK file.
If any of those are used by an app, this must be made transparent. So this „overhaul“ includes to guarantee exactly that: make the reasons visible (in a separate block of the app’s details page on the website of the repo labeled „App configuration & Special Access“, together with above outlined „flags“).
Certificates and Signing
Certificates are used to sign apps. They are usually created by the developers themselves and stored in their private keystores – at least for the APK files provided via the IzzyOnDroid repository. Exceptions are Google’s PlayStore, where Google wants to perform the signing itself – and also F-Droid‘s own repo where F-Droid generates the corresponding certificates if reproducible builds can not be established.
App signing is a security feature. Its purpose is to ensure that the APK cannot be tampered with (so nobody can inject e.g. malicious elements), and that you can check the APK was really provided by the corresponding developer. Updates to an installed app are only accepted if the signature of the corresponding APK file matches that of the already installed app. Which is only the case if both have been signed using the very same certificate. You can check the certificates of APK files yourself, the tool for that is called apksigner
:
$ apksigner verify --verbose --print-certs org.fdroid.fdroid.apk
Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): true
Verified using v3 scheme (APK Signature Scheme v3): true
Number of signers: 1
Signer #1 certificate DN: CN=Ciaran Gultnieks, OU=Unknown, O=Unknown, L=Wetherby, ST=Unknown, C=UK
Signer #1 certificate SHA-256 digest: 43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab
Signer #1 certificate SHA-1 digest: 05f2e65928088981b317fc9a6dbfe04b0fa13b4e
Signer #1 certificate MD5 digest: 17c55c628056e193e95644e989792786
Signer #1 key algorithm: RSA
Signer #1 key size (bits): 2048
Signer #1 public key SHA-256 digest: e3d2cc87a245da2e84d4fb71e527c164e084d48bccf76ffad46ad17f1bfde388
Signer #1 public key SHA-1 digest: 26ef7882633282a9b04688178ee7f372fbec7c3d
Signer #1 public key MD5 digest: 9225fccafb33b605a86cfc09d7f38ec6
When an app is added to the IzzyOnDroid repo, its certificate is pinned: AllowedSigningKeys
is set to the value of Signer #1 certificate SHA-256 digest
. So updates are only accepted into the repository if the certificates match. This is done to prevent an APK that was potentially tampered with from even entering the repository. While such an APK would not be installed (or even offered as an update) for those having the previous version running on their device, keeping it from entering the repository protects those who’d install the app for the first time.
But this is not the only thing checked for at this level. Let’s see what else is done:
Debug Certificates & Weak Certificates
These are only checked for at inclusion time – apps with debug certificates are no longer accepted into the repo. Updates won’t bring those in as the repo already has AllowedSigningKeys
in place for all apps and thus certificates are „pinned“ (see above); APKs with a different signature (switching the signing key definitely causes this) thus would be rejected straight away and not even reach the index. A scan was run over all APKs currently in the repo to find those using a debug key (see scan existing APKs for use of debug keys) and showed there were 112 such APKs initially, 40 of them even with expired debug keys (that does not mean 112 resp. 40 apps, as there can be up to 3 APKs per app in the repo). The latter 40 have been started with: 23 apps obviously no longer maintained (repositories deleted/archived, author not seen for years etc.) have been removed, for the remaining ones I reached out to their authors. Some of them have been fixed within a week, others received no answer at all. The latter were then removed as well if there was no response within a month (or if the authors stated not to address this).
The remaining 72 were addressed in a separate run. Most of those are already solved: usually developers switched to a proper release key, and the debug versions have been removed here. The last APKs signed with a debug key should be gone end of March, 2024.
For those who wonder why this should be an issue, please see:
Further, a proper release key points out in their DN who created the certificate, while debug keys always say it was „Android debug“:
Signer #1 certificate DN: C=US, O=Android, CN=Android Debug
For those interested in technical details, these were the commands used to ferret them out:
apksigner verify --verbose --print-certs $apk |grep "Android Debug"
jarsigner -verify -verbose -certs $apk |grep deny |sed "s/.*denyAfter \(.*\),.*/\1/"
(oldest ones first; what’s expired and is no longer maintained gets removed; only works for apps having a v1 signature, fails for those with only v2/v3; only ran over the APKs detected by the former run which all had a v1 signature)
Hint for developers: as using a different signing key means all folks already using your app have to uninstall the old version before they can install the new one, you might consider using „key rotation“. Key rotation can be done using apksigner
if one has access to both, the original and the new private key. I have not yet tested whether fdroidserver supports this, so be welcome to reach out to me and I can run a test. As key rotation was only introduced with Android 9, this is only possible if the minSdkVer
of your app points to Android 9 or later.
BLOBs in APK signing blocks
APK signing blocks are where signing details are stored in. Fay drew my attention to the fact that there could be "weird stuff" included in APK signing blocks – something few people are aware of (I was not until then). Of course there are also things one would expect here:
OK_BLOCKS = dict(
# https://source.android.com/docs/security/features/apksigning/v2#apk-signing-block-format
APK_SIGNATURE_SCHEME_V2_BLOCK=0x7109871a,
APK_SIGNATURE_SCHEME_V3_BLOCK=0xf05368c0,
APK_SIGNATURE_SCHEME_V31_BLOCK=0x1b93ad61,
VERITY_PADDING_BLOCK=0x42726577,
)
I dug into it, and found even more weird stuff. So checks for that were implemented as well:
DEPENDENCY_INFO_BLOCK
: This is supposed to be a binary representation of build dependencies inserted by Google itself, or also by Android Studio and IntelliJ IDEA (plus probably also some other development tools), when an APK is being signed. But it is also encrypted using a public key owned by Google, so one cannot really verify what else might have been placed there. This means when found (which is very often) I reach out to the corresponding developers, suggesting them to useapksigner
for signing instead, which does not add this block – or to make sure Android Studio resp. IntelliJ IDEA will not include them (see below). Apkverifier includes a short comment in its code, a.o. „The data is compressed, encrypted by a Google Play signing key...“ (source)
So this in essence is a „blob“ without transparency. As it’s encrypted using a Google Play public key, it cannot be decrypted without the corresponding private key – so except for Google, no one can say for sure which other bits might have been added along.GOOGLE_PLAY_FROSTING_BLOCK
: A post on StackOverflow explains: „The frosting block is added by Play Store, so it proves that particular file was downloaded from Play Store, but not all APKs have it yet - APKs that were not updated recently are missing it. Curiously, the frosting includes a metadata chunk encoded using protbuf. The structure is rather complex …“
So the presence of this block means the APK file originated at Google’s Play Store. I didn’t encounter an APK with that yet, so a proper procedure for handling this still needs to be developed. Whenever possible, the solution should be: „Dear developer, please sign the APK yourself and make sure to avoid theDEPENDENCY_INFO_BLOCK
as well“ – which in this case would probably be present, too.SOURCE_STAMP_V1_BLOCK
andSOURCE_STAMP_V2_BLOCK
: SourceStampVerifier.java has some comments on this one: „SourceStamp improves traceability of apps with respect to unauthorized distribution. The stamp is part of the APK that is protected by the signing block. The APK contents hash is signed using the stamp key, and is saved as part of the signing block.“
In the open source world and with free/libre FOSS licenses, there should be no such thing as „unauthorized distribution“: the licenses make clear that the Four Essential Freedoms of Free Software are guaranteed – which include „The freedom to redistribute copies so you can help your neighbor“ (freedom-2) and „The freedom to distribute copies of your modified versions to others“ (freedom-3).
From a quick glance at the code (remember I’m no Java developer) it seems this is to ensure that all split APKs are coming from the same source, and the code to check that is public/FOSS, so I don’t see why this should raise concerns – except for it being inserted by a 3rd party which is most likely Google, who writes: „For apps uploaded as app bundles, we will improve this security by introducing what is called a source stamp. This source metadata is inserted into the app’s manifest by bundletool. When the APK is generated on Play’s server, it’s also signed with a Google key in addition to your app signing key.“ (source) Asbundletool
can be used to insert this, it could also have been the original developer, though.
Again, I have not yet encountered this one and also need more information to decide how it should be handled, but at least it’s checked for and will raise a warning sent to me – like it is for the others.MEITUAN_APK_CHANNEL
: This block includes JSON with some metadata, used by Chinese company Meituan. It e.g. can be found here, and Meituan seems to use it for payload, which clearly makes it a risky one.
There might be other block types, as a developer could just create their own block type (or abuse an existing one) and put anything they want in there, including code. Which is why „unknown blocks“ will also raise a warning, again sent with a report by mail to me. As with sensitive permissions and intent filters, this check is run manually before a new app gets added to the repo, but also automatically for each incoming update. If you want to check an APK for these yourselves, you can find the script used for that here. In this script you also find all currently known blocks, with links to details on them.
As pointed out on DEPENDENCY_INFO_BLOCK
, I recommend using apksigner
for signing to avoid some of those BLOBs right away. According to the official documentation, to keep Android Studio and also IntelliJ IDEA from inserting this block, including the following snippet with your build.gradle.kts
should help:
android {
dependenciesInfo {
// Disable including dependency metadata when building APKs
includeInApk = false
// Disable including dependency metadata when building Android App Bundles
includeInBundle = false
}
}
Thanks to Naveen for pointing this out, and to soupslurpr for the note to handle AABs as well! Meanwhile the approach to exclude the DEPENDENCY_INFO_BLOCK
via the outlined modification of the build.gradle
has been confirmed as a success, and was applied in the source code of several apps present in the IzzyOnDroid repository.
Currently, SigningBlock BLOBs are only recorded to the repository’s APK-Checker database and made visible on the website. How this shall be dealt with in the future is tracked in a separate issue: Dealing with BLOBs found in APK signing blocks.
One more word on this: as the payload example of Meituan shows, this is not just „cosmetic“. One can take an APK that is properly signed by the developer’s key, add something to the signing block – and verification with apksigner
still fully succeeds as if nothing was wrong. The modified app even installs properly on an Android device – so this is something to take quite seriously. Details on this can be found in this POC. A short quote from the POC:
Whether the payload is present or not does not affect the validity of the signature. Thus we get two APKs -- with an identical valid v1+v2+v3 signature -- but one says "nothing to see here..." when you run it, whereas the other says e.g. "This is the payload".
In a test, the EICAR sample was included with an APK modified as outlined above, and uploaded to VirusTotal. Only 2 out of its 64 scanner engines detected it, which means in many cases such a tampering could go totally undetected. But even if more malware scanners would detect it it would be a bad sign as long as signature verification does not: a malicious actor could take the APK of a reputable author, add malware to a signing block and spread the resulting APK, thus affecting the reputation of the original author – who will be thought responsible as the signature „proves“ it was them.
In another test, an ELF binary was injected into a signing block. VirusTotal did not seem to see that at all. Pithus in those cases at least points out the „unknown“ blocks.
So why is this a security issue? Android apps have access to their own APK file, so they can hide payload here (and as the case of the MEITUAN_APK_CHANNEL
shows, some even do). With most scanners ignoring the signing blocks entirely, this poses a risk that should be addressed.
Thanks to Fay for the efforts she put into this!
Reactions by developers I contacted
So far all of my reports, when answered, have been received well and acted upon positively. Either the „potential offenders“ have been clarified (with links to the code and a proper explanation) – or my reports led to improvements of the app itself, as those „offenders“ were removed (partly with code rewrites using less offensive implementations), or by making justified ones more transparent and explaining them to those using the app when the related functionality was triggered (e.g. with android:usesCleartextTraffic
, when a non-secured URL was entered, asking if this was really intended or just a typo (missing the s
in https
) – and sometimes both, as in the described example.
Verdict
Security is no „set-and-forget“, but an ongoing process. This is the third „big round“ of checks put into place at the IzzyOnDroid repository, but it will certainly not be the last. As shown, transparency is an important aspect of this repo, so you’ll be shown „what’s inside“ even if it’s not a security issue – so when encountering something that „looks suspicious“ you’ll at least have an explanation why it is there, hopefully.
Also, „making things safe(r)“ is a learning process. I’m thankful for all help I received from developers explaining me (who’s not an Android dev) backgrounds and whereabouts. And I’m glad I could help some developers with what I’ve learned on the path. This „working together“ I feel is very important as it helps us all to improve – and to make our devices safer places. Let’s keep that up!