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.
It wasn’t until recently that I began to ponder the ramifications of Android applications utilizing various third-party JSON libraries for serialization.
Despite Android having a basic JSON API, you will still see Android applications include and utilize these third-party JSON libraries.
Android’s JSON API does not support object serialization and is relatively basic in its capabilities. Therefore, I believe developers may resort to other libraries for a more robust feature set.
Android does not typically include POP gadgets or chains as most of you have seen in Java over the last few years, so a different approach is required in terms of achieving successful exploitation.
In this blog post, we will cover the types of memory corruption scenarios that can be triggered through deserializing untrusted JSON in the Jackson, FlexJSON, and json-io libraries on Android.
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.
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:
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.”
“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.”
So, let’s take this knowledge into account when searching for classes that can be used in a JSON attack payload.
finalizemethod, the class must operate directly on the target ‘native’ field
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.
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:
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.
unref function casts the field i.e.
familyPtr to a
FontFamily object and the calls the
Unref virtual function on it.
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
destructor function casts the field i.e.
regionHandle into an
SkRegion object and then deletes it.
android.graphics.Path class contains a public zero argument constructor and a public field called
finalize method passes the
mNativePath field as an argument to the native
finalizer method casts the field i.e.
objHandle to a
SkPath object and destroys the object.
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 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
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.
I/DEBUG (26346): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x6666666a
I/DEBUG (26346): r0 00000000 r1 b3d68acc r2 00000000 r3 00000000
I/DEBUG (26346): r4 66666666 r5 b672c028 r6 32d6ca70 r7 66666666
I/DEBUG (26346): r8 00000000 r9 b4828c00 sl 32c01340 fp 32d6ca70
I/DEBUG (26346): ip 00000001 sp b3d68ab0 lr b6729033 pc b6729032 cpsr 60070030
FontFamily class is the most promising in terms of obtaining potential code execution. In each of our target classes, the ‘native’ field is representing an underlying C++ object instance.
FontFamily C++ class inherits from the
anroid::MinikinRefCounted C++ class. So
fontFamily->Unref(); is calling the
MinikinRefCounted::Unref virtual function.
Because we control what is being dereferenced in
R4, this may mean we can achieve code execution via the virtual function call i.e.
The crash occurs within the
delete is called on the
SkRegion object, the skia library has redefined the
delete operator into a function that calls
I/DEBUG (26346): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x66666676
I/DEBUG (26346): r0 66666666 r1 b3d68acc r2 66666666 r3 00000000
I/DEBUG (26346): r4 66666666 r5 6fa51ab0 r6 32d6dad0 r7 66666666
I/DEBUG (26346): r8 00000000 r9 b4a23400 sl 32c01340 fp 32d6dad0
I/DEBUG (26346): ip b628c220 sp b3d68aa8 lr b61612bd pc b61611fe cpsr 60070030
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.
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.
I/DEBUG (26346): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x66666666
I/DEBUG (26346): r0 66666666 r1 b3d68acc r2 00000001 r3 00000000
I/DEBUG (26346): r4 66666666 r5 b6b2a2f0 r6 b6b2a2f4 r7 b6b2a2f0
I/DEBUG (26346): r8 00000000 r9 b4a24000 sl 32c01340 fp 32d84680
I/DEBUG (26346): ip b6f9d24c sp b3d68aa0 lr b6f597cd pc b6153daa cpsr 60070030
SkPath’s destructor calls the subroutine
0xE7E14 passes it a value that we potentially control.
R0 in this context is the
mNativePath field in the payload.
It appears that we may have some control over the
BLX R2 instruction which is most likely a call to the virtual function
The crash stems from the
android::Paint C++ class’ destructor.
I/DEBUG (26346): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x666666ee
I/DEBUG (26346): r0 666666da r1 b3d68acc r2 66666666 r3 666666da
I/DEBUG (26346): r4 66666666 r5 6fa4dd90 r6 32d72a10 r7 66666666
I/DEBUG (26346): r8 00000000 r9 b4828c00 sl 32c01340 fp 32d72a10
I/DEBUG (26346): ip b6f9cdd0 sp b3d68ab0 lr b6f59321 pc b6f56c6a cpsr 00070030
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.
finalizer 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
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
we might actually have some control over a virtual function call.
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.
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.
Subscribe to VerSprite’s Security Blog and stay tuned for our next post, where we will dig into what is required in a few different contexts to achieve successful exploitation.
VerSprite's Research and Development division (a.k.a VS-Labs) is comprised of individuals who are passionate about diving into the internals of various technologies.
Our clients rely on VerSprite's unique offerings of zero-day vulnerability research and exploit development to protect their assets from various threat actors. From advanced technical security training to our research for hire B.O.S.S offering, we help organizations solve their most complex technical challenges. VerSprite's Research as a Service →