Java JSON Deserialization: Memory Corruption Vulnerability
Researching Java JSON Deserialization
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 Java JSON Deserialization 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.
Constraints and Target Identification with Java JSON Deserialization
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 in Java JSON Deserialization
FontFamily
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.
Region
The android.graphics.Region
class contains a public zero argument constructor and a public field called mNativeRegion
. The class’ finalize
method 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.
Path
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.
Paint
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:
Pretty basic.
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.
Crash Triage Related to Java JSON Deserialization
FontFamily
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
The 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.
The 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. this->UnrefLocked();
Region
The crash occurs within the libskia
library.
Even though delete
is called on the SkRegion
object, the skia library has redefined the delete
operator into a function that calls sk_free
.
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.
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.
Path
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 SkPath::validate
.
Paint
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
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 PaintGlue
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 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 of Java JSON Deserialization
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.
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.
References
- https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf
- https://versprite.com/blog/application-security/experiments-with-json-io-serialization-mass-assignment-and-general-java-object-wizardry
- https://www.usenix.org/system/files/conference/woot15/woot15-paper-peles.pdf
- https://speakerdeck.com/pwntester/friday-the-13th-json-attacks
- http://seclists.org/fulldisclosure/2014/Nov/51
- https://github.com/jdereg/json-io
- http://flexjson.sourceforge.net/
- https://github.com/FasterXML/jackson
- http://androidxref.com/5.1.1_r6/
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /