java反序列化入门篇-02-反射

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

解释一下大致思路:

  • 通过getRuntime方法获取Runtime实例,这里提醒一下Runtime实例是静态实例,只有一个,所以无法通过new获得

  • 通过exec执行命令,其返回是Process类型

  • 后面我们将返回的内容写入字节流再通过循环读取。InputStream 是单向管道流,读过的数据就被 “消费掉” 了,不会重复读取同一批字节,所以第一遍循环读出的是字节长度,第二遍就是-1了

通过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);


}
}

返回

1
2
yunli 10086
yunli 10086

间接赋值

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

}
}
}

返回

1
2
3
2
1
3

上面的代码中我们写入了静态代码块,实例代码块,从返回结果大家也很明显能看出,我们是先调用了静态代码块,在调用实例代码块,最后才运行构造函数

在前面我们提到过静态代码块是在类初始化的时候调用的,所以当我们Class c = Class.forName("org.example.FunctionSort$Test")的时候就自动运行了静态代码块的逻辑。而实例代码块是构造器在调用的时候才运行的,它会被插到每个构造方法内,具体位置在super()之后,构造器自己代码之前。

所以反射+静态代码块是我们一个非常好用的武器。


java反序列化入门篇-02-反射
http://blog.yunli1.com/2026/09/20/java反序列化入门篇-02-反射/
作者
yunli
发布于
2026年9月20日
许可协议