JSON Deserialization Memory Corruption Vulnerabilities on Android JSON Deserialization Memory Corruption Vulnerabilities on Android

Analyzing JSON Deserialization Memory Corruption Vulnerabilities on Android

Written By: Ben Watson

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 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

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:

  1. The class must contain a public or private zero argument constructor
  2. In order set a private field, there must be an appropriate public setter method
  3. 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.

  1. The class must contain a public or private zero argument constructor
  2. The class must contain a field that we can set and represents something ‘native’
  3. The class must also contain an overridden finalize method
  4. 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

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.

font_family_finalize

The unref function casts the field i.e. familyPtr to a FontFamily object and the calls the Unref virtual function on it.

font_family_unref

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.

region_finalize

The native destructor function casts the field i.e. regionHandle into an SkRegion object and then deletes it.

region_native_dtor

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.

path_glue_finalizer

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.

paint_finalize

The native finalizer method casts the field i.e. objHandle to a Paint object and deletes it.

paint_glue_finalizer

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.

flex_serialize

The JSON output is the following:

font_family_json

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.

flex_json_serialize

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

FontFamily

font_family_payload

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

font_family_unref_ida

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.

font_family_unref

minikin_unref

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

region_payload

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

region_crash

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.

free_runs_ida_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.

sk_free

Path

path_payload

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_dtor

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.

skpath_virtual_func_call

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.

skpath_dtor

Paint

paint_payload

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

paint_dtor_ida

paint_crash

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.

skpaint_dtor

skpaint_dtor

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.

pixel_unref_blx

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.

  1. Identified target classes that fall within the deserialization constraints set forth by the target JSON libraries
    1. Public or private zero argument constructor
    2. Public field that represents a native pointer
    3. Overridden finalize method that operates on this public field
  2. Generate our test cases for the target classes and trigger memory corruption for each
  3. 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

Protect Your Assets from Various Threat Actors

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 →

We are an international squad of professionals working as one.

logos