反射
Java中对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用,总之通过“反射”,我们可以将Java这种静态语言附加上动态特性。(P神的定义)
反射中几位重要的方法
- 获取类的方法:
forname
- 实例化类对象的方法:
newInstance
- 获取函数的方法:
getMethod
- 执行函数的方法:
invoke
获取类的三种方式
-
obj.getClass()
如果上下文中存在某个类的实例obj
,那么我们可以直接通过obj.getClass()
来获取它的类。Runtime runtime = Runtime.getRuntime(); System.out.println(runtime.getClass()); // class java.lang.Runtime
-
Test.class
如果你已经加载了某个类,只是想获取到它的java.lang.Class
对象,那么就直接 拿它的 class 属性即可。这个⽅法其实不属于反射。 -
Class.forName
如果你知道某个类的名字,想获取到这个类,就可以使⽤forName
来获取Class clazz = Class.forName("java.lang.Runtime"); System.out.println(clazz); // class java.lang.Runtime
使用newInstance
不成功的一些原因
- 你使用的类没有无参构造函数
- 你使用的类构造函数是私有的
案例
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "whoami");
会报错
原因是Runtime
类的构造方法是私有的
可以这样
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");
getMethod
的作用是通过反射获取一个类的特定的公有方法。
invoke
的作用是执行方法,它的第一个参数是:
- 如果这个方法是一个普通方法,那么第一个参数是类对象
- 如果这个方法是一个静态方法,那么第一个参数是类
上述命令执行的payload可以分解为
Class clazz = Class.forName("java.lang.Runtime");
Method exec = clazz.getMethod("exec", String.class);
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz);
exec.invoke(runtime, "calc.exe");
到这里P神提出了两个疑问
- 如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类 呢?
- 如果一个方法或构造方法是私有方法,我们是否能执行它呢?
第一个问题的解决方式,需要用到一个新的放射方法getConstructor
和 getMethod 类似, getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载, 所以必须用参数列表类型才能唯一确定一个构造函数。 获取到构造函数后,我们使用 newInstance 来执行。
ProcessBuilder有两个构造函数
- public ProcessBuilder(List command)
- public ProcessBuilder(String… command)
用第一种形式
主要传入List.class
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
利用反射的方式
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));
第二种形式
主要传入String[].class
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{
{
"calc.exe"}})).start();
反射的方式
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{
{
"calc.exe"}}));
第二个问题:如果一个方法或构造方法是私有方法,我们是否能执行它呢?
解决方式是getDeclared
系列的反射,它与普通的getMethod
、getConstructor
区别是:
getMethod
系列方法获取的是当前类中所有公共方法,包括从父类继承的方法getDeclaredMethod
系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私 有的方法,但从父类里继承来的就不包含了
实现代码
Class clazz = Class.forName("java.lang.Runtime");
Constructor exec = clazz.getDeclaredConstructor();
exec.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(exec.newInstance(), "calc.exe");
这里使用了一个方法 setAccessible ,这个是必须的。我们在获取到一个私有方法后,必须用 setAccessible 修改它的作用域,否则仍然不能调用。
RMI
RMI全称是Remote Method Invocation,远程方法调用。
RMI Server
package org.vulhub.RMI;
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class RmiServer {
// interface继承自interface使用extends
// interface 代表这个类没有字段 全是方法
// ⼀个继承了 java.rmi.Remote 的接⼝ 其中定义了我们要调用的类
public interface IRemoteHelloWorld extends Remote {
// 这一句是什么意思?
public String hello() throws RemoteException;
}
// extends 继承关键字
// 使用 implements 关键字可以变相的使java具有多继承的特性
// throw和throws就是异常相关的关键字
public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld {
protected RemoteHelloWorld() throws RemoteException {
super();
}
// 定义我们要远程调⽤的函数
public String hello() throws RemoteException {
System.out.println("call from");
return "Hello world";
}
}
// ⼀个主类,⽤来创建Registry
private void start() throws Exception {
RemoteHelloWorld h = new RemoteHelloWorld();
LocateRegistry.createRegistry(1099);
Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
}
public static void main(String[] args) throws Exception {
new RmiServer().start();
}
}
一个RMI Server分为三部分
- 一个继承了
java.rmi.Remote
的接口,其中定义我们要远程调用的函数,比如这里的hello()
- 一个实现了此接口的类
- 一个主类,用来创建Register,并将上面的类实例化后绑定到一个地址,这就是我们所谓的Server了。
RMI Client
package org.vulhub.Train;
import org.vulhub.RMI.RMIServer;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class <