I began researching a few Java JSON deserialization libraries back in 2013.
More specifically, I asked what types of vulnerabilities could exist if an attacker had control over the JSON being deserialized.
Since then, there has been a great deal of discussion around JSON attacks against both .NET and Java JSON libraries.
My biggest takeaway at the time was the ability to control instance data on an instantiated object via a setter method in both the Jackson and JsonIO libraries, which is also covered by Alvaro Munoz in Friday the 13th: JSON Attacks.
Attacking deserialization on Android is certainly nothing new.
In the previous Android deserialization research, the root problem is the deserialization of an untrusted object in which the attacker has control over a field that is essentially an address for native backed memory, which is then operated on within the class’ finalize method once garbage collection kicks in.
Taking the previous research into consideration, this research explores the memory corruption scenarios that occur when deserializing untrusted JSON that represents a class with a controllable ‘native pointer’ field.
Before we dive into the technical details, let’s talk about my testing environment. For the sake of simplicity, I started all of this research on a Nexus 5 running 5.1.1.
The reason why I chose to do that will become clear in the later parts of this blog series when we eventually dive into garbage collection internals.
Alright, let’s get into it.
Constraints and Target Identification
In order to attack the deserialization process for each of these JSON libraries, we need to construct a JSON payload that will trigger the instantiation of a target class.
However, we have a few important constraints regarding the Java class structure based on the deserialization requirements for each library.
Therefore, we must abide by these constraints:
The class must contain a public or private zero argument constructor
In order set a private field, there must be an appropriate public setter method
Public fields are fair game
These constraints exist because of each of the library’s deserialization mechanics. Internally they use reflection in order to take the JSON representation of the target class and construct a valid instance.
“All objects that pass through the deserializer must have a no argument constructor. The no argument constructor does not have to be public. That allows you to maintain some encapsulation. JSONDeserializer will bind parameters using setter methods of the objects instantiated if available. If a setter method is not available it will using reflection to set the value directly into the field.” – FlexJSON
“There are times when json-io cannot instantiate a particular class even though it makes many attempts to instantiate a class, including looping through all the constructors (public, private) and invoking them with default values, etc.
However, sometimes a class just cannot be constructed, for example, one that has a constructor that throws an exception if particular parameters are not passed into it.” – json-io
So, let’s take this knowledge into account when searching for classes that can be used in a JSON attack payload.
The class must contain a public or private zero argument constructor
The class must contain a field that we can set and represents something ‘native’
The class must also contain an overridden finalize method
In that finalize method, the class must operate directly on the target ‘native’ field
Why the Finalize Method is Important
Finalizers are called when the Android Runtime decides that a certain instance should be garbage collected.
The main purpose of a finalize method or ‘finalizer’ is to release or destroy all resources used by the object before it is freed.
Android includes a class called the FinalizerDaemon that runs in the back and is responsible for pulling FinalizerReference instances from a queue, obtaining the object that the FinalizerReference was created for and calling its finalize method.
FinalizerReferences are created for any class that includes an overridden finalize when instantiated.
Now that we understand what a ‘finalizer’ is and what a finalize method is supposed to do, let’s talk about the desired functionality we want to see in a class that meets our requirements and how the Java Native Interface comes into play.
Ideally, the finalize method will take the field (which is supposed to effectively be a native memory address) and pass it to a JNI method.
Within the native portion of the JNI method we will be able to trigger memory corruption because the code will somehow operate on the native field that we can control through deserialization.
Here are the steps I took to search for the target classes:
Download Android’s framework source
Use find, xargs and grep to search for fields that include native in their names
Use find, xargs and grep to search for classes that included the finalize method
Target Class Analysis
The android.graphics.FontFamily class contains a public field called mNativePtr. The class also contains a finalize method that passes the mNativePtr field as an argument to the nUnrefFamily native function. This call also contains a public zero argument constructor.
The unref function casts the field i.e. familyPtr to a FontFamily object and the calls the Unref virtual function on it.
The android.graphics.Region class contains a public zero argument constructor and a public field called mNativeRegion. The class’ finalizemethod passes the mNativeRegion field as an argument to the nativeDestructor method.
The native destructor function casts the field i.e. regionHandle into an SkRegion object and then deletes it.
The android.graphics.Path class contains a public zero argument constructor and a public field called mNativePath. Its finalize method passes the mNativePath field as an argument to the native finalizer method.
The native finalizer method casts the field i.e. objHandle to a SkPath object and destroys the object.
The android.graphics.Paint class contains a public field called mNativePaint and a public zero argument constructor. The finalize method passes the mNativePath field to the native finalizer method.
The native finalizer method casts the field i.e. objHandle to a Paint object and deletes it.
Now that the target classes have been selected, we need to work on generating test-cases for attempting to trigger memory corruption.
Obviously, the simplest way to create a payload for any one of these classes is to select one of the JSON libraries and serialize the target class with its serialization API.
In this example, I’m going to utilize the FlexJSON library.
The JSON output is the following:
Now we just need to manipulate the address of the mNativePtr field and deserialize this JSON to ensure the FontFamily class gets instantiated.
To trigger memory corruption, we are relying on the finalize method for a target class in a test-case to be invoked.
In my initial test-case generation process, I was running all of my experiments in a dummy Android application.
When the target class is instantiated, because the class has an overridden the finalize method, internally the ART will create a FinalizeReference handle.
This handle is used in order process the code in the overridden finalize method if and when garbage collection kicks in.
All we need to do is force garbage collection after the object has been instantiated and the finalizer should be invoked.
The crash occurs in the libminikin library within the android::MinikingRefCounted::Unref virtual function.
R0 is under our control, thus resulting in an invalid read at the LDR R0, [R0, #0x10] instruction. This subroutine is called from the SkRegion::freeRuns virtual function. What is especially interesting about the SkRegion::freeRuns virtual function is the subroutine it calls at the end of its graph.
The subroutine 0x1E1818 will attempt to call sk_free on a value we control. This means we may have an arbitrary free primitive that we could utilize.
The crash occurs in the SkPath C++ class’ destructor.
The android::Paint::~Paint destructor calls subroutine 0x8FC68 and passes the value in R0 after adding 0x74 to it. R0 contains the address held by mNativePaint. The crash will occur at the LDR R0, [R0, #0x14] instruction when the subroutine attempts to dereference an invalid address.
The PaintGluefinalizer method will call the delete operator on the android::Paint instance. The delete operator has been redefined and will call the android::Paint::~Paint destructor which in turn calls the SkPaint::~SkPaint destructor.
We observe here in the source code that the destructor goes through a handful of instance variables and calls SkSafeUnref on them.
Assuming we actually get this far, we now control R0. If we track into SkPixelRef::globalUnref,
we might actually have some control over a virtual function call.
Conclusion & Takeaways
My objective with part one of this blog series was to cover initial exploration of the research. We accomplished a few important things, so let’s list them out.
Identified target classes that fall within the deserialization constraints set forth by the target JSON libraries
Public or private zero argument constructor
Public field that represents a native pointer
Overridden finalize method that operates on this public field
Generate our test cases for the target classes and trigger memory corruption for each
Analyze the internal workings around why each crash exists
Let’s be honest, there are still a lot of questions to be answered here. Probably the most important question is whether or not any of these crashes are exploitable.