java反序列化入门篇-02-反射 前言 在基础篇-01中我们将大致的反序列化架构和涉及的知识都介绍了一下,但想要到后面的CC链的学习,光有点皮毛还是不够的,后面几篇我们就逐步给大家丰富一下,这篇先从比较好理解的反射入手,由于前文有一定的介绍,所以后面可能不会反复提及基础的方法调用,反射方法等等,忘记的师傅们可以再翻一下第一篇
Java 命令执行的三种方式 说是三种方式,但我认为只是同一条利用链的分段罢了,至于为什么这么说,通过源码来给大家捋一下
我们平日最常利用Runtime类的exec方法来进行命令执行,我们去到这个方法的具体实现里面看看
这里返回的是ProcessBuilder对象调用start方法的返回值,这个ProcessBuilder其实就是我们后面要讲的第二个方法所要利用的类,我们先去start方法里看看
奖池还在叠加,又冒出来一个新的类ProcessImpl,而它也是我们第三个方法所要讲到的类
而所有的命令进程创建与命令执行都是在ProcessImpl的构造函数中实现的,代码篇幅较长就不贴出来了,感兴趣的师傅可以自行查看。这就是我为什么说三种方法其实都是一条利用链的片段,三种方式追根究底都是ProcessImpl。用一句话来描述三者的关系就是:Runtime 是高层门面;ProcessBuilder 是参数配置器;ProcessImpl 是底层真正干活的实现类。
调用 Runtime 类进行命令执行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import sun.awt.windows.WPrinterJob;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;public class execRuntime { public static void main (String args[]) throws IOException { Runtime runtime = Runtime.getRuntime(); Process process = runtime.exec("whoami" ); InputStream inputStream = process.getInputStream(); byte [] bytes = new byte [100 ]; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); int readlen = 0 ; while ((readlen = inputStream.read(bytes))!=-1 ){ byteArrayOutputStream.write(bytes,0 ,readlen); } System.out.println(byteArrayOutputStream); } }
1 2 3 返回 laptop-lqh9tqqc\save
解释一下大致思路:
通过ProcessBuilder类进行命令执行 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;public class execProcessBuilder { public static void main (String[] args) throws IOException { ProcessBuilder processBuilder = new ProcessBuilder ("whoami" ); Process process = processBuilder.start(); byte [] bytes = new byte [1024 ]; InputStream inputStream = process.getInputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); int readlen = 0 ; while ((readlen=inputStream.read(bytes))!=-1 ){ byteArrayOutputStream.write(bytes,0 ,readlen); } System.out.println(byteArrayOutputStream); } }
1 2 3 返回 laptop-lqh9tqqc\save
这里原理和上面差不多,只不过ProcessBuilder的构造函数是public,我们可以new
通过ProcessImpl类进行命令执行 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 import sun.java2d.loops.ProcessPath;import java.io.ByteArrayOutputStream;import java.io.InputStream;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.Map;public class execProcessImpl { public static void main (String args[]) throws Exception { String[] cmds = new String []{"whoami" }; Class processImplClass = Class.forName("java.lang.ProcessImpl" ); Method method = processImplClass.getDeclaredMethod("start" , String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean .class); method.setAccessible(true ); Process process = (Process) method.invoke(null ,cmds,null ,"." ,null ,true ); InputStream inputStream = process.getInputStream(); byte [] cache = new byte [1024 ]; int readLen = 0 ; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); while ((readLen = inputStream.read(cache)) != -1 ){ byteArrayOutputStream.write(cache, 0 , readLen); } System.out.println(byteArrayOutputStream); } }
1 2 3 返回 laptop-lqh9tqqc\save
虽然ProcessImpl没有可用的构造函数,但我们可以反射调用,从这个利用也可以看出反射的学习多重要了吧
java反射修改变量 我们在前面也介绍了可以通过反射来修改变量的值,这里重点介绍几种特殊修饰符修饰的变量应该如何反射
Private 这个前面也聊过,但由于是下面的基础,就先再提一下
PrivatePerson.java
1 2 3 4 5 6 7 8 public class PrivatePerson { private StringBuilder name = new StringBuilder ("yunli" ); public void printInfo () { System.out.println(name); } }
varreflect.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class varreflect { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { Class c = Class.forName("org.example.PrivatePerson" ); Constructor constructor = c.getConstructor(); Object o = constructor.newInstance(); Method method = c.getDeclaredMethod("printInfo" ); method.invoke(o); Field field = c.getDeclaredField("name" ); field.setAccessible(true ); field.set(o,new StringBuilder ("yunli need hardworking" )); method.invoke(o); } }
返回
1 2 yunli yunli need hardworking
static static单独修饰的变量同样可以通过getDeclaredField去反射获取
staticPerson
1 2 3 4 5 6 7 8 9 public class staticPerson { private static StringBuilder name = new StringBuilder ("yunli" ); public void printInfo () { System.out.println(name); } }
varreflect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class varreflect { public static void main (String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { Class c = Class.forName("org.example.staticPerson" ); Constructor constructor = c.getConstructor(); Object o = constructor.newInstance(); Method method = c.getDeclaredMethod("printInfo" ); method.invoke(o); Field field = c.getDeclaredField("name" ); field.setAccessible(true ); field.set(o,new StringBuilder ("yunli need hardworking" )); method.invoke(o); } }
返回
1 2 yunli yunli need hardworking
final 对于final修饰的变量有一些不同,我们需要分情况来讨论了
我们分为两种情况:直接赋值与间接赋值,对于直接赋值的变量,我们即使进行反射修改,但改的只是堆中的值,对象中的值并不会被改变,同样序列化和反序列化中的过程中存储的还是原值。但
至于什么情况是直接赋值呢,所谓直接赋值,其实就是编译时赋值,也就是在创建字段时就对字段赋值,并且值为 JAVA 的 8 种基础数据类型或者 String 类型 ,用个更笼统的说法就是单纯字面量,用new,方法调用,表达式运算这种得出来的值都不算。直接赋值外的其他情况都属于间接赋值。
直接赋值 finalPerson.java
1 2 3 4 5 6 7 8 9 10 11 12 import java.io.Serializable;public class finalPerson implements Serializable { private final String name = "yunli" ; private final int age = 10086 ; public void printInfo () { System.out.println(name+" " +age); } }
varreflect.java
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 import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class varreflect { public static void main (String[] args) throws Exception { finalPerson finalperson = new finalPerson (); Class c = finalperson.getClass(); Constructor constructor = c.getConstructor(); Object o = constructor.newInstance(); Method method = c.getDeclaredMethod("printInfo" ); method.invoke(o); Field field = c.getDeclaredField("name" ); field.setAccessible(true ); field.set(o,"yunli need hardworking" ); Field field1 = c.getDeclaredField("age" ); field1.setAccessible(true ); field1.set(o,18 ); method.invoke(o); } }
返回
间接赋值 finalPerson.java
1 2 3 4 5 6 7 8 9 10 11 12 import java.io.Serializable;public class finalPerson implements Serializable { private final StringBuilder name = new StringBuilder ("yunli" ); private final int age = 10086 ; public void printInfo () { System.out.println(name+" " +age); } }
varreflect.java
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 import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class varreflect { public static void main (String[] args) throws Exception { finalPerson finalperson = new finalPerson (); Class c = finalperson.getClass(); Constructor constructor = c.getConstructor(); Object o = constructor.newInstance(); Method method = c.getDeclaredMethod("printInfo" ); method.invoke(o); Field field = c.getDeclaredField("name" ); field.setAccessible(true ); field.set(o,new StringBuilder ("yunli need hardworking" )); Field field1 = c.getDeclaredField("age" ); field1.setAccessible(true ); field1.set(o,18 ); method.invoke(o); } }
返回
1 2 yunli 10086 yunli need hardworking 10086
static+final 对于static final修饰的变量,我们是无法直接通过反射修改值的,尝试修改的话会报错
StaticFinalPerson.java
1 2 3 4 5 6 7 public class StaticFinalPerson { static final StringBuilder name = new StringBuilder ("yunli" ); public void printInfo () { System.out.println(name); } }
varreflect.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class varreflect { public static void main (String[] args) throws Exception { Class c = Class.forName("org.example.StaticFinalPerson" ); Constructor constructor = c.getConstructor(); Object o = constructor.newInstance(); Method method = c.getDeclaredMethod("printInfo" ); method.invoke(o); Field field = c.getDeclaredField("name" ); field.setAccessible(true ); field.set(o,new StringBuilder ("yunli need hardworking" )); method.invoke(o); } }
不过无需灰心,虽然static final的组合技确实阻碍了我们的利用,但我们把这个障碍移开不就好了,移开的方法就是去掉final修饰符
varreflect.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package org.example;import java.io.*;import java.lang.reflect.*;public class varreflect { public static void main (String[] args) throws Exception { Class c = Class.forName("org.example.StaticFinalPerson" ); Constructor constructor = c.getConstructor(); Object o = constructor.newInstance(); Method method = c.getDeclaredMethod("printInfo" ); method.invoke(o); Field field = c.getDeclaredField("name" ); field.setAccessible(true ); Field field1 = field.getClass().getDeclaredField("modifiers" ); field1.setAccessible(true ); field1.setInt(field,field.getModifiers() & ~Modifier.FINAL); field.set(o,new StringBuilder ("yunli need hardworking" )); method.invoke(o); } }
返回
1 2 yunli yunli need hardworking
反射中的代码块执行顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import java.lang.reflect.Constructor;public class FunctionSort { public static void main (String[] args) throws Exception{ Class c = Class.forName("org.example.FunctionSort$Test" ); Constructor constructor = c.getDeclaredConstructor(); Object o = constructor.newInstance(); } static class Test { { System.out.println("1" ); } static { System.out.println("2" ); } Test() { System.out.println("3" ); } } }
返回
上面的代码中我们写入了静态代码块,实例代码块,从返回结果大家也很明显能看出,我们是先调用了静态代码块,在调用实例代码块,最后才运行构造函数
在前面我们提到过静态代码块是在类初始化的时候调用的,所以当我们Class c = Class.forName("org.example.FunctionSort$Test")的时候就自动运行了静态代码块的逻辑。而实例代码块是构造器在调用的时候才运行的,它会被插到每个构造方法内,具体位置在super()之后,构造器自己代码之前。
所以反射+静态代码块是我们一个非常好用的武器。