Co-authored by Roee Hay.
Over 55 percent of Android phones are at risk of a high-severity serialization vulnerability that IBM’s X-Force Application Security Research Team found in the Android platform. In a nutshell, advanced attackers could exploit this arbitrary code execution vulnerability to give a malicious app with no privileges the ability to become a “super app” and help the cybercriminals own the device. In addition to this Android serialization vulnerability, the team also found several vulnerable third-party Android software development kits (SDKs), which can help attackers own apps.
We recently saw a similar attack technique exposed as part of the Hacking Team leak. That exploit used a fake news app called BeNews that was built to bypass Google Play’s filtering by requiring a benign set of privileges. Once the user ran the app, it then downloaded additional code with an exploit used to escalate permissions using the Futex vulnerability (CVE-2014-3153). What our team found has not been seen in the wild yet but shows that with the right focus and tools, malicious apps have the ability to bypass even the most security-conscious users.
One Class to Rule Them All
Our team titled the paper “One Class to Rule Them All” since the single vulnerable class that we found in the Android platform, OpenSSLX509Certificate, was enough to take over the device using our attack technique. Developers take advantage of classes within the Android platform and SDKs. These classes provide functionality for apps — for example, accessing the network or the phone’s camera. The vulnerability we found can be exploited by malware through the communication channel that takes place between apps or services. As the information is broken down and put back together, malicious code is inserted into this stream, exploits the vulnerability at the other end and then owns the device.
We are presenting our research paper on this new vulnerability at USENIX WOOT ’15 in Washington, D.C. This is our third consecutive appearance at WOOT, and as always, we worked closely with the vendors to ensure patches were out before the vulnerability disclosure.
The first Android serialization vulnerability (CVE-2015-3825) lies within the Android platform itself, affecting versions 4.3 to 5.1 — or Jelly Bean, KitKat and Lollipop — as well as M Preview 1. This vulnerability impacted more than 55 percent of Android devices at the time of discovery and allowed arbitrary code execution in apps and services on the device. They can be exploited by malware installed on the victim’s device.
Our paper describes a reliable proof of concept (PoC) that demonstrates the feasibility of the attack. It does not include any exploit code. The PoC exploit we created attacks the highly privileged system_server process. Exploiting system_server allows for privilege escalation to the system user with a rather relaxed SELinux profile (due to system_server‘s many responsibilities), which enables the attacker to cause a lot of damage.
For instance, an attacker can take over any application on the victim’s device by replacing the target app’s Android application package (APK). This can then allow the attacker to perform actions on behalf of the victim. In addition, we were able to run shell commands to exfiltrate data from all applications installed on the device by exploiting the Android Keychain app. We could also change the SELinux policy and, on some devices, also load malicious kernel modules.
How It Works
Below you can find a video demo of our PoC. Once our malware is executed, it replaces a real app with a fake one, allowing the attacker to exfiltrate sensitive data from the app and/or create a perfect phishing attack. We replaced the real Facebook app with a fake one called Fakebook.
The other vulnerabilities are in third-party Android SDKs and allow arbitrary code execution in the context of apps that use these SDKs. This executed code can, for example, steal sensitive information from the attacked app.
The discovered vulnerabilities are a result of the attacker’s ability to control pointer values during object deserialization in arbitrary apps’ memory space, which is then used by native app code invoked by the runtime’s garbage collector (GC).
We extended the research done by Jann Horn back in 2014. Horn showed that Android allowed deserialization of any class, even non-Java serializable ones, in the context of the attacked app or service leading to remote code execution. Even on a patched system (one that does not allow you to load nonserializable classes), one vulnerable serializable class is enough for a complete takeover.
Android IPC, Java Serialization and Garbage Collection
Android apps are sandboxed for security. However, they can interact using messages called Intents, which carry both the destination and the payload. Complex data types must be serialized by the sender and then deserialized by the recipient. In order to ease things for the developer, there is a built-in support for serialization in Java.
For example, serialized (and other) objects can be added to the Intent’s extras bundle as follows:
- SerializableType obj = …
- Bundle b = intent.getExtras();
- b.putString(“foo”, “some string”);
- b.putSerializable(“bar”, obj);
The recipient will have a very similar code:
- Bundle b = intent.getExtras();
- String foo = (String)b.getString(“foo”);
- SerializableType obj = (SerializableType)b.getSerializable(“bar”);
Now, due to the way Android works, all of the bundle’s values will be instantiated as soon as any of the bundle’s values is touched. In our example, touching the “foo” member in line six also instantiates our “bar” serializable object.
Objects in Java are managed so the developer does not need to care about memory de-allocation done by the GC. In some cases, however, developers do need to react to the GC event. For instance, if the object uses some native (e.g., ARM, x86) code through the Java native interface (JNI), the native, architecture-dependent code may need to free native memory allocations. Another example is an object that creates temporary files, which may want to delete them before it is freed.
A Possible Weakness
During the research, we had the following insight: If we found a serializable class that, during its finalize method, freed some native object whose pointer we control (given by a class field), then we could gain code execution in the context of the attacked app or service regardless of the Android patch that was released due to Horn’s discovery.
If we found such a class in the Android framework, then it would affect any app or service that touches an incoming bundle, either from Intent or by other Android Interface Definition Language Interprocess Communication (AIDL IPC). If we found such a class in a third-party SDK, it would affect any app that used that SDK. We therefore ran two different experiments.
The Android Serialization Vulnerability (CVE-2015-3825)
The goal of the first experiment was to find a vulnerable Android framework class available to any application or service. In order to find such a class, we gathered a list of all the loadable classes by parsing the boot.art file found in an Android 5.1.1 Nexus 5 device. We found 13,321 classes that we then scanned using a small Android app that we wrote. The app loads the classes one after another by using Java reflection. For each class, it checks that it:
- Is serializable;
- Contains a finalize method;
- Contains an attacker-controlled field.
Our code found two classes, only one of which was interesting: OpenSSLX509Certificate. Only a short glimpse at OpenSSLX509Certificate was needed to leave us overwhelmed — I had an attacker-controllable pointer (mContext) used in native code during the finalize method!
We then dove into the native code, which showed us that we can decrement a positive integer value at an arbitrary address given by the pointer. Chaining the decrements and further optimization described in the research paper allowed us to achieve an arbitrary memory overwrite, a building block used by our PoC exploit.
Our PoC exploit uses the fact that all apps and some services, including our malicious process, are forked from the Zygote process. Since all of its forked processes inherit the same memory layout, it makes the address space layout randomization (ASLR) effectively useless.
Our exploit targets system_server. It achieves code execution by overwriting a callback function pointer and then triggering a call to that pointer. This allows us to control the program counter (PC). We point the PC to a gadget that does the Stack-Pivoting technique. Our return-oriented programming (ROP) chain then allocates a memory page as executable, which is allowed as per system_server‘s SElinux policy, and copies our payload to that page. We then jump to the payload, which can do a lot of malicious stuff, including:
- Replace the code (APKs) of arbitrary apps;
- Exfiltrate data of arbitrary apps (e.g., by exploiting Android KeyChain app, which is allowed to run shell commands and access data);
- Change SELinux policy rules;
- Load arbitrary kernel modules in devices with kernel compiled with CONFIG_MODULES.
The SDK Vulnerabilities (CVE-2015-2000/1/2/3/4/20)
As previously mentioned, vulnerable classes can be found in specific apps or frameworks, implying a more restricted targeted attack. We therefore decided to analyze 32,701 popular Android apps from top developers in order to find such classes. Since using our aforementioned runtime technique to conduct this experiment would take hours to complete, we decided to use a different approach. We created a tool that runs dexlib2 over the apps’ dex files in a mere 93 minutes. The experiment is so fast because it simply performs a very shallow static analysis, whereas adhering to the previous experiment’s technique would have required installing each app on an Android device — an incredibly slow process.
We found a total of 358 classes over 176 APKs that met our criteria (i.e., serializable with a finalize method and an attacker-controllable field). Our analysis had quite a few interesting findings, including 6 vulnerable SDKs:
- Jumio (CVE-2015-2000)
- MetaIO (CVE-2015-2001)
- PJSIP PJSUA2 (CVE-2015-2003)
- GraceNote GNSDK (CVE-2015-2004)
- MyScript (CVE-2015-2020)
- esri ArcGis (CVE-2015-2002)
In addition, we found out that the Google Play Services APK also included the vulnerable OpenSSLX509Certificate class.
We then wanted to see if these SDKs had something in common. The first five indeed did! They all use SWIG, an interoperability tool that connects C/C++ code with a variety of high-level languages including Java. Under certain developers’ provided configuration, SWIG can generate the following vulnerable code:
Google has fixed the two OpenSSLX509Certificate instances by adding the transient modifier to the mContext member. Google has patched Android 5.1 and 5.0 (commit ID de55e62f6c7ecd57d0a91f2b497885c3bdc661d3) and has also backported the patch to Android 4.4 (commit ID b9d6334acde7460502face82417de40e438a3f4). The patch is also available on Android M (build MPZ79M).
We also reported the issues to the relevant SDK vendors or code maintainers who provided patches. Jumio removed the vulnerable SWIG classes, while esri has patched ArcGis Runtime SDK by adding the transient modifier to the native pointer. MyScript and GraceNote fixed their SDKs by adding the transient modifier to the sensitive variables. PJSIP patched PJSUA2 by overriding the readObject/writeObjects methods, effectively making the vulnerable class nonserializable. The MetaIO SDK was fixed as well, as it no longer implements the serializable interface.
Since the generated vulnerable code was due to bad configuration given by the developer, we do not consider SWIG to be vulnerable. This is somewhat analogous to blaming a compiler for buffer overflows. However, even the most competent developers could miss the fact that they accidentally extended a serializable class. Therefore, we decided to contact the SWIG team, which released a more secure version, 3.0.7. The patched version generates swigCPtr with the transient modifier.
While the patches fixed the specific instances that we had found, we feel that a general problem deserves a general mitigation, reducing the impact of such serialization attacks. Since bundles are very common in Android’s IPC, we suggest changing the bundle’s behavior from one that automatically instantiates all of its values to a lazy approach, such as retrieving only the values of keys it is asked for. By design, the problem will still remain, but it will depend more on specific developers’ code so fewer apps will be at risk if another vulnerable class is found, significantly narrowing the attack surface.
In addition, further protection that prevents arbitrary readObject/Replace, writeObject/Replace methods to be called can be achieved by deprecating the current getParcelable and getSerializable methods of both bundle and parcel, and changing the APIs to include the class name of the expected objects.
Mitigating exploitation is also possible. First, as explained previously, ASLR is pretty much useless in Android if the attacker can launch malware. Lee et al. provided a secure replacement for the insecure Zygote implementation. In addition, as our PoC exploit overwrites a function pointer, pointer integrity mechanisms are also possible. One might think that callback pointers must be writable because of their functionality, i.e., they have to be dynamically changed throughout program lifetime; however, this does not have to be the case.
One mechanism that solves this problem is keeping the memory area that holds the function pointers to read-only. The memory will become writable by the callback setter functions just for the period of changing the values. A different mitigation approach discussed by Stefan Röttger proposed white-listing function pointer values and identifying cases where the function pointers are being abused. Another interesting anti-exploitation mitigation is that fields will be transient by default (i.e., an opt-in approach as opposed to the current opt-out).
In Android 5.0, the WebView component moved to an updatable APK, decoupling it from the rest of the system. This had quite a few significant advantages with respect to security. First, it allowed Google to release security patches much faster. Second, it tackled the Android fragmentation problem since patches could be backported for old Android versions and delivered to various devices all at once.
We encourage Google to continue its efforts toward decoupling the vendors’ dependent code from the rest of the system so patches will be available much faster. Our case showed the significance of such a separation, as the Google Play Services instance of OpenSSLX509Certificate was updated in multiple Android versions three days after our report.
As opposed to vulnerabilities found in final products, such as operating systems or applications where an automatic update mechanism is usually available, the situation is by far worse for SDKs. One vulnerable SDK can affect dozens of apps whose developers are usually unaware of it, taking months to update. For example, a recent X-Force study showed that a high-severity vulnerability found in Apache Cordova for Android still affected dozens of Android apps even though it had been patched for months. The situation is most frustrating for apps that use orphan SDKs or ones that no longer receive security updates.
Developers should be aware that depending on third-party SDKs comes with significant risk. They should prepare for alternatives and choose their SDKs wisely. Developers also deserve better tools. Gradle and its third-party plugin, Gradle Versions Plugin, which notify the developer when a new version is available for a used SDK, are a pretty good start.
Or Peles (@peles_o), X-Force Application Security Research Team, IBM Security.
Roee Hay (@roeehay), X-Force Application Security Research Team, IBM Security.