Java学习

发布于 2021-04-15  21 次阅读


概念知识补充:

  1. 运行时包:java虚拟机只允许由同一个类装载器装载到同一包中的类型互相访问,而由同一类装载器装载,属于同一个包的,多个类型的集合就是我们所指的运行时包了
  2. class文件校验器

第一趟,检查class文件的二进制数据是否删改,文件长度,签名

第二趟,检查方法区的语法分析,是否能编译

第三趟,字节码校验

第四趟,符号引用转化为直接引用,检查被引用的类

  1. 安全管理器:ClassLoader的第一行代码就是检查安全管理器是否存在。对于外部资源的访问控制起中枢作用
  1. jar包的签名和认证:hash密钥附加在jar包
  2. 策略文件:java.security.Policy实现。通过密钥对对文件,代码库,代码来源设置权限
  3. 保护域:类装载器封装类的代码来源和签名者,为CodeSource对象。将策略文件读取为Policy对象,并集合。那么保护域就是形容class,permission,codeSource的一个对象

类加载机制

将类文件到虚拟机能识别利用的过程,虚拟机能直接使用的类型:java.lang,class

1)装载

先查找和导入class文件类加载器:常见ClassLoader。原则:使用双亲委派机制>>双亲委派机制不同的类加载器负责加载不同区域的类文件

如Bootstrap ClassLoader负责加载¥Java_HOME中jre/lib/rt.jar里所有的class文件等

如Extension ClassLoader负责加载java平台扩展功能的一些jar包

如Extension ClassLoader负责加载java平台扩展功能的一些jar包

如App ClassLoader负责加载classpath中指定的jar包

加载器层树:(由高到低,由父到子,但不是继承关系,而是组合关系)

Bootstrap ClassLoader

Extension ClassLoader

App ClassLoader

User ClassLoader (用户自定义加载器)

双亲委派机制就是自定义类由顶级父类加载器判断是否能加载,如果不能,则由顶级的下一级加载。保证只有能加载其的最高级加载器加载

实现方法:重写findClass方法

通常情况下,自定义的class类都是在AppClassLoader加载器层上

2)链接

验证

保证加载类的正确性

准备

为类的静态变量分配内存

解析

类中符号引用转为直接引用

public static a=3; //令a=0

3)初始化

对类的静态变量,静态代码块进行初始化

//令上面的a=3

4)运行

class->变量 常量 方法->jvm内存的分配

java类动态加载方式

显式

java反射(下一章更新)

反射机制:在运行状态中,可以知道类的所有属性和方法,对于对象可以动态调用方法和属性

反射就是把类中的成分映射成一个个对象

Class.forName会初始化类的属性和方法

ClassLoader

loadClass()方法不会初始化类方法

ClassLoader加载机制

调用 public Class<?> loadClass(String name) 方法加载类

调用findLoadedClass方法检查类是否初始化,若已则返回类对象

若无法加载,则调用自身findClass方法,通过双亲委派模型不断重写findClass方法直到子加载器,用defineClass把字节码转换成Class。若直到用户自定义加载器都无法实现加载,则返回加载失败。

如果loadClass的reslove参数为1,则调用resolveClass方法链接类。默认为0.

最后返回虚拟机加载后的java.lang.Class对象

自定义类加载器

:通过java.lang.ClassLoadera

使用自定义类加载器重写findClass方法,在调用defineClass方法时传入类的字节码向JVM中定义此类,通过反射机制调用类中方法

例如此例:

package com.anbai.sec.classloader;
//准备重写
import java.lang.reflect.Method;
//使用反射机制

public class TestClassLoader extends ClassLoader {

    // TestHelloWorld类名
    private static String testClassName = "com.anbai.sec.classloader.TestHelloWorld";

    // TestHelloWorld类字节码
    private static byte[] testClassBytes = new byte[]{
            -54, -2, -70, -66, 0, 0, 0, 51, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0,
            16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
            101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101,
            1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108,
            97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99,
            101, 70, 105, 108, 101, 1, 0, 19, 84, 101, 115, 116, 72, 101, 108, 108, 111, 87, 111,
            114, 108, 100, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 12, 72, 101, 108, 108, 111,
            32, 87, 111, 114, 108, 100, 126, 1, 0, 40, 99, 111, 109, 47, 97, 110, 98, 97, 105, 47,
            115, 101, 99, 47, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115,
            116, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 16, 106, 97, 118, 97, 47, 108,
            97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1,
            0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0,
            1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1,
            0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 10, 0, 1, 0, 11,
            0, 0, 0, 2, 0, 12
    };

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // 只处理TestHelloWorld类
        if (name.equals(testClassName)) { //判断name是否和testClassName相等
            // 调用JVM的native方法定义TestHelloWorld类
            return defineClass(testClassName, testClassBytes, 0, testClassBytes.length);
        }

        return super.findClass(name);//指向父类
    }

    public static void main(String[] args) {
        // 创建自定义的类加载器
        TestClassLoader loader = new TestClassLoader();

        try {
            // 使用自定义的类加载器加载TestHelloWorld类
            Class testClass = loader.loadClass(testClassName);

            // 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld();
            Object testInstance = testClass.newInstance();

            // 反射获取hello方法
            Method method = testInstance.getClass().getMethod("hello");

            // 反射调用hello方法,等价于 String str = t.hello();
            String str = (String) method.invoke(testInstance);

            System.out.println(str);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

//说实话我现在还看不大懂哈。。。

自定义类字节码可绕过rasp,或者加密

URLClassLoader

远程执行资源。在payload和webshell中可以利用来加载远程jar实现rce

  1. 类名.class

2. Class.forName("全类名")

3. classLoader.loadClass("类名")

String className = 'java.lang.HelloWorld';
Class exampleClassName1 = Class.forName(className);
Class exampleClassName2 =java.lang.HelloWorld.class;
Class exampleClassName3 =ClassLoader.getSystemClassLoader().loadClass(classname);

如果在知道对象的情况下可以通过对象返回其类

Class exampleClassName4=ObjectName.getClass();

获取数组类型Class对象需要用JNI描述符

[D //相当于double[].class

[[Ljava.lang.String; //相当于string[][].class

当反射调用内部类时候需要用$代替.

如,调用com.anbai.Test类中的内部类Hello

则 com.anbai,Test$Hello

反射java.lang.Runtime

原理:java.lang,Runtime中存在exec,Runtime类的()构造方法

反射Runtime执行本地命令代码:

//先通过反射获取Runtime类对象
Class runtimeClass1 = Class.forName('java.lang.Runtime');

//获取构造方法
//java.lang.Class.getDeclaredConstructor() 方法返回一个Constructor对象
Constructor constructor = runtimeClass1.getDeclaredConstructor();

//由于Runtime的构造方法时private权限,需要用反射方法修改constructor的访问权限
constructor.setAccessible(true)
/*备注1
*/

//创建Runtime类实例,这里建议使用newInstance()方法创建

备注1:《getConstructor()与getDeclaredConstructor()方法的区别及setAccessible()方法的作用(超详细)》https://blog.csdn.net/iwlnner/article/details/99674547?utm_source=app&app_version=4.5.8

具体两者区别就是getConstructor无法获取到私有方法,而前者可以通过反射获取到私有方法

备注2:https://www.cnblogs.com/yunger/p/5793669.html

反射调用类方法

获取当前类所有方法

Method[] methods = clazz.getDeclaredMethods(); 

获取当前类指定的方法

Method method = clazz.getDeclaredMethod("MethodName");

getMethod可以获取到当前类和父类所有public方法

getDeclaredMethed可以获取到当前类的所有方法

反射调用方法:Method的invoke方法

method.invoke(方法实例对象,方法参数值)

如果调用static方法第一个参数可以null

反射调用成员变量

获取当前类的所有成员变量:

Field fields = clazz.getDeclaredFields();

获取当前类指定的成员变量:

Field field = clazz.getDeclaredField("VarName");

同样有getField和getDeclaredMethod的区别

获取成员变量值:

Object obj = field.get(类实例对象);

修改成员变量值:

field.set(类实例对象,修改后值);

field.setAccessible(true)能够修改成员变量访问权限

而final修饰的成员变量,需要先修改方法 。 

sum.misc.Unsafe

反射可调用的java底层API

public final class Unsafe
    private static final Unsafe theUnsafe;

由此可知Unsafe类不可继承,不可new

(var0.getClassLoader() != null)

由之前的Bootstrap Classloader返回null可以调用

反射获取Unsafe类

获取成员变量值

// 反射获取Unsafe的theUnsafe成员变量
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");

// 反射设置theUnsafe访问权限
theUnsafeField.setAccessible(true);

// 反射获取theUnsafe成员变量值
Unsafe unsafe = (Unsafe) theUnsafeField.get(null);

获取Unsafe对象

// 获取Unsafe无参构造方法
Constructor constructor = Unsafe.class.getDeclaredConstructor();

// 修改构造方法访问权限
constructor.setAccessible(true);

// 反射创建Unsafe类实例,等价于 Unsafe unsafe1 = new Unsafe();
Unsafe unsafe1 = (Unsafe) constructor.newInstance();

allocateInstance无视构造方法创建类实例

当反射不能(被限制)用构造方法创建某类实例,Unsafe的allocateInstance方法可以绕过限制

// 使用Unsafe创建UnSafeTest类实例
UnSafeTest test = (UnSafeTest) unsafe1.allocateInstance(UnSafeTest.class);

defineClass直接调用JVM创建类对象

ClassLoader被限制的情况下,使用Unsafe的defineClass向JVM中注册一个类

// 使用Unsafe向JVM中注册com.anbai.sec.classloader.TestHelloWorld类
Class helloWorldClass = unsafe1.defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length);

间桐桜のお菓子屋さん