ClassLoader 0x00 ByteCode 	Java字节码(ByteCode)其实仅仅指的是Java虚拟机执行使用的一类指令,通常被存储 在.class文件中。字节码的本质就是一个字节数组 ,它有特定的复杂的内部格式,Java类初始化的时候会调用java.lang.ClassLoader 加载字节码,.class文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的.class文件,并创建对应的class对象,将class文件加载到虚拟机的内存,而在JVM中类的查找与装载就是由ClassLoader 完成的。
 
ByteCode2Class脚本: https://github.com/hengyunabc/dumpclass 
0x01 显式与隐式加载 Java类加载方式分为显式和隐式
显式:利用反射来加载一个类,Class.forName()等。 
隐式:通过ClassLoader来动态加载,new 一个类或者 类名.方法名返回一个类。 
 
1 2 3 4 5 6 7 8 9 10 11 @Test public  void  loadClassTest ()  throws  Exception {          Class<?> aClass = Class.forName("java.lang.Runtime" );     System.out.println(aClass.getName());          Class<?> aClass1 = ClassLoader.getSystemClassLoader().loadClass("java.lang.ProcessBuilder" );     System.out.println(aClass1.getName()); }
 
0x02 ClassLoader ClassLoader(类加载器)主要作用就是将class文件读入内存,并为之生成对应的java.lang.Class对象。
ClassLoader中的一些核心方法有:
loadClass(加载指定的Java类)  
findClass(查找指定的Java类)  
findLoadedClass(查找JVM已经加载过的类)  
defineClass(定义一个Java类)  
resolveClass(链接指定的Java类)  
 
当我们正常加载一个Class的时候,方法的执行顺序也如上所示。
内置ClassLoader JVM中存在3个内置ClassLoader:
BootstrapClassLoader 启动类加载器  负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.xxx.* 都在里面,比如 java.util.*、java.io.*、java.nio.*、java.lang.*  等等。 
ExtensionClassLoader 扩展类加载器  负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中 
AppClassLoader 系统类加载器  才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的 。 
 
自定义ClassLoader 除了内置的这三个ClassLoader,我们还可以自定义ClassLoader ,自定义ClassLoader必须继承java.lang.ClassLoader类。一个简单的demo为例,定义一个类并获取其字节码再利用自定义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 public  class  Person  {     public  String id;     private  int  age;     public  Person (String id,int  age) {         this .id=id;         this .age=age;     }     public  void  hello () {         System.out.println("func hello called." );     } }import  java.net.URI;import  java.nio.file.Files;import  java.nio.file.Paths;import  java.util.Base64;public  class  Util  {     public  static  String getByteArray (String className)  throws  Exception{       URI  uri  =  Util.class.getClassLoader().getResource(className+".class" ).toURI();       byte [] bytes = Files.readAllBytes(Paths.get(uri));       String  base64  =  Base64.getEncoder().encodeToString(bytes);       return  base64;     } }
 
编写自定义ClassLoader,并重写findClass()。
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 import  java.lang.reflect.Method;public  class  MyClassLoader  extends  ClassLoader {     private  static  String  className  =  "Person" ;     private  static  byte [] byteArray;     static  {         try  {             byteArray = Util.getByteArray(className);         } catch  (Exception e) {             e.printStackTrace();         }     }     @Override      protected  Class<?> findClass(String name) throws  ClassNotFoundException {                  if  (name.equals(className)){             return  defineClass(className, byteArray, 0 , byteArray.length );         }         return  super .findClass(name);     }     public  static  void  main (String[] args)  throws  Exception{         MyClassLoader  myClassLoader  =  new  MyClassLoader ();         Class<?> aClass = myClassLoader.loadClass(className);         Object  o  =  aClass.newInstance();                  Method  m  =  o.getClass().getMethod("hello" );         m.invoke(o);     } }
 
双亲委派模型 双亲委派模型展示了类的实际加载过程以及ClassLoader的使用顺序。
 
先不考虑自定义ClassLoader,加入我们编写一个普通类,那么它的加载过程就可以用上图来表示。
双亲委派可以简单理解为:向上委派,向下加载 
当一个.class文件要被加载时首先会在AppClassLoader检查是否已被加载,如果加载过就不再加载,如果没有被加载则向上,也就是父加载器委派,父加载器重复这个过程直到BootstrapClassLoader,如果BootstrapClassLoader也没有加载过此类则开始向下加载;首先询问BootstrapClassLoader是否可以加载,如果可以就自己加载,如果不可以则向下加载;子加载器重复这个过程直到AppClassloader,如果AppClassloader也无法加载则抛出ClassNotFoundException异常。
如果我们自定义了ClassLoader并加载类,那么就会先从自定义ClassLoader开始加载,顺序上先于AppClassLoader。 
补充 从双亲委派模型来理解上文中几个方法(loadClass、findClass….):
如果我们要自定义加载一个类,那么首先调用loadClass去检查这个类是否被加载过,同样是向上委派的过程。如果没有发现类被记载则调用findClass。 
findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass 。 
defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类。 
 
值得注意的是,defineClass方法并不会调用类的静态代码块或者构造方法,相关的调用如下:
初始化:静态代码块 
实例化:构造代码块\无参构造函数 
 
而使用Class.forName进行动态类加载有两种模式,初始化与不初始化,默认是初始化 。
指定初始化会调用静态代码块,禁止初始化不会调用静态代码块。
1 2 3 4 5  Class.forName("Person" ); Class.forName("Person" ,false ,getSystemClassLoader());
 
0x03 加载字节码的几种方式 1.利用defineClass() 上面已经提到defineClass()会将Class转换成类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import  java.lang.reflect.Method; import  java.util.Base64; public  class  HelloDefineClass  { public  static  void  main (String[] args)  throws  Exception { Method  defineClass  =   ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class);  defineClass.setAccessible(true ); byte [] code =  Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM" ); Class  hello  =  (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello" , code, 0 , code.length);  hello.newInstance();  }  } 
 
2.利用URLClassLoader URLClassLoader 实际上是我们平时默认使用的 AppClassLoader 的父类,所以,我们解释URLClassLoader 的工作过程实际上就是在解释默认的Java类加载器的工作流程。
正常情况下,Java会根据配置项 sun.boot.class.path 和 java.class.path 中列举到的基础路径(这些路径是经过处理后的 java.net.URL 类)来寻找.class文件来加载,而这个基础路径有分为三种情况:
URL未以斜杠 / 结尾,则认为是一个JAR文件,使用 JarLoader 来寻找类,即为在Jar包中寻找.class文件
 
URL以斜杠 / 结尾,且协议名是 file  ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻找.class文件
 
URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类
 
 
注意第三种情况,如果我们使用的是http协议,那么就会使用到Loader来寻找类,也就是说URLClassLoader允许远程加载类 。
1 2 3 4 5 6 7 8 9 10 11 12 import  java.net.URL; import  java.net.URLClassLoader; public  class  HelloClassLoader   { public  static  void  main ( String[] args )  throws  Exception  {  URL[] urls = {new  URL ("http://localhost:8000/" )}; URLClassLoader  loader  =  URLClassLoader.newInstance(urls); Class  c  =  loader.loadClass("Hello" );  c.newInstance();  }  }
 
3.利用xlan TemplatesImpl 是com.sun.org.apache.xalan包中一个非常有利用价值的类,它是默认包含在JDK中的。
通过调用其newTransformer()方法我们最终可以调用defineClass(),具体的细节并不复杂不再赘述,大致过程是
1 newTransformer()  ->  getTransletInstance()  ->  defineTransletClasses()  ->  defineClass()
 
当然有几个属性要满足条件才能打通利用链:
_name不为空 
_bytecodes存放字节码 
_tfactory必须是TransformerFactoryImpl类 
加载的类必须是AbstractTranslet的子类 
 
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 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;import  java.io.IOException;public  class  Test  extends  AbstractTranslet  {     public  Test () {         try  {             Runtime.getRuntime().exec("calc" );         } catch  (IOException e) {             e.printStackTrace();         }     }     @Override      public  void  transform (DOM document, SerializationHandler[] handlers)  throws  TransletException {     }     @Override      public  void  transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler)  throws  TransletException {     } }
 
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 import  com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import  com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import  java.io.FileInputStream;import  java.io.FileOutputStream;import  java.io.ObjectInputStream;import  java.io.ObjectOutputStream;import  java.lang.reflect.Field;import  java.nio.file.Files;import  java.nio.file.Paths;public  class  Client  {     public  static  void  main (String[] args)  throws  Exception {         TemplatesImpl  templates  =  new  TemplatesImpl ();         Class  templatesClass  =  templates.getClass();         Field  namefield  =  templatesClass.getDeclaredField("_name" );         namefield.setAccessible(true );         namefield.set(templates,"aaa" );                  byte [] code = Files.readAllBytes(Paths.get("c:\\tmp\\classes\\Test.class" ));         byte  [][] codes = {code};         Field  bytecodesfield  =  templatesClass.getDeclaredField("_bytecodes" );         bytecodesfield.setAccessible(true );         bytecodesfield.set(templates,codes);          Field  tfactoryfield  =  templatesClass.getDeclaredField("_tfactory" );         tfactoryfield.setAccessible(true );         tfactoryfield.set(templates, new  TransformerFactoryImpl ());         templates.newTransformer();              } }
 
4.利用BCELClassLoader 注意: BCELclassloader在jdk8u251之后的版本就无法使用 
BCEL字节码是字节码的一种,与上文中提到的字节码在本质上并无不同,仅仅是形式上的变化。
BCEL类存储在com.sun.org.apache.bcel.internal.util包中。 
BECL ClassLoader也是一种恢复成一个类并在JVM虚拟机中进行加载的字节序列。 
BCEL也是在JDK库中,在com.sun.org.apache.bcel.internal.util的包中有一个ClassLoader类,它是一个ClassLoader类,和默认的java.lang包下的ClassLoader类不同,loadClass实现不同而已。 
欲让BCELclassloader识别加载BCEL字节码,需要在开头添加 $$BCEL$$。 
 
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 import  com.sun.org.apache.bcel.internal.Repository;import  com.sun.org.apache.bcel.internal.classfile.JavaClass;import  com.sun.org.apache.bcel.internal.classfile.Utility;import  com.sun.org.apache.bcel.internal.util.ClassLoader;import  evil.Exploit;import  java.util.Base64;public  class  BCELClassLoaderDemo  {          public  static  String generateBcelCode1 (Class clazz)  throws  Exception {         JavaClass  evilJavaClazz  =  Repository.lookupClass(clazz);         String  code  =  Utility.encode(evilJavaClazz.getBytes(), true );         String  bcelCode  =  "$$BCEL$$"  + code;         System.out.println("bcelcode="  + bcelCode);         return  bcelCode;     }          public  static  String generateBcelCode2 (String classBase64)  throws  Exception {         byte [] codes = Base64.getDecoder().decode(classBase64);         String  code  =  Utility.encode(codes, true );         String  bcelCode  =  "$$BCEL$$"  + code;         System.out.println("bcelcode="  + bcelCode);         return  bcelCode;     }     public  static  void  main (String[] args)  {         try  {             ClassLoader  bcelClassLoader  =  new  ClassLoader ();             String  bcelCode  =  generateBcelCode2("yv66v...(class字节码的base64编码)..." );             bcelClassLoader.loadClass(bcelCode).newInstance();         } catch  (Exception e) {             e.printStackTrace();         }     } }
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package  evil;import  java.io.IOException;public  class  Exploit  {     static {         try  {             Runtime.getRuntime().exec("calc.exe" );         } catch  (IOException e) {             e.printStackTrace();         }     } }
 
5.利用unsafe sun.misc.Unsafe类中有defineClass()
1 2 3 4 5 6 sun.misc.Unsafe public  Class<?> defineClass(String s,                             byte [] bytes,                             int  i,                             int  i1,                             ClassLoader classLoader,                             java.security.ProtectionDomain protectionDomain)
 
我们可以直接调用
1 2 3 4 5 6 7 8 9 10 Constructor  constructor  =  Unsafe.class.getDeclaredConstructor(); constructor.setAccessible(true );Unsafe  unsafe  =  (Unsafe) constructor.newInstance(); unsafe.defineClass(<className>,<classBytes>,<offset=0 >,<length>);
 
测试用class自行编写。
unsafe的补充: https://javasec.org/javase/Unsafe