The protobuf library includes the "javanano" compiler, commonly used in many Android applications (due to its tiny resource footprint). The "javanano" compiler supports a variety of Android-specific compilation flags which can be used to modify the generated message classes.
One such compilation flag is "parcelable_messages". Enabling this flag causes the generated Android classes to implement the Parcelable interface, allowing them to be serialized into Parcels (and placed in Bundles). By default this flag is switched off.
Message classes that are generated using the "parcelable_messages" option either extend "ParcelableExtendableMessageNano" (if they need to support unknown fields) or directly extend "ParcelableMessageNano". Both of these classes use the helper class "ParcelableMessageNanoCreator" in order to serialize the nano messages to a Parcel and deserialize them back again.
In order to keep track of the specific message class serialized to a Parcel, the "ParcelableMessageNanoCreator" class writes the class name of the serialized message to the parcel, directly followed by a byte array containing a marshalled representation of the message.
Here is a snippet of the corresponding unparcelling code from "ParcelableMessageNanoCreator":
```
1. public T createFromParcel(Parcel in) {
2. String className = in.readString();
3. byte[] data = in.createByteArray();
4. T proto = null;
5. try {
6. Class<?> clazz = Class.forName(className);
7. Object instance = clazz.newInstance();
8. proto = (T) instance;
9. MessageNano.mergeFrom(proto, data);
10. } catch (...) { ... }
11. return proto;
12. }
```
In order to load the nano message's class, the method calls "Class.forName" on the supplied class name in the parcel. This causes the static initializer of the class to be called if it hadn't been invoked before. Next, the method calls the class's default constructor to create a new instance. Only then does the method attempt to cast the newly created instance to the expected message type.
Since the name supplied in the Parcel is attacker-controlled, this allows an attacker to supply any class name. This, in turn, will cause the unparcelling application to call the static initializer and instantiate any chosen class.
Many Google applications on Android use the "javanano" compiler with the "parcelable_messages" option. I've downloaded the latest Pixel XL firmware image (marlin-nde63h), and ran a script to find all the applications in which the "javanano" compiler is used:
```
GoogleHindiIME.apk
YouTube.apk
LatinIMEGooglePrebuilt.apk
SystemUIGoogle.apk
AndroidPlatformServices.apk
KoreanIME.apk
GmsCoreSetupPrebuilt.apk
GCS.apk
Tycho.apk
GooglePinyinIME.apk
CalendarGooglePrebuilt.apk
```
Since Android allows Bundles to be included in Intents (the intent's "extras"), this allows an attacker to write a fake Parcelable instance of a nano message (with a chosen class name) into an intent's extras and send it to an application. If the application accesses the intent's extras in any way, this will cause the Bundle to be unparcelled within the application, triggering the class loading and instantiation.
I'm attaching two small applications which can be used to demonstrate this issue.
This first, "TestBundleRecv", includes three classes:
```
-A compiled parcelable nano message class: com.example.laginimaineb.testbundlerecv.nano.ProtoObjOuter$ProtoObj
-A "privileged" class: com.example.laginimaineb.testbundlerecv.PrivilegedClass
-A simple broadcast receiver: com.example.laginimaineb.testbundlerecv.TestReceiver
```
When the application receives a broadcast, it accesses the intent's extras (causing the bundle to be unparcelled).
The second, "TestBundleRecv", includes a "fake" copy of the nano message class contained in the first application (com.example.laginimaineb.testbundlerecv.nano.ProtoObjOuter$ProtoObj). However, instead of normally serializing it to a parcel, this class writes the name of the previously mentioned privileged class ("com.example.laginimaineb.testbundlerecv.PrivilegedClass") to the parcel. Then, the application simply adds the new instance of this fake class to a new intent's extras and broadcasts that intent to the first application's broadcast receiver.
When the intent is received in TestBundleRecv's broadcast receiver, the extras bundle is unparcelled, causing "ParcelableMessageNanoCreator" to load and instantiate the privileged class (this will also print a log message for visibility).
This issue can be addressed by using the same patterns used in Android's "Parcel.readParcelable" (see http://androidxref.com/7.0.0_r1/xref/frameworks/base/core/java/android/os/Parcel.java#2486). Namely, the alternate signature of "Class.forName" allows to explicitly prevent the loaded class's static initializer from being called. Then "Class.isAssignableFrom" can be used in order to make sure the provided class in fact extends the nano message's type.
[附件:JavaNanoPoC.tar.gz](https://bugs.chromium.org/p/project-zero/issues/attachment?aid=260763)
暂无评论