zoukankan      html  css  js  c++  java
  • java web安全(自定义Classloader)--服务器端动态解析二进制class文件

    参考

    https://xz.aliyun.com/t/2744#reply-16082
    

     首先要让服务端有动态地将字节流解析成Class的能力,这是基础。
    正常情况下,Java并没有提供直接解析class字节数组的接口。不过classloader内部实现了一个protected的defineClass方法,可以将byte[]直接转换为Class,方法原型如下

    这个类是classloader里的,classloader是将class字节类载入虚拟机的一种形式,为了给外界提供一种加载class的途径。比如可以从网络上加载。需要同时输入类名字是java的一种规范,即字节流是一个什么类,因为如果是jar,根据包名即可知道类名,但是字节流在没有载入jvm的时候,若能告诉类名,则可以更快的返回类型。当然,如果你实在不知道类名,那也没有关系,可以为null,通常这样是可以执行的,只是这样jvm需要把所有的字节码载入完毕后再设置类名,这样会多些步骤,也会多些消耗。而且还有一种情况就是,考虑到以后也可能会在字节码中并不携带类名的可能,所以就提供了这种实现方式,但是如果是这种情况就必须指定类名了。这个方法是通过jni实现的,属于jvm的代码,其功能就是通过字节码定义类,所以是通过调用C/C++实现的,可以下载jvm源码了解详情,这里我就不多介绍了。另:同样一个类名,不同的字节码可以多次定义,我就通过这个功能实现了类似反射的功能,调用的效率比java本身的反射功能高效20倍。当然,我的这种代码只是为了高效实现一个动态语言功能,或许反射的重复调用效率或者其他场景下效率会比这种情况高,我没有办法知道这种高效场景,毕竟我不喜欢使用反射。
    

     因为该方法是protected的,我们没办法在外部直接调用,当然我们可以通过反射来修改保护属性,不过我们选择一个更方便的方法,直接自定义一个类继承classloader,然后在子类中调用父类的defineClass方法。
    下面我们写个demo来测试一下:

    Payload.java

    package define.classs;
    import java.io.IOException;
    
    public class Payload {
    	public String toString() {
    		try {
    			Runtime.getRuntime().exec("notepad.exe");
    		}catch(IOException e) {
    			e.printStackTrace();
    		}
    		return "seven god";
    	}
    }
    

     Go.java

    package define.classs;   
    import sun.misc.BASE64Decoder;
    
    public class Go {
        public static class Myloader extends ClassLoader //继承ClassLoader
        {   
            public  Class get(byte[] b)
            {
                return super.defineClass(b, 0, b.length);
            }       
        }
        public static void main(String[] args) throws Exception {
            // TODO Auto-generated method stub
            String classStr="yv66vgAAADQAKAEAFWRlZmluZS9jbGFzc3MvUGF5bG9hZAcAAQEAEGphdmEvbGFuZy9PYmplY3QHAAMBAAxQYXlsb2FkLmphdmEBAAY8aW5pdD4BAAMoKVYMAAYABwoABAAIAQAEdGhpcwEAF0xkZWZpbmUvY2xhc3NzL1BheWxvYWQ7AQAIdG9TdHJpbmcBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAE2phdmEvaW8vSU9FeGNlcHRpb24HAA4BABFqYXZhL2xhbmcvUnVudGltZQcAEAEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMABIAEwoAEQAUAQALbm90ZXBhZC5leGUIABYBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAYABkKABEAGgEAD3ByaW50U3RhY2tUcmFjZQwAHAAHCgAPAB0BAAlzZXZlbiBnb2QIAB8BAAFlAQAVTGphdmEvaW8vSU9FeGNlcHRpb247AQAEQ29kZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAD0xpbmVOdW1iZXJUYWJsZQEADVN0YWNrTWFwVGFibGUBAApTb3VyY2VGaWxlACEAAgAEAAAAAAACAAEABgAHAAEAIwAAAC8AAQABAAAABSq3AAmxAAAAAgAkAAAADAABAAAABQAKAAsAAAAlAAAABgABAAAABAABAAwADQABACMAAABpAAIAAgAAABS4ABUSF7YAG1enAAhMK7YAHhIgsAABAAAACQAMAA8AAwAkAAAAFgACAAAAFAAKAAsAAAANAAQAIQAiAAEAJQAAABIABAAAAAcACQAIAA0ACQARAAsAJgAAAAcAAkwHAA8EAAEAJwAAAAIABQ==";
            BASE64Decoder code=new sun.misc.BASE64Decoder();
            Class result=new Myloader().get(code.decodeBuffer(classStr));//将base64解码成byte数组,并传入t类的get函数
            System.out.println(result.newInstance().toString());
        }
    }
    简单解释一下上述代码:
    
        首先自定义一个类Myloader,并继承classloader父类,然后自定义一个名为get的方法,该方法接收byte数组类型的参数,然后调用父类的defineClass方法去解析byte数据,并返回解析后的Class。
        单独编写一个Payload类,并实现toString方法。因为我们想要我们的服务端尽可能的短小精悍,所以我们定义的Payload类即为默认的Object的子类,没有额外定义其他方法,因此只能借用Object类的几个默认方法,由于我们执行payload之后还要拿到执行结果,所以我们选择可以返回String类型的toString方法。把这个类编译成Payload.class文件。
        main函数中classStr变量为上述Payload.class文件二进制流的base64编码。
        新建一个Myloader的实例,将classStr解码为二进制字节流,并传入Myloader实例的get方法,得到一个Class类型的实例result,此时result即为Payload.class(注意此处的Payload.class不是上文的那个二进制文件,而是Payload这个类的class属性)。
        调用result类的默认无参构造器newInstance()生成一个Payload类的实例,然后调用该实例的toString方法,继而执行toString方法中的代码:Runtime.getRuntime().exec("calc.exe");return “OK”
        在控制台打印出toString方法的返回值。
        OK,代码解释完了,下面尝试执行Demo类,成功弹出计算器,并打印出“OK”字符串,如下图:
    

     到这里就实现了我们动态解析执行class字节流了。

  • 相关阅读:
    假期学习总结2-14
    假期学习总结2-13
    假期总结2-12
    假期总结2-11
    读人月神话
    冲刺第五天 11.29 THU
    冲刺第四天 11.28 WED
    冲刺第三天 11.27 TUE
    冲刺第二天 11.26 MON
    冲刺第一天 11.23 FRI
  • 原文地址:https://www.cnblogs.com/-zhong/p/14299988.html
Copyright © 2011-2022 走看看