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
finalizemethod - In that
finalizemethod, 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’ 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.
![]()
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/
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /