Java反序列化-反射(二)
0x01 Java反射
Java反射(
Reflection
)是Java非常重要的动态特性,通过使用反射我们不仅可以获取到任何类的成员方法(Methods
)、成员变量(Fields
)、构造方法(Constructors
)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。Java反射机制是Java语言的动态性的重要体现,也是Java的各种框架底层实现的灵魂。 - 摘抄知识盒子园长代码审计系列
什么是反射?
Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法,本质是JVM
得到class
对象之后,再通过class
对象进行反编译,从而获取对象的各种信息。
Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM
。通过反射,可以在运行时动态创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
反射的原理
如下图
1、创建一个·Person
类,javac
会将这个这个类编译成class
文件。
2、再通过ClassLoader
类加载器将class
文件中的内容封装到Class
类对象中。
3、当new person
时,从Class
封装类中找到Person
的Class
并创建。
0x02 获取Class对象
获取Class对象的三种方法
- Class.forName(“全类名”) - 将字节码文件加载进内存,返回class对象
- 多用于配置文件,将类名定义在配置文件中
- 类名.class - 通过类名的属性class获取
- 多用于参数的传递
- 对象.getClass() - getClass()方法在Object类中定义
- 多用于对象的获取字节码的方式
同一个字节码文件在一次程序运行过程中,只会被加载一次,不论通过哪一种方法获取的Class对象都是同一个
FanDemo
package com.time.fanshe; |
Person
package com.time.fanshe; |
获取Class对象的成员变量
- Field[] getFields() :获取所有public修饰的成员变量
- Field getField(String name) 获取指定名称的 public修饰的成员变量
- Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
- Field getDeclaredField(String name)
忽略访问权限修饰符的安全检查 - setAccessible(true):暴力反射
获取Class对象的构造方法
- Constructor<?>[] getConstructors()
- Constructor
getConstructor(类<?>… parameterTypes)
- Constructor
- Constructor
getDeclaredConstructor(类<?>… parameterTypes) - Constructor<?>[] getDeclaredConstructors()
获取Class对象的成员方法
- Method[] getMethods()
- Method getMethod(String name, 类<?>… parameterTypes)
- Method[] getDeclaredMethods()
- Method getDeclaredMethod(String name, 类<?>… parameterTypes)
Demo
package com.time.fanshe; |
0x03 反射java.lang.Runtime
测试代码
package com.time.fanshe; |
运行结果
获取 java.lang.Runtime 反射类的片段
String className = "java.lang.Runtime"; |
通过以上任意一种方式就可以获取java.lang.Runtime
类的Class对象了,反射调用内部类的时候需要使用$
来代替.
,如com.anbai.Test
类有一个叫做Hello
的内部类,那么调用的时候就应该将类名写成:com.anbai.Test$Hello
反射调用Runtime
实现本地命令执行的流程如下:
- 反射获取
Runtime
类对象(Class.forName("java.lang.Runtime")
)。 - 使用
Runtime
类的Class对象获取Runtime
类的无参数构造方法(getDeclaredConstructor()
),因为Runtime
的构造方法是private
的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true)
)。 - 获取
Runtime
类的exec(String)
方法(runtimeClass1.getMethod("exec", String.class);
)。 - 调用
exec(String)
方法(runtimeMethod.invoke(runtimeInstance, cmd)
)。
上面的代码每一步都写了非常清晰的注释,接下来我们将进一步深入的了解下每一步具体含义。