JAVA 安全学习笔记(一)JAVA 反射机制
九涅·烧包包儿 / / 程序员 / 阅读量
>  Author: shaobaobaoer
>  Codes : https://github.com/ninthDevilHAUNSTER/JavaSecLearning
>  Mail: shaobaobaoer@126.com
>  WebSite: shaobaobaoer.cn

最近在学习JAVA安全。上个礼拜摸了一个JAVA_ADB项目,本来想着把ArknightsAutoHelper摸个Jar出来,想想还是工程量太大了,而且还有些JAVA高级编程还没弄通,就当是熟悉一下java的语法了。本周开始正式学习JAVA安全的知识。

0x00 Refer

感谢dalao们的博客,让我获益匪浅~

https://javasec.org/javase/

0x01 JAVA 反射机制

Java是编译型语言,我们编写的java文件需要编译成后class文件后才能够被JVM运行。

Java反射(Reflection)是Java非常重要的动态特性,通过使用反射我们不仅可以获取到任何类的成员方法(Methods)、成员变量(Fields)、构造方法(Constructors)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。Java反射机制是Java语言的动态性的重要体现。

反射获取一个 Class 对象

Java反射操作的是java.lang.Class对象,通常我们有如下3种

String className = "java.lang.Runtime";
Class<?> clazz = Class.forName(className);
Class<?> clazz = java.lang.Runtime.class;
Class<?> clazz = this.getClass().getClassLoader().loadClass(className);
//Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass(className); 
// 第三种也可以这么写,反正就是要找打一个classloader类,调用其LoadClass的方法
不要因为Import了就写成Runtime。这应该是不同两种机制。

反射获取 Class 实例

与获取类实例相关的类为 Constructor 类,并对应clazz.getConstructor(s),clazz.getDeclaredConstructor(s)方法

  • 并对应clazz.getDeclaredConstructor和 并对应clazz.getConstructor都可以获取到类构造方法,区别在于后者无法获取到私有方法
  • 传入的参数为构造函数的参数类型。
  • 之后利用  constructor.newInstance(); 可以获取 Class 实例。
// 获取构造方法
// 使用Runtime类的Class对象获取Runtime类的无参数构造方法(getDeclaredConstructor())
// 因为Runtime的构造方法是private的我们无法直接调用,所以我们需要通过反射去修改方法的访问权限(constructor.setAccessible(true))。
Constructor<?> constructor = runtimeClass1.getDeclaredConstructor();
constructor.setAccessible(true);
// 创建Runtime类示例,等价于 Runtime rt = new Runtime();
Object runtimeInstance = constructor.newInstance();

反射获取 Class 对象的方法

与获取Class方法相关的类为 Method 类,并对应clazz.getMethod(s),clazz.getDelcareMethod(s)方法

  • getMethod只能获取到当前类和父类的所有有权限的方法(如:public)
  • getDeclaredMethod能获取到当前类的所有成员方法(不包含父类)
  • getMethod 方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。(方法名+参数的class类型)
Class<?> clazz = Class.forName("java.lang.Runtime");

Method[] methods = clazz.getMethods();

for (Method method : methods) {
    System.out.println(method.getName());
}

Method method = clazz.getMethod("exec", String.class);

执行将采用method.invoke方法

    public Object invoke(Object obj, Object... args)
  • 如果该方法为  static,则第一个obj为null,  后面为该方法的参数
  • 如果该方法不为static,则第一个obj为类实例,后面为该方法的参数

反射 Class 成员变量

与获取 Class 成员变量相关的类为 Feild 类,并对应clazz.getField(s),clazz.getDelcareField(s)方法

// 反射调用类变量
Field[] f = clazz.getDeclaredFields();
for (Field _f : f) {
    String _f_name = _f.getName();
}

获取将采用.get方法,返回这个变量的类。

    public Object get(Object obj)
  • 如果该成员变量为  static,则第一个obj为null
  • 如果该成员变量不为static,则第一个obj为类实例

赋值将采用.set方法。

    public void set(Object obj, Object value)
  • 如果该成员变量为  static,则第一个obj为null   ,第二个为值。
  • 如果该成员变量不为static,则第一个obj为类实例 ,第二个为值。

反射 Class 成员变量或方法的语言修饰符

语言修饰符,就是形如static,void,final这种的。

与获取 Class 语言修饰符相关的类为 Modifier ,

图片标题

从类继承图可以得知,Field、Constructor、Method均实现了Member的接口,而Member的接口中就存在着 getModifier 这个方法。因此该方法这三个类均有实现。

实际上 getModifier 返回的是一串数字。这串数字是如何转化为 static等语言修饰符的呢?

Modifier类中定义了非常多的INT型私有常量。

    /**
     * The {@code int} value representing the {@code public}
     * modifier.
     */
    public static final int PUBLIC           = 0x00000001;

...

    /**
     * The {@code int} value representing the {@code abstract}
     * modifier.
     */
    public static final int ABSTRACT         = 0x00000400;

通过简单的按位与方法,就可以获取到语言修饰符。

修改 Class 构造函数/成员变量/方法的可访问权限

有一些方法是不能被直接调用的,因为有final、private的存在。

    Class<?> clazz = Class.forName("java.lang.Process");
    Process a = Runtime.getRuntime().exec("cmd /c dir");
    Method m = a.getClass().getDeclaredMethod("getInputStream");
    m.invoke(a);

>>>>>>>

java.lang.IllegalAccessException: class xxx cannot access a member of class java.lang.ProcessImpl (in module java.base) with modifiers "public"

    Class<?> clazz = Class.forName("java.lang.Runtime");
    Constructor<?> c = clazz.getDeclaredConstructor();
    Object o = c.newInstance();

>>>>>>>

java.lang.IllegalAccessException: class xxxx cannot access a member of class java.lang.Runtime (in module java.base) with modifiers "private"

比如说 ProcessImpl这一个类是final的,类内所有的东西都不可直接调用,需要通过反射去修改方法的访问权限。

又比如说 Runtime的构造方法是private的。我们无法直接调用,需要修改权限。

分析类继承图,可以得知Constructor,Method,Field均继承了AccessibleObject

图片标题

该类中定义了 啥叫 可达(Accessible),也可以通过setAccessible方法修改权限。

@CallerSensitive   // overrides in Method/Field/Constructor are @CS
public static void setAccessible(AccessibleObject[] array, boolean flag)

@CallerSensitive   // overrides in Method/Field/Constructor are @CS
public void setAccessible(boolean flag) 

经过权限修改,这些方法就可以访问了,但是由于@CallerSensitive,所以运行时候会有警告信息

    Class<?> clazz = Class.forName("java.lang.Runtime");
    Constructor<?> c = clazz.getDeclaredConstructor();
    c.setAccessible(true);
    Object o = c.newInstance();

综合测试

综上,可以整合一下代码,看看Runtime类的相关细节。

Class<?> runtimeClass1 = Class.forName(className);

// 反射调用类变量
System.out.println(String.format("%s 的所有类变量", className));
Field[] f = runtimeClass1.getDeclaredFields();

for (Field _f : f) {
    String _f_name = _f.getName();
    String mod = Modifier.toString(_f.getModifiers());
    System.out.println(String.format(
            "%s %s", mod, _f_name
    ));
}
System.out.println(String.format("%s 的所有构造函数", className));
Constructor<?>[] c = runtimeClass1.getDeclaredConstructors();

for (Constructor<?> _c : c) {
    String mod = Modifier.toString(_c.getModifiers()); // 取得访问权限 getModifiers返回的是字节码,通过异或(可能)操作,可以获取响应权限
    String metName = _c.getName(); // 取得方法名称
    Class<?> xx[] = _c.getParameterTypes();
    StringBuilder strb = new StringBuilder();
    for (Class<?> x :
            xx) {
        strb.append(x.getName()); // 稍微解析一下 入口参数
    }
    System.out.println(String.format(
            "%s %s(%s)", mod, metName, strb.toString().replace(";", " , ")
    ));
}


// 反射调用类方法

Method[] m = runtimeClass1.getDeclaredMethods();
System.out.println(String.format("%s 的所有方法", className));
for (Method _m : m
) {
    String mod = Modifier.toString(_m.getModifiers()); // 取得访问权限 getModifiers返回的是字节码,通过异或(可能)操作,可以获取响应权限
    String metName = _m.getName(); // 取得方法名称
    String return_type = _m.getReturnType().getName(); // 获取返回的东西
    Class<?> xx[] = _m.getParameterTypes();
    StringBuilder strb = new StringBuilder();
    for (Class<?> x :
            xx) {
        strb.append(x.getName()); // 稍微解析一下 入口参数
    }
    System.out.println(String.format(
            "%s %s(%s) -> %s", mod, metName, strb.toString().replace(";", " , "), return_type
    ));

支付宝捐赠
请使用支付宝扫一扫进行捐赠
微信捐赠
请使用微信扫一扫进行赞赏
有 0 篇文章