该博客为参考学习笔记博客,仅为本人记录的笔记,所以欢迎大家去Drunkbaby
师傅的博客中进行学习!Drunkbaby’s Blog
参考链接:Java反序列化基础篇-02-Java 反射与 URLDNS 链分析
一、Java反射的概念
1.1 正射与反射
我们在编写代码时,当需要使用到某一个类的时候,都会先了解这个类是做什么的。然后实例化这个类,接着用实例化好的对象进行操作,这就是正射。
1 2
| Student student = new Student(); student.doHomework("数学");
|
反射就是,一开始并不知道我们要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。我们以这一段经典的反射代码为例说明。
在上一篇中所讲的Person.java中的Person类,我们新建一个ReflectionTest.java文件来获取Class
1 2 3 4
| public static void main(String[] args) { Person p = new Person(); Class c = p.getClass(); }
|
接下来的重点问题在于如何理解Class
是什么
1.2 反射的理解
Java 的反射机制是指在==运行状态==中,对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为 Java 语言的反射机制。
以下是Drunkbaby
师傅的原文:
Java 本身是一种静态语言,为什么这么说,看这一段代码就知道了。
1
| Student student = new Student();
|
那反过来,什么是动态语言呢?PHP 本身就拥有很多动态特性,我们来看这一段代码。在这一段代码里面,我们输入 eval
,php 就执行 eval
命令;输入 echo
就执行 echo
命令;这就是语言的动态特性。
1.3 Java Class对象的理解
我们程序在运行的时候会编译生成一个 .class
文件,而这个 .class
文件中的内容就是相对应的类的所有信息,比如这段程序当中:
1 2 3 4
| public static void main(String[] args) throws Exception{ Person person = new Person(); Class c = person.getClass(); }
|
其实 **person.class
就是 Class
**,Class 也就是描述类的类。
Class 类的对象作用是运行时提供或获得某个对象的类型信息。
所以反射其实就是操作 Class
,看清楚了,是大 C
二、Java反射运用
2.1 反射组成相关的类
反射机制相关操作一般位于java.lang.reflect包中。
java反射机制组成需要重点注意以下的类:
java.lang.Class:类对象;
java.lang.reflect.Constructor:类的构造器对象;
java.lang.reflect.Field:类的属性对象;
java.lang.reflect.Method:类的方法对象;
2.2 反射的使用方法
一些比较常用的反射函数,配合着在反序列化中实现弹shell等等的操作;
获取类的方法:forName
实例化类对象的方法:newInstance
获取函数的方法:getMethod
执行函数的方法:invoke
1. 首先需要实例化对象
对于普通用户我们可以采用以下方法创建实例:
1
| Person test = new Person();
|
而我们在创建 Class 类的实例对象却不能使用上述方法,运行会抛出错误
1
| Class test = new Class();
|
因为 Class 类是 private
私有属性,我们也无法通过创建对象的方式来获取 class 对象,那么我们怎样才能够获取到 class 对象呢?一般我们获取 class 对象就有以下三种方法,我们来逐一看看。
- 实例化对象的getClass()方法
如果上下文中存在某个类的示例obj
,那么我们可以通过obj.getClass()来获取它的Class;例如:
1 2
| Person p = new Person(); Class pClass = p.getClass();
|
- 使用类的.class方法
如果你已经加载了某个类,只是想获取到它的 java.lang.Class
对象,那么就直接拿它的 class
属性即可。这个⽅法其实不属于反射。
1 2
| Class personClass = Person.class;
|
- Class.forName(String className):动态加载类
如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName
来获取,后续要利用的话是需要实例化的。
1
| Class psClass = Class.forName("com.mango.serialize.Person");
|
以上是三种获取Class的方法,我们在写一个简单的示例去演示获取Class;
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 28 29
| package com.mango.reflection;
import com.mango.serialize.Person;
public class reflectionTest { public static void main(String[] args) throws ClassNotFoundException { Person p = new Person(); Class class1 = p.getClass(); System.out.println(class1.getName());
Class class2 = Person.class; System.out.println(class2.getName());
Class class3 = Class.forName("com.mango.serialize.Person"); System.out.println(class3.getName()); } }
|
2. 获取成员变量Field
获取成员变量Field位于 java.lang.reflect.Field
包中
Field[] getFields() :获取所有 public 修饰的成员变量
Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
Field getField(String name) 获取指定名称的 public 修饰的成员变量
Field getDeclaredField(String name) 获取指定的成员变量
3. 获取成员方法Method
1 2 3 4 5 6 7
| Method getMethod(String name, Class<?>... parameterTypes) //返回该类所声明的public方法 Method getDeclaredMethod(String name, Class<?>... parameterTypes) //返回该类所声明的所有方法
Method[] getMethods() //获取所有的public方法,包括类自身声明的public方法,父类中的public方法、实现的接口方法 Method[] getDeclaredMethods() // 获取该类中的所有方法
|
在Person.java中添加以下代码:
1 2 3 4 5 6 7 8
| public void study(String s){ System.out.println("studying" + s); }
public String sleep(int age){ System.out.println("sleeping" + age); return "sleeping"; }
|
新建java文件reflectionTest2.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 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| package com.mango.reflection;
import java.lang.reflect.Method;
public class reflectionTest2 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { Class c1 = Class.forName("com.mango.serialize.Person"); Method[] methods1 = c1.getDeclaredMethods(); Method[] methods2 = c1.getMethods();
for(Method m : methods1){ System.out.println(m); }
System.out.println("---------------分割线---------------");
for(Method m : methods2){ System.out.println(m); }
System.out.println("---------------分割线---------------");
Method method3 = c1.getMethod("study", String.class); System.out.println(method3); System.out.println("---------------分割线---------------");
Method method4 = c1.getDeclaredMethod("sleep", int.class); System.out.println(method4); } }
|
运行结果:
4. 获取构造函数Constructor
1 2 3 4
| Constructor<?>[] getConstructors() :只返回public构造函数 Constructor<?>[] getDeclaredConstructors() :返回所有构造函数 Constructor<> getConstructor(Class<?>... parameterTypes) : 匹配和参数配型相符的public构造函数 Constructor<> getDeclaredConstructor(Class<?>... parameterTypes) : 匹配和参数配型相符的构造函数
|
新建一个java文件PersonConstructor.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 26 27 28 29 30 31 32 33 34 35 36
| package com.mango.entity;
public class PersonConstructor { private String name; private int age;
private PersonConstructor(int age) { this.age = age; }
private PersonConstructor(String name) { this.name = name; }
public PersonConstructor(String name, int age) { this.name = name; this.age = age; }
@Override public String toString() { return "PersonConstructor{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
|
新建一个java文件reflectionTest03.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 26 27 28 29 30 31 32 33 34
| package com.mango.reflection;
import java.lang.reflect.Constructor;
public class reflectionTest03 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { Class c = Class.forName("com.mango.entity.PersonConstructor"); Constructor[] constructors1 = c.getDeclaredConstructors(); Constructor[] constructors2 = c.getConstructors(); for (Constructor con : constructors1){ System.out.println(con); } System.out.println("---------------分割线---------------"); for (Constructor con : constructors2){ System.out.println(con); } System.out.println("---------------分割线---------------");
Constructor constructor3 = c.getConstructor(String.class, int.class); System.out.println(constructor3); System.out.println("---------------分割线---------------"); Constructor constructor4 = c.getDeclaredConstructor(String.class); System.out.println(constructor4); } }
|
运行结果:
2.3 反射创建实例对象
反射创建对象,也叫做做反射之后实例化对象,这里用到的是我们之前讲过的 newInstance()
方法
1 2
| Class c = Class.forName("ClassName"); Object o = c.newInstance();
|
这里再介绍以下invoke
方法,invoke
方法位于 java.lang.reflect.Method 类中,用于执行某个的对象的目标方法。一般会和getMethod
方法配合进行调用函数;
1
| public Object invoke(Object obj, Object... args)
|
第一个参数为类的实例,第二个参数为相应函数中的参数
但需要注意的是,invoke 方法第一个参数并不是固定的:
- 如果调用这个方法是普通方法,第一个参数就是类对象;
- 如果调用这个方法是静态方法,第一个参数就是类;
首先在Person.java
中加入方法instance
1 2 3
| public void instance(){ System.out.println("newInstance!"); }
|
然后我们重新创建一个文件reflectionTest04.java
编辑代码举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.mango.reflection;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class reflectionTest04 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Class c = Class.forName("com.mango.entity.Person"); Object o = c.newInstance(); Method method = c.getMethod("instance"); method.invoke(o); } }
|
运行结果:
其实讲到这里应该也能够理解上一节中所讲述的通过反射修改对象值的内容
三、利用反射弹计算器
在讲述这部分内容之前我们需要对Runtime
类进行了解
在Java编程中,Runtime
类提供了一种与Java应用程序的运行环境进行交互的方式。Runtime
类是一个单例类,它封装了应用程序运行时的环境,通过它,开发者可以访问JVM的某些底层特性和功能。以下是 Runtime
类的主要作用和功能:
执行系统命令
可以使用 exec
方法来执行操作系统命令,这在需要与系统进程交互时非常有用。
1 2 3 4 5
| try { Runtime.getRuntime().exec("notepad"); } catch (IOException e) { e.printStackTrace(); }
|
内存管理
关闭JVM
添加JVM关闭钩子
我们这里主要运用的是==执行系统命令==的功能,所以这里只对执行系统命令进行举例;
假设我们需要使用Runtime
类进行弹计算器,那么我们正常通过代码编写应该是
1 2 3
| public static void main(String[] args) throws IOException { Runtime.getRuntime().exec("calc"); }
|
我们来到java.lang.Runtime
中发现Runtime()
构造方法是私有的,所以我们不能直接通过newInstance
去实例化对象,所以引申出以下两种方法调用exec
方法
方法一:通过获取构造方法设置可访问属性setAccessible(true)
,然后再实例化对象即可,具体实现如下:
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
| package com.mango.reflection;
import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class reflectionRuntime { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException { Class c = Class.forName("java.lang.Runtime"); Constructor con = c.getDeclaredConstructor(); con.setAccessible(true); Object o = con.newInstance(); Method mRuntime = c.getDeclaredMethod("getRuntime"); Method mExec = c.getDeclaredMethod("exec", String.class); Object re = mRuntime.invoke(o); mExec.invoke(re, "calc"); } }
|
方法二:使用单例模式直接调用getRuntime
和exec
函数
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
| package com.mango.reflection;
import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class reflectionRuntime { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException { Class c = Class.forName("java.lang.Runtime"); Method mRuntime = c.getMethod("getRuntime"); Method mExec = c.getMethod("exec", String.class); Object re = mRuntime.invoke(c); mExec.invoke(re, "calc"); } }
|
四、总结
以上两种方式都是可以成功弹出计算器的,本次的博客是对反射机制进行介绍和一些简单的实践,后续可以通过这些内容在反序列化中进行利用;反射只是一种方式,并不能够作为一种攻击手段;后续还需要学习更多关于反序列化的知识和内容;