x
java反序列化
ObjectOutputStream用于将java对象的基本数据类型和图像写入OutputStream,,实现对象的序列化
测试失败
问了ai
错误发生在尝试反序列化一个包含 AnnotationInvocationHandler 的对象时
反序列化两个条件
- 入口实现序列化接口
- 重写readObject函数
首先找到接收流new ObjectInputStream(流).readObject()
然后找到序列化入口类,最终执行危险方法Runtime.exec("calc")
transfomer接口(输入对象返回对象)
反射
把java类中的各种成分映射成java对象,手动编写的类别编译后会产生一个Class对象,表示创建的类的类型信息,Class类的对象作用是运行时提供或获得某个对象的类型信息
我靠太困难了读不动类加载器
类加载到堆区有实例对象和Class对象
反射包中常用的类:Constructor,Field,Method
InvokerTransfomer
输入一个对象,通过反射机制调用对象中的一个方法
1 | Class cls = inputClass.getClass(); |
命令执行危险代码
1 | Runtime.getRuntime().exec("calc"); |
Runtime.getRuntime()返回当前java进程的Runtime对象
通过反射达到相同效果
1 | InvokerTransfomer invokerTransfomer = new InvokerTransfomer("exec",new Class[](String.class),new Object[]{"calc"});//方法,参数类型,参数 |
然后在map包下的TransfomeredMap类寻找哪里调用了transfomer()方法
最终找到checkSetValue()方法,再找一个赋值函数,把invokerTransfomer赋值给checkSetValue方法里面的valueTransfomer,或者找valueTransformer的传参入口
找到了一个装饰方法decorate
但是由于checkSetValue也是私有方法,所以继续找在哪里运用了此方法
最终在AbstractinputCheckedMapDecorator类里的MapEntry中找到,当调用MapEntry中的setValue方法就会调用到checkSetValue方法
map(TODO)
最常用的集合类是List和Map
当通过键去查对应的值时,使用List遍历效率很低,这时Map这种键值映射表的数据结构,可以快速查找值
学不动,继续往下看
TransfomeredMap是一个实现了map接口的类,可以把它当作一个map去操作
TransfomeredMap把map传给了它的父类AbstractInputCheckedMapDecorator,再传给它的父类AbstractedMapDecorator,然后把map赋值到自身的属性
AbstractInputCheckedMapDecorator装饰器模式,里面重写了MapEntity的setValue方法(加了一层检查checkValue)
装饰器模式
用decorator(map)传入一个map,然后返回给你一个map,中间就是对输入的map做了一些增强
1 | protected abstract Object checkSetValue(Object var1); |
AbstractInputCheckMapDecorator定义了抽象方法checkSetValue(未执行具体代码)
一些共有属性和方法可以被综合在一起定义成抽象类减少代码量
map设置值是防止它抛出异常
然后再找哪个类调用setValue方法,找到AnnotationInvokerHandler类,它既可以序列化,又重写了readObject方法,还调用setValue方法
然后发现我jdk版本装错了,jdk版本要在1.7.x及1.8.0_71以下版本,我的是1.8.0_91
不是8u64下载后怎么是8u111啊
C:\Program Files\Java\jdk1.8.0_91
仔细观察这个链接,路径上有/cn/,说明是中文官网
我们将这个去掉,即https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
来到英文官网就可正常下载了
好的又过了一天环境配好了,踩了好多坑
项目里面的sdk用24高版本,因为idea是用17写的
模块里面的依赖是1.8低版本,和博客或视频里面说的完全不一样,依旧不知道为什么
不同类的同名函数,任意方法调用(反射/动态加载字节码)
输出
入口点:functors(Commons Collections自定义的一组功能类)定义了一个Transformer的接口,作用就是接受一个对象,然后调用transform方法,对接受的对象进行操作,有点像装饰器或者代理的功能
查看Transformer的实现类有哪些,发现invokerTransformer,可以看见它接收一个对象,然后反射调用,方法,值,参数类型都是可控的
java中命令执行代码是
1 | Runtime.getRuntime().exec(); |
如何通过反射实现命令执行
1 | Runtime runtime = Runtime.getRuntime();//已有一个getRuntime对象,如何通过反射调用方法? |
然后改成invokerTransformer方法实现的代码,首先我们知道要调用invokerTransformer类的transformer方法,transformer方法接收一个对象,然后看一下InvokerTransformer的构造函数怎么写
发现需要传参数名,参数类型,参数值
1 | new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transformer(runtime) |
参数名是calc,参数类型是数组,参数值也是数组(看源码接收数据) xx.class是可以在运行时传递和使用的对象
这一行代码可以代替
1 | Class c = Runtime.class; |
然后我们需要回头找还有哪一个类里面或者方法里面调用了transform(查找用法)
发现map包下transformedMap类里面的checksetvalue方法里面调用了valuetransformer对象的transform方法,再找vT是哪里来的,往上找到是通过构造函数传过来的,那就是只需要在vT位置传一个IT对象(上文通过反射获取的方法信息),但是构造函数是protected类型,只能被自己调用,所以这个肯定还有某个函数被调用时调用它,上翻找到静态方法(可以直接调用)decorate,它完成了刚才装饰的操作
然后去掉后面调用的transform方法,开始写代替代码,先把IT new成一个对象由IT类型的it接收,然后新建一个map类型的对象,新建一个hashmap,调用装饰方法 ,因为csv只对value操作了,所以key先不赋值了
1 | new HashMap<>().var |
entry是hashmap遍历时一个键值对就叫一个entry,先给map赋个值
1 | map.put("key","value"); |
开始遍历map
1 | for(Map.Entry entry:map.entrySet()){ |
这就是遍历map的一种写法
这里可以看到AICMD里面的setValue实际上就是entry里的setValue,AICMD重写了setValue方法,正常来讲,我们只需要遍历这个被修饰过的map,它就会走到AICMD重写的这个sV方法里面,然后调用csv
所以就把decorate方法返回的map赋个值,遍历一下,写好泛型,然后循环里面setvalue,这时就调用的是重写的setvalue方法,我们已经找到一半了,如果有一个遍历数组的地方,然后它调用了setvalue,就可以执行后半条链了
然后找到了annotationInvocationhandler有一个遍历map entry的功能,看这个类的名字就可以大概知道它是动态代理的的调用处理器类,看到构造函数接收class对象和一个map对象,map对象可以传decoate增强过的map对象,tape是继承了注解的泛型,接下来就实例化这个类,注意它不是public的,是default类型的,只能在这个包里调用,所以就要用反射去获取他
1 | Class c = Class.forname("") |
然后反射创建它的构造器,构造器也不是公有的所以declare
去看他构造方法接受的参数,是class和map,然后就实例化了newInstance,然后序列化和反序列化
但是还有一个问题就是setvalue里面要传getruntime对象,但是annotation里面的setvalue里面传的对象控制不了,还有就是runtime对象是不能在序列化的,没有继承serliseable接口,所以继续通过反射,还要过这两个if,先解决不能序列化的问题
1 | Class c = Runtime.class; |
然后在对runTime对象反射调用exec方法
1 | Method method1 = c.getMethod("exec",String.class); |
这是正常的反射,然后改成IT版本的,为了反序列化时能在readObject里面触发
1 | Method getRuntimeMethod = (method) new InvokeTransformer("getRuntime",new Class[]{String.class,Class[],class},new Object[]{"calc"}).transform(Runtim.class); |
然后在调用invoke方法
1 | Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null } |
如有错误,多多指教